Skip to content

http.rb resubmits request body when following unsafe redirect #649

@odinhb

Description

@odinhb

I ran into a bug where http.rb raises HTTP::ConnectionError: error writing to socket: Connection reset by peer when POSTing ~3mb of xml to a server. The error's #cause is Errno::ECONNRESET: Connection reset by peer.

The reason this happened is that I needed to follow a redirect (302 FOUND) directly after a POST request, (I don't have control of this server) so I used the chainable API:

HTTP
  .follow(strict: false)
  # ...snip
  .post(url, data)

Expecting this to issue a simple GET request to the provided URL. (Maybe with the same cookies/headers).
To my surprise, the above code repeats the entire request body, which for whatever reason, the receiving server does not care to wait for before closing the connection. (I have no idea what the code of said server is actually doing).

I've reproduced the issue here. The stacktrace when running this script against the real world server looks like this:

stacktrace
TEST 11: STATUS RECEIVED: 200 OK ✓ SUCCESS

TEST 12: ❌ FAIL: error raised: HTTP::ConnectionError: error writing to socket: Connection reset by peer
STACK: /home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/timeout/null.rb:52:in `write'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/timeout/null.rb:52:in `write'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/request/writer.rb:109:in `write'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/request/writer.rb:65:in `block in send_request'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/request/writer.rb:82:in `block in each_chunk'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/request/body.rb:93:in `write'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/request/body.rb:37:in `copy_stream'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/request/body.rb:37:in `each'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/request/writer.rb:80:in `each_chunk'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/request/writer.rb:65:in `send_request'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/request/writer.rb:38:in `stream'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/request.rb:118:in `stream'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/connection.rb:76:in `send_request'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/client.rb:74:in `perform'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/client.rb:35:in `block in request'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/redirector.rb:63:in `perform'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/client.rb:34:in `request'
/home/odin/.rbenv/versions/2.6.6/gemsets/repro/gems/http-4.4.1/lib/http/chainable.rb:27:in `post'
/home/odin/dev/work/httprb-socket-mem-error-repro/http_requester.rb:30:in `request_validation!'
/home/odin/dev/work/httprb-socket-mem-error-repro/setup.rb:15:in `go!'
/home/odin/dev/work/httprb-socket-mem-error-repro/setup.rb:35:in `block in test!'
/home/odin/dev/work/httprb-socket-mem-error-repro/setup.rb:32:in `times'
/home/odin/dev/work/httprb-socket-mem-error-repro/setup.rb:32:in `test!'
main.rb:24:in `<main>'
CAUSE: Errno::ECONNRESET: Connection reset by peer

The failure is flaky. It seems that sometimes http.rb does manage to write the entire request body into the socket before it closes, probably due to network delays.

I'm not sure this gem is following a standard that says that this behaviour is indeed correct.
At any rate, I found this behaviour surprising and wish I had a simple option for getting around it. Maybe something like this:

HTTP
  .follow(strict: false, body: false) # please mr. http don't copy the body into the redirect it might be masssssssive
  # ... snip
  .post(url, data)

Environment

http.rb version: 4.4.1
ruby version: 2.6.6

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions