From b86d8c005ae27b12c1e5dc3c86234df2a4a981e9 Mon Sep 17 00:00:00 2001 From: thomas morgan Date: Mon, 18 Sep 2023 17:48:36 -0600 Subject: [PATCH] don't read unused request bodies --- lib/async/http/protocol/http1/server.rb | 12 +++++++--- test/async/http/protocol/http11.rb | 32 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/async/http/protocol/http1/server.rb b/lib/async/http/protocol/http1/server.rb index f5d9a168..35b48418 100644 --- a/lib/async/http/protocol/http1/server.rb +++ b/lib/async/http/protocol/http1/server.rb @@ -83,7 +83,7 @@ def each(task: Task.current) version = request.version # Same as above: - request = nil unless body + request = nil unless request.body response = nil write_body(version, body, head, trailer) @@ -97,8 +97,13 @@ def each(task: Task.current) write_body(request.version, nil) end - # Gracefully finish reading the request body if it was not already done so. - request&.finish + if request&.body + # Read one chunk to allow small or 0-length bodies to identify the body as complete. + request.body.read unless request.body.empty? + # If request body has outstanding data, will close the stream. This avoids wasting cycles if remaining + # body size is large or unknown. If body was already read, leaves stream open for potential keep-alive. + request.close + end # This ensures we yield at least once every iteration of the loop and allow other fibers to execute. task.yield @@ -109,6 +114,7 @@ def each(task: Task.current) end end end + end end end diff --git a/test/async/http/protocol/http11.rb b/test/async/http/protocol/http11.rb index af2497ff..ed40a721 100755 --- a/test/async/http/protocol/http11.rb +++ b/test/async/http/protocol/http11.rb @@ -7,6 +7,7 @@ require 'async/http/protocol/http11' require 'async/http/a_protocol' +require 'protocol/http/body/readable' describe Async::HTTP::Protocol::HTTP11 do it_behaves_like Async::HTTP::AProtocol @@ -32,6 +33,37 @@ def around end end + with 'bad request body' do + let(:app) do + Protocol::HTTP::Middleware.for do |request| + Protocol::HTTP::Response[400, {}, ["Bad request"]] + end + end + + class EndlessBody < Protocol::HTTP::Body::Readable + attr :chunks_read + def initialize + @chunks_read = 0 + end + def read + @chunks_read += 1 + Async::Task.current.yield + 'endless' + end + end + + it "should close the connection when request unread" do + body = EndlessBody.new + response = client.post('/', {}, body) + + expect(response).to be(:bad_request?) + + response.read + sleep 0.01 # brief window for multiple chunks to be read and avoid Async::Stop + expect(body.chunks_read).to be :<, 5 + end + end + with 'head request' do let(:app) do Protocol::HTTP::Middleware.for do |request|