From 789983a51eab41005534dac532f75d5bff43f954 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 25 Oct 2023 01:09:35 +1300 Subject: [PATCH] Ignore non-final responses. (#137) --- async-http.gemspec | 4 ++-- fixtures/async/http/a_protocol.rb | 22 ++++++++++++++++++++-- lib/async/http/protocol/http1/request.rb | 4 ++++ lib/async/http/protocol/http1/response.rb | 18 +++++++++++------- lib/async/http/protocol/http2/request.rb | 10 ++++++++++ lib/async/http/protocol/http2/response.rb | 6 ++++++ lib/async/http/protocol/http2/stream.rb | 8 +++----- lib/async/http/protocol/request.rb | 3 +++ 8 files changed, 59 insertions(+), 16 deletions(-) diff --git a/async-http.gemspec b/async-http.gemspec index 0323abee..e39a79b7 100644 --- a/async-http.gemspec +++ b/async-http.gemspec @@ -22,8 +22,8 @@ Gem::Specification.new do |spec| spec.add_dependency "async", ">= 1.25" spec.add_dependency "async-io", ">= 1.28" spec.add_dependency "async-pool", ">= 0.2" - spec.add_dependency "protocol-http", "~> 0.24.0" - spec.add_dependency "protocol-http1", "~> 0.15.0" + spec.add_dependency "protocol-http", "~> 0.25.0" + spec.add_dependency "protocol-http1", "~> 0.16.0" spec.add_dependency "protocol-http2", "~> 0.15.0" spec.add_dependency "traces", ">= 0.10.0" end diff --git a/fixtures/async/http/a_protocol.rb b/fixtures/async/http/a_protocol.rb index a151efea..c9a40c9e 100644 --- a/fixtures/async/http/a_protocol.rb +++ b/fixtures/async/http/a_protocol.rb @@ -41,8 +41,26 @@ module HTTP end end - with "huge body", timeout: 600 do - let(:body) {::Protocol::HTTP::Body::File.open("/dev/zero", size: 512*1024**2)} + with "interim response" do + let(:app) do + ::Protocol::HTTP::Middleware.for do |request| + request.write_interim_response( + ::Protocol::HTTP::Response[103, [["link", "; rel=preload; as=style"]]] + ) + + ::Protocol::HTTP::Response[200, {}, ["Hello World"]] + end + end + + it "can read informational response" do + response = client.get("/") + expect(response).to be(:success?) + expect(response.read).to be == "Hello World" + end + end + + with "huge body" do + let(:body) {::Protocol::HTTP::Body::File.open("/dev/zero", size: 8*1024**2)} let(:app) do ::Protocol::HTTP::Middleware.for do |request| diff --git a/lib/async/http/protocol/http1/request.rb b/lib/async/http/protocol/http1/request.rb index b482bbb3..37d5c8b3 100644 --- a/lib/async/http/protocol/http1/request.rb +++ b/lib/async/http/protocol/http1/request.rb @@ -38,6 +38,10 @@ def hijack? def hijack! @connection.hijack! end + + def write_interim_response(response) + @connection.write_interim_response(response.version, response.status, response.headers) + end end end end diff --git a/lib/async/http/protocol/http1/response.rb b/lib/async/http/protocol/http1/response.rb index f3026825..0803da38 100644 --- a/lib/async/http/protocol/http1/response.rb +++ b/lib/async/http/protocol/http1/response.rb @@ -11,17 +11,21 @@ module Protocol module HTTP1 class Response < Protocol::Response def self.read(connection, request) - if parts = connection.read_response(request.method) - self.new(connection, *parts) + while parts = connection.read_response(request.method) + response = self.new(connection, *parts) + + if response.final? + return response + end end end UPGRADE = 'upgrade' - - # @attribute [String] The HTTP response line reason. + + # @attribute [String] The HTTP response line reason. attr :reason - - # @parameter reason [String] HTTP response line reason phrase + + # @parameter reason [String] HTTP response line reason phrase. def initialize(connection, version, status, reason, headers, body) @connection = connection @reason = reason @@ -34,7 +38,7 @@ def initialize(connection, version, status, reason, headers, body) def connection @connection end - + def hijack? @body.nil? end diff --git a/lib/async/http/protocol/http2/request.rb b/lib/async/http/protocol/http2/request.rb index f28fbefa..4fe519d6 100644 --- a/lib/async/http/protocol/http2/request.rb +++ b/lib/async/http/protocol/http2/request.rb @@ -141,6 +141,16 @@ def send_response(response) @stream.send_headers(nil, headers, ::Protocol::HTTP2::END_STREAM) end end + + def write_interim_response(response) + protocol_headers = [ + [STATUS, response.status] + ] + + headers = ::Protocol::HTTP::Headers::Merged.new(protocol_headers, response.headers) + + @stream.send_headers(nil, headers) + end end end end diff --git a/lib/async/http/protocol/http2/response.rb b/lib/async/http/protocol/http2/response.rb index e919ca55..fccebcbd 100644 --- a/lib/async/http/protocol/http2/response.rb +++ b/lib/async/http/protocol/http2/response.rb @@ -39,7 +39,13 @@ def accept_push_promise_stream(promised_stream_id, headers) # This should be invoked from the background reader, and notifies the task waiting for the headers that we are done. def receive_initial_headers(headers, end_stream) headers.each do |key, value| + # It's guaranteed that this should be the first header: if key == STATUS + status = Integer(value) + + # Ignore informational headers: + return if status >= 100 && status < 200 + @response.status = Integer(value) elsif key == PROTOCOL @response.protocol = value diff --git a/lib/async/http/protocol/http2/stream.rb b/lib/async/http/protocol/http2/stream.rb index 4bbfbb92..7cb3c876 100644 --- a/lib/async/http/protocol/http2/stream.rb +++ b/lib/async/http/protocol/http2/stream.rb @@ -50,13 +50,11 @@ def receive_trailing_headers(headers, end_stream) end def process_headers(frame) - if @headers.nil? - @headers = ::Protocol::HTTP::Headers.new - self.receive_initial_headers(super, frame.end_stream?) - elsif frame.end_stream? + if frame.end_stream? && @headers self.receive_trailing_headers(super, frame.end_stream?) else - raise ::Protocol::HTTP2::HeaderError, "Unable to process headers!" + @headers ||= ::Protocol::HTTP::Headers.new + self.receive_initial_headers(super, frame.end_stream?) end # TODO this might need to be in an ensure block: diff --git a/lib/async/http/protocol/request.rb b/lib/async/http/protocol/request.rb index 5a6e8a15..6782718d 100644 --- a/lib/async/http/protocol/request.rb +++ b/lib/async/http/protocol/request.rb @@ -25,6 +25,9 @@ def hijack? false end + def write_interim_response(response) + end + def peer if connection = self.connection connection.peer