From e0382acd91f9577e3155cfcdd0e2b275d0eb8c14 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 24 Aug 2020 17:06:03 -0400 Subject: [PATCH 1/2] Satisfy IO#readpartial API IO#readpartial allows for a second "outbuf" param which some streaming usages expect, so support it here to allow using response bodies anywhere IO can be. --- lib/http/connection.rb | 14 ++++++++------ spec/lib/http/connection_spec.rb | 8 ++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/http/connection.rb b/lib/http/connection.rb index adeac882..851d54aa 100644 --- a/lib/http/connection.rb +++ b/lib/http/connection.rb @@ -90,17 +90,19 @@ def send_request(req) # # @return [String] data chunk # @return [nil] when no more data left - def readpartial(size = BUFFER_SIZE) + def readpartial(size = BUFFER_SIZE, outbuf = nil) return unless @pending_response chunk = @parser.read(size) - return chunk if chunk - finished = (read_more(size) == :eof) || @parser.finished? - chunk = @parser.read(size) - finish_response if finished + unless chunk + finished = (read_more(size) == :eof) || @parser.finished? + chunk = @parser.read(size) + finish_response if finished + end - chunk || "".b + chunk ||= "".b + outbuf ? outbuf.replace(chunk) : chunk end # Reads data from socket up until headers are loaded diff --git a/spec/lib/http/connection_spec.rb b/spec/lib/http/connection_spec.rb index fbe5dd10..800846bf 100644 --- a/spec/lib/http/connection_spec.rb +++ b/spec/lib/http/connection_spec.rb @@ -84,5 +84,13 @@ expect(buffer).to eq "1234567890" expect(connection.finished_request?).to be true end + + it "fill outbuf when present" do + connection.read_headers! + outbuf = String.new + buffer = String.new + buffer << outbuf while connection.readpartial(2, outbuf) + expect(buffer).to eq "1234567890" + end end end From 07c1f39800654bb15f8047b3db6dad9378014364 Mon Sep 17 00:00:00 2001 From: Alexey Zapparov Date: Wed, 28 Aug 2024 20:45:41 +0200 Subject: [PATCH 2/2] feat: Make connection IO.copy_stream compatible --- lib/http/connection.rb | 9 ++++----- lib/http/response/body.rb | 8 +++++--- spec/lib/http/connection_spec.rb | 28 ++++++++++++++++++++++++---- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/lib/http/connection.rb b/lib/http/connection.rb index 851d54aa..8d7c0b96 100644 --- a/lib/http/connection.rb +++ b/lib/http/connection.rb @@ -34,7 +34,7 @@ def initialize(req, options) @pending_request = false @pending_response = false @failed_proxy_connect = false - @buffer = "".b + @buffer = String.new(capacity: BUFFER_SIZE, encoding: Encoding::BINARY) @parser = Response::Parser.new @@ -89,19 +89,18 @@ def send_request(req) # Read a chunk of the body # # @return [String] data chunk - # @return [nil] when no more data left + # @raise [EOFError] when there's no more data left def readpartial(size = BUFFER_SIZE, outbuf = nil) - return unless @pending_response + raise EOFError unless @pending_response chunk = @parser.read(size) unless chunk finished = (read_more(size) == :eof) || @parser.finished? - chunk = @parser.read(size) + chunk = @parser.read(size) || String.new(encoding: Encoding::BINARY) finish_response if finished end - chunk ||= "".b outbuf ? outbuf.replace(chunk) : chunk end diff --git a/lib/http/response/body.rb b/lib/http/response/body.rb index 8148fd08..5409afbb 100644 --- a/lib/http/response/body.rb +++ b/lib/http/response/body.rb @@ -45,14 +45,16 @@ def to_s raise StateError, "body is being streamed" unless @streaming.nil? - begin - @streaming = false - @contents = String.new("", encoding: @encoding) + @streaming = false + @contents = String.new("", encoding: @encoding) + begin while (chunk = @stream.readpartial) @contents << String.new(chunk, encoding: @encoding) chunk = nil # deallocate string end + rescue EOFError + # do nothing rescue @contents = nil raise diff --git a/spec/lib/http/connection_spec.rb b/spec/lib/http/connection_spec.rb index 800846bf..493baac4 100644 --- a/spec/lib/http/connection_spec.rb +++ b/spec/lib/http/connection_spec.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "stringio" + RSpec.describe HTTP::Connection do let(:req) do HTTP::Request.new( @@ -77,10 +79,15 @@ it "reads data in parts" do connection.read_headers! buffer = String.new - while (s = connection.readpartial(3)) - expect(connection.finished_request?).to be false if s != "" - buffer << s + + begin + while (s = connection.readpartial(3)) + expect(connection.finished_request?).to be false if s != "" + buffer << s + end + rescue EOFError end + expect(buffer).to eq "1234567890" expect(connection.finished_request?).to be true end @@ -89,8 +96,21 @@ connection.read_headers! outbuf = String.new buffer = String.new - buffer << outbuf while connection.readpartial(2, outbuf) + + begin + buffer << outbuf while connection.readpartial(2, outbuf) + rescue EOFError + end + expect(buffer).to eq "1234567890" end + + it "can be used with IO.copy_stream" do + output = StringIO.new + + IO.copy_stream(connection, output) + + expect(output.string).to eq "1234567890" + end end end