Skip to content

Commit 19e8885

Browse files
committed
Improve body handling, especially HEAD requests.
1 parent 9443325 commit 19e8885

File tree

6 files changed

+56
-36
lines changed

6 files changed

+56
-36
lines changed

async-http.gemspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
2121
spec.add_dependency("async-io", "~> 1.27.0")
2222
spec.add_dependency("async-pool", "~> 0.2")
2323

24-
spec.add_dependency("protocol-http", "~> 0.14.1")
24+
spec.add_dependency("protocol-http", "~> 0.15.1")
2525
spec.add_dependency("protocol-http1", "~> 0.10.0")
2626
spec.add_dependency("protocol-http2", "~> 0.11.0")
2727

lib/async/http/protocol/http2/request.rb

+21-26
Original file line numberDiff line numberDiff line change
@@ -165,36 +165,31 @@ def push(path, headers = nil, scheme = @scheme, authority = @authority)
165165

166166
def send_response(response)
167167
if response.nil?
168-
@stream.send_headers(nil, NO_RESPONSE, ::Protocol::HTTP2::END_STREAM)
169-
elsif response.body? && !self.head?
170-
pseudo_headers = [
171-
[STATUS, response.status],
172-
]
173-
174-
if protocol = response.protocol
175-
pseudo_headers << [PROTOCOL, protocol]
176-
end
177-
178-
if length = response.body.length
179-
pseudo_headers << [CONTENT_LENGTH, length]
180-
end
181-
182-
headers = ::Protocol::HTTP::Headers::Merged.new(
183-
pseudo_headers,
184-
response.headers
185-
)
186-
168+
return @stream.send_headers(nil, NO_RESPONSE, ::Protocol::HTTP2::END_STREAM)
169+
end
170+
171+
protocol_headers = [
172+
[STATUS, response.status],
173+
]
174+
175+
if protocol = response.protocol
176+
protocol_headers << [PROTOCOL, protocol]
177+
end
178+
179+
if length = response.body&.length
180+
protocol_headers << [CONTENT_LENGTH, length]
181+
end
182+
183+
headers = ::Protocol::HTTP::Headers::Merged.new(protocol_headers, response.headers)
184+
185+
if body = response.body and !self.head?
187186
@stream.send_headers(nil, headers)
188-
@stream.send_body(response.body)
187+
@stream.send_body(body)
189188
else
190-
headers = ::Protocol::HTTP::Headers::Merged.new([
191-
[STATUS, response.status],
192-
], response.headers)
189+
# Ensure the response body is closed if we are ending the stream:
190+
response.close
193191

194192
@stream.send_headers(nil, headers, ::Protocol::HTTP2::END_STREAM)
195-
196-
# If the response had a body but it was not sent, close it (e.g. HEAD request).
197-
response.body&.close
198193
end
199194
end
200195
end

lib/async/http/protocol/http2/response.rb

+11-5
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,15 @@ def receive_initial_headers(headers, end_stream)
7575

7676
@response.headers = @headers
7777

78-
unless @response.valid?
79-
send_reset_stream(::Protocol::HTTP2::Error::PROTOCOL_ERROR)
80-
else
81-
# We only construct the input/body if data is coming.
82-
unless end_stream
78+
if @response.valid?
79+
if !end_stream
80+
# We only construct the input/body if data is coming.
8381
@response.body = prepare_input(@length)
82+
elsif @response.head?
83+
@response.body = ::Protocol::HTTP::Body::Head.new(@length)
8484
end
85+
else
86+
send_reset_stream(::Protocol::HTTP2::Error::PROTOCOL_ERROR)
8587
end
8688

8789
self.notify!
@@ -140,6 +142,10 @@ def wait
140142
@stream.wait
141143
end
142144

145+
def head?
146+
@request&.head?
147+
end
148+
143149
def valid?
144150
!!@status
145151
end

lib/async/http/protocol/http2/stream.rb

+12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def initialize(stream, length)
3434
super(length)
3535

3636
@stream = stream
37+
@remaining = length
3738
end
3839

3940
def read
@@ -42,6 +43,17 @@ def read
4243
@stream.request_window_update
4344
end
4445

46+
# We track the expected length and check we got what we were expecting.
47+
if @remaining
48+
if chunk
49+
@remaining -= chunk.bytesize
50+
elsif @remaining > 0
51+
raise EOFError, "Expected #{self.length} bytes, #{@remaining} bytes short!"
52+
elsif @remaining < 0
53+
raise EOFError, "Expected #{self.length} bytes, #{@remaining} bytes over!"
54+
end
55+
end
56+
4557
return chunk
4658
end
4759
end

spec/async/http/protocol/http11_spec.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939

4040
expect(response).to be_success
4141
expect(response.version).to be == "HTTP/1.1"
42-
expect(response.body).to be nil
42+
expect(response.body).to be_empty
43+
4344
response.read
4445
end
4546
end

spec/async/http/protocol/shared_examples.rb

+9-3
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
end
8787
end
8888

89-
context 'working server' do
89+
context 'with working server' do
9090
let(:server) do
9191
Async::HTTP::Server.for(endpoint, protocol) do |request|
9292
if request.method == 'POST'
@@ -108,7 +108,7 @@
108108
expect(server.scheme).to be == "http"
109109
end
110110

111-
context 'GET' do
111+
context 'using GET method' do
112112
let(:expected) {"GET #{protocol::VERSION}"}
113113

114114
context 'with response' do
@@ -124,6 +124,10 @@
124124
expect(response.read).to eq expected
125125
end
126126

127+
it "provides content length" do
128+
expect(response.body.length).to_not be_nil
129+
end
130+
127131
let(:tempfile) {Tempfile.new}
128132

129133
it "can save to disk" do
@@ -170,7 +174,9 @@
170174

171175
it "is successful and without body" do
172176
expect(response).to be_success
173-
expect(response.body).to be_nil
177+
expect(response.body).to_not be_nil
178+
expect(response.body).to be_empty
179+
expect(response.body.length).to_not be_nil
174180
expect(response.read).to be_nil
175181
end
176182
end

0 commit comments

Comments
 (0)