Skip to content

Commit f4d82c5

Browse files
committed
Updated (client) support for interim responses.
1 parent 91c2fff commit f4d82c5

File tree

8 files changed

+59
-31
lines changed

8 files changed

+59
-31
lines changed

async-http.gemspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
2828
spec.add_dependency "async-pool", "~> 0.7"
2929
spec.add_dependency "io-endpoint", "~> 0.11"
3030
spec.add_dependency "io-stream", "~> 0.4"
31-
spec.add_dependency "protocol-http", "~> 0.29"
31+
spec.add_dependency "protocol-http", "~> 0.30"
3232
spec.add_dependency "protocol-http1", "~> 0.20"
3333
spec.add_dependency "protocol-http2", "~> 0.18"
3434
spec.add_dependency "traces", ">= 0.10"

fixtures/async/http/a_protocol.rb

+14-4
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,28 @@ module HTTP
4444
with "interim response" do
4545
let(:app) do
4646
::Protocol::HTTP::Middleware.for do |request|
47-
request.write_interim_response(
48-
::Protocol::HTTP::Response[103, [["link", "</style.css>; rel=preload; as=style"]]]
49-
)
47+
request.send_interim_response(103, [["link", "</style.css>; rel=preload; as=style"]])
5048

5149
::Protocol::HTTP::Response[200, {}, ["Hello World"]]
5250
end
5351
end
5452

5553
it "can read informational response" do
56-
response = client.get("/")
54+
called = false
55+
56+
callback = proc do |status, headers|
57+
called = true
58+
expect(status).to be == 103
59+
expect(headers).to have_keys(
60+
"link" => be == ["</style.css>; rel=preload; as=style"]
61+
)
62+
end
63+
64+
response = client.get("/", interim_response: callback)
5765
expect(response).to be(:success?)
5866
expect(response.read).to be == "Hello World"
67+
68+
expect(called).to be == true
5969
end
6070
end
6171

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def initialize(connection, authority, method, path, version, headers, body)
2424
# HTTP/1 requests with an upgrade header (which can contain zero or more values) are extracted into the protocol field of the request, and we expect a response to select one of those protocols with a status code of 101 Switching Protocols.
2525
protocol = headers.delete('upgrade')
2626

27-
super(nil, authority, method, path, version, headers, body, protocol)
27+
super(nil, authority, method, path, version, headers, body, protocol, self.public_method(:write_interim_response))
2828
end
2929

3030
def connection
@@ -39,8 +39,8 @@ def hijack!
3939
@connection.hijack!
4040
end
4141

42-
def write_interim_response(response)
43-
@connection.write_interim_response(response.version, response.status, response.headers)
42+
def write_interim_response(status, headers = nil)
43+
@connection.write_interim_response(@version, status, headers)
4444
end
4545
end
4646
end

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

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ def self.read(connection, request)
1717

1818
if response.final?
1919
return response
20+
else
21+
request.send_interim_response(response.status, response.headers)
2022
end
2123
end
2224
end

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

+10-10
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ def initialize(*)
2323
attr :request
2424

2525
def receive_initial_headers(headers, end_stream)
26+
@headers = ::Protocol::HTTP::Headers.new
27+
2628
headers.each do |key, value|
2729
if key == SCHEME
2830
raise ::Protocol::HTTP2::HeaderError, "Request scheme already specified!" if @request.scheme
@@ -85,7 +87,7 @@ def closed(error)
8587
end
8688

8789
def initialize(stream)
88-
super(nil, nil, nil, nil, VERSION, nil)
90+
super(nil, nil, nil, nil, VERSION, nil, nil, nil, self.public_method(:write_interim_response))
8991

9092
@stream = stream
9193
end
@@ -117,10 +119,6 @@ def send_response(response)
117119
[STATUS, response.status],
118120
]
119121

120-
if protocol = response.protocol
121-
protocol_headers << [PROTOCOL, protocol]
122-
end
123-
124122
if length = response.body&.length
125123
protocol_headers << [CONTENT_LENGTH, length]
126124
end
@@ -142,14 +140,16 @@ def send_response(response)
142140
end
143141
end
144142

145-
def write_interim_response(response)
146-
protocol_headers = [
147-
[STATUS, response.status]
143+
def write_interim_response(status, headers = nil)
144+
interim_response_headers = [
145+
[STATUS, status]
148146
]
149147

150-
headers = ::Protocol::HTTP::Headers::Merged.new(protocol_headers, response.headers)
148+
if headers
149+
interim_response_headers = ::Protocol::HTTP::Headers::Merged.new(interim_response_headers, headers)
150+
end
151151

152-
@stream.send_headers(nil, headers)
152+
@stream.send_headers(nil, interim_response_headers)
153153
end
154154
end
155155
end

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

+27-10
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,25 @@ def accept_push_promise_stream(promised_stream_id, headers)
3838

3939
# This should be invoked from the background reader, and notifies the task waiting for the headers that we are done.
4040
def receive_initial_headers(headers, end_stream)
41+
# While in theory, the response pseudo-headers may be extended in the future, currently they only response pseudo-header is :status, so we can assume it is always the first header.
42+
status_header = headers.shift
43+
44+
if status_header.first != ':status'
45+
raise ProtocolError, "Invalid response headers: #{headers.inspect}"
46+
end
47+
48+
status = Integer(status_header.last)
49+
50+
if status >= 100 && status < 200
51+
return receive_interim_headers(status, headers)
52+
end
53+
54+
@response.status = status
55+
@headers = ::Protocol::HTTP::Headers.new
56+
4157
headers.each do |key, value|
4258
# It's guaranteed that this should be the first header:
43-
if key == STATUS
44-
status = Integer(value)
45-
46-
# Ignore informational headers:
47-
return if status >= 100 && status < 200
48-
49-
@response.status = Integer(value)
50-
elsif key == PROTOCOL
51-
@response.protocol = value
52-
elsif key == CONTENT_LENGTH
59+
if key == CONTENT_LENGTH
5360
@length = Integer(value)
5461
else
5562
add_header(key, value)
@@ -74,6 +81,16 @@ def receive_initial_headers(headers, end_stream)
7481
return headers
7582
end
7683

84+
def receive_interim_headers(status, headers)
85+
if headers.any?
86+
headers = ::Protocol::HTTP::Headers[headers]
87+
else
88+
headers = nil
89+
end
90+
91+
@response.request.send_interim_response(status, headers)
92+
end
93+
7794
# Notify anyone waiting on the response headers to be received (or failure).
7895
def notify!
7996
if notification = @notification

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,9 @@ def receive_trailing_headers(headers, end_stream)
5151
end
5252

5353
def process_headers(frame)
54-
if frame.end_stream? && @headers
54+
if @headers and frame.end_stream?
5555
self.receive_trailing_headers(super, frame.end_stream?)
5656
else
57-
@headers ||= ::Protocol::HTTP::Headers.new
5857
self.receive_initial_headers(super, frame.end_stream?)
5958
end
6059

lib/async/http/protocol/request.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def hijack?
2525
false
2626
end
2727

28-
def write_interim_response(response)
28+
def write_interim_response(status, headers = nil)
2929
end
3030

3131
def peer

0 commit comments

Comments
 (0)