Skip to content

Commit

Permalink
Fixed HTTP.rb adapter, to allow streaming real responses, when WebMoc…
Browse files Browse the repository at this point in the history
…k is enabled.
  • Loading branch information
bblimke committed Feb 21, 2024
1 parent 3c9317b commit 216cad9
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 12 deletions.
42 changes: 30 additions & 12 deletions lib/webmock/http_lib_adapters/http_rb/response.rb
Expand Up @@ -6,9 +6,19 @@ def to_webmock
webmock_response = ::WebMock::Response.new

webmock_response.status = [status.to_i, reason]

webmock_response.body = body.to_s
webmock_response.headers = headers.to_h
# This call is used to reset the body of the response to enable it to be streamed if necessary.
# The `body.to_s` call above reads the body, which allows WebMock to trigger any registered callbacks.
# However, once the body is read to_s, it cannot be streamed again and attempting to do so
# will raise a "HTTP::StateError: body has already been consumed" error.
# To avoid this error, we replace the original body with a new one.
# The new body has its @stream attribute set to new Streamer, instead of the original Connection.
# Unfortunately, it's not possible to reset the original body to its initial streaming state.
# Therefore, this replacement is the best workaround currently available.
reset_body_to_allow_it_to_be_streamed!(webmock_response)

webmock_response.headers = headers.to_h
webmock_response
end

Expand All @@ -21,16 +31,7 @@ def from_webmock(request, webmock_response, request_signature = nil)
# HTTP.rb 3.0+ uses a keyword argument to pass the encoding, but 1.x
# and 2.x use a positional argument, and 0.x don't support supplying
# the encoding.
body = if HTTP::VERSION < "1.0.0"
Body.new(Streamer.new(webmock_response.body))
elsif HTTP::VERSION < "3.0.0"
Body.new(Streamer.new(webmock_response.body), webmock_response.body.encoding)
else
Body.new(
Streamer.new(webmock_response.body, encoding: webmock_response.body.encoding),
encoding: webmock_response.body.encoding
)
end
body = build_http_rb_response_body_from_webmock_response(webmock_response)

return new(status, "1.1", headers, body, uri) if HTTP::VERSION < "1.0.0"

Expand All @@ -54,7 +55,18 @@ def from_webmock(request, webmock_response, request_signature = nil)
})
end

private
def build_http_rb_response_body_from_webmock_response(webmock_response)
if HTTP::VERSION < "1.0.0"
Body.new(Streamer.new(webmock_response.body))
elsif HTTP::VERSION < "3.0.0"
Body.new(Streamer.new(webmock_response.body), webmock_response.body.encoding)
else
Body.new(
Streamer.new(webmock_response.body, encoding: webmock_response.body.encoding),
encoding: webmock_response.body.encoding
)
end
end

def normalize_uri(uri)
return unless uri
Expand All @@ -65,5 +77,11 @@ def normalize_uri(uri)
uri
end
end

private

def reset_body_to_allow_it_to_be_streamed!(webmock_response)
@body = self.class.build_http_rb_response_body_from_webmock_response(webmock_response)
end
end
end
16 changes: 16 additions & 0 deletions spec/acceptance/http_rb/http_rb_spec.rb
Expand Up @@ -98,4 +98,20 @@
http_request(:post, "http://www.example.com/", body: "abc")
end

describe "when making real requests", net_connect: true do
before do
WebMock.allow_net_connect!
end

it "should allow streaming the response body" do
response = HTTP.get("http://localhost:#{WebMockServer.instance.port}")

read_body = ""
response.body.each do |chunk|
read_body << chunk
end

expect(read_body).to eql("hello world")
end
end
end

0 comments on commit 216cad9

Please sign in to comment.