From 2ad324f4e971e4d78d71d2b3b397e3d922830504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janko=20Marohni=C4=87?= Date: Mon, 23 Oct 2017 21:33:19 +0200 Subject: [PATCH 1/2] Add Request::Body#source This makes it possible to access the original body object. --- lib/http/request/body.rb | 46 ++++++++++++++++-------------- spec/lib/http/request/body_spec.rb | 6 ++++ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/lib/http/request/body.rb b/lib/http/request/body.rb index cef79b5d..690ce28a 100644 --- a/lib/http/request/body.rb +++ b/lib/http/request/body.rb @@ -3,25 +3,27 @@ module HTTP class Request class Body - def initialize(body) - @body = body + attr_reader :source - validate_body_type! + def initialize(source) + @source = source + + validate_source_type! end # Returns size which should be used for the "Content-Length" header. # # @return [Integer] def size - if @body.is_a?(String) - @body.bytesize - elsif @body.respond_to?(:read) - raise RequestError, "IO object must respond to #size" unless @body.respond_to?(:size) - @body.size - elsif @body.nil? + if @source.is_a?(String) + @source.bytesize + elsif @source.respond_to?(:read) + raise RequestError, "IO object must respond to #size" unless @source.respond_to?(:size) + @source.size + elsif @source.nil? 0 else - raise RequestError, "cannot determine size of body: #{@body.inspect}" + raise RequestError, "cannot determine size of body: #{@source.inspect}" end end @@ -29,24 +31,24 @@ def size # # @yieldparam [String] def each(&block) - if @body.is_a?(String) - yield @body - elsif @body.respond_to?(:read) - IO.copy_stream(@body, ProcIO.new(block)) - elsif @body.is_a?(Enumerable) - @body.each(&block) + if @source.is_a?(String) + yield @source + elsif @source.respond_to?(:read) + IO.copy_stream(@source, ProcIO.new(block)) + elsif @source.is_a?(Enumerable) + @source.each(&block) end end private - def validate_body_type! - return if @body.is_a?(String) - return if @body.respond_to?(:read) - return if @body.is_a?(Enumerable) - return if @body.nil? + def validate_source_type! + return if @source.is_a?(String) + return if @source.respond_to?(:read) + return if @source.is_a?(Enumerable) + return if @source.nil? - raise RequestError, "body of wrong type: #{@body.class}" + raise RequestError, "body of wrong type: #{@source.class}" end # This class provides a "writable IO" wrapper around a proc object, with diff --git a/spec/lib/http/request/body_spec.rb b/spec/lib/http/request/body_spec.rb index 020e34b4..86d99097 100644 --- a/spec/lib/http/request/body_spec.rb +++ b/spec/lib/http/request/body_spec.rb @@ -46,6 +46,12 @@ end end + describe "#source" do + it "returns the original object" do + expect(subject.source).to eq "" + end + end + describe "#size" do context "when body is nil" do let(:body) { nil } From 9a2868245bb7ede5e1e17e2b2e3ab00e30cb9801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janko=20Marohni=C4=87?= Date: Sat, 11 Nov 2017 02:10:05 +0100 Subject: [PATCH 2/2] Create deflated body right before streaming starts This makes it easier for 3rd-party libraries to inspect and log the request body, because they can rely on HTTP::Request#body always returning a HTTP::Request::Body object, instead of a DeflatedBody object when :auto_deflate is enabled. We also define HTTP::Request::Body#== for easier testing. --- lib/http/request.rb | 24 ++++++++++-------------- lib/http/request/body.rb | 5 +++++ spec/lib/http/request/body_spec.rb | 29 +++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/lib/http/request.rb b/lib/http/request.rb index 9d74c86d..66e11b8d 100644 --- a/lib/http/request.rb +++ b/lib/http/request.rb @@ -86,7 +86,8 @@ def initialize(opts) raise(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(@scheme) @proxy = opts[:proxy] || {} - @body = request_body(opts[:body], opts) + @body = Request::Body.new(opts[:body]) + @deflate = opts[:auto_deflate] @version = opts[:version] || "1.1" @headers = HTTP::Headers.coerce(opts[:headers] || {}) @@ -100,18 +101,20 @@ def redirect(uri, verb = @verb) headers.delete(Headers::HOST) self.class.new( - :verb => verb, - :uri => @uri.join(uri), - :headers => headers, - :proxy => proxy, - :body => body, - :version => version + :verb => verb, + :uri => @uri.join(uri), + :headers => headers, + :proxy => proxy, + :body => body.source, + :auto_deflate => @deflate, + :version => version ) end # Stream the request to a socket def stream(socket) include_proxy_headers if using_proxy? && !@uri.https? + body = @deflate ? @deflate.deflated_body(self.body) : self.body Request::Writer.new(socket, body, headers, headline).stream end @@ -186,13 +189,6 @@ def socket_port private - # Transforms body to an object suitable for streaming. - def request_body(body, opts) - body = Request::Body.new(body) unless body.is_a?(Request::Body) - body = opts[:auto_deflate].deflated_body(body) if opts[:auto_deflate] - body - end - # @!attribute [r] host # @return [String] def_delegator :@uri, :host diff --git a/lib/http/request/body.rb b/lib/http/request/body.rb index 690ce28a..4c5379de 100644 --- a/lib/http/request/body.rb +++ b/lib/http/request/body.rb @@ -40,6 +40,11 @@ def each(&block) end end + # Request bodies are equivalent when they have the same source. + def ==(other) + self.class == other.class && self.source == other.source # rubocop:disable Style/RedundantSelf + end + private def validate_source_type! diff --git a/spec/lib/http/request/body_spec.rb b/spec/lib/http/request/body_spec.rb index 86d99097..3616ba29 100644 --- a/spec/lib/http/request/body_spec.rb +++ b/spec/lib/http/request/body_spec.rb @@ -141,4 +141,33 @@ end end end + + describe "#==" do + context "when sources are equivalent" do + let(:body1) { HTTP::Request::Body.new("content") } + let(:body2) { HTTP::Request::Body.new("content") } + + it "returns true" do + expect(body1).to eq body2 + end + end + + context "when sources are not equivalent" do + let(:body1) { HTTP::Request::Body.new("content") } + let(:body2) { HTTP::Request::Body.new(nil) } + + it "returns false" do + expect(body1).not_to eq body2 + end + end + + context "when objects are not of the same class" do + let(:body1) { HTTP::Request::Body.new("content") } + let(:body2) { "content" } + + it "returns false" do + expect(body1).not_to eq body2 + end + end + end end