Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion async-http.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "async-pool", "~> 0.11"
spec.add_dependency "io-endpoint", "~> 0.14"
spec.add_dependency "io-stream", "~> 0.6"
spec.add_dependency "protocol-http", "~> 0.62"
spec.add_dependency "protocol-http", "~> 0.64"
spec.add_dependency "protocol-http1", "~> 0.39"
spec.add_dependency "protocol-http2", "~> 0.26"
spec.add_dependency "protocol-url", "~> 0.2"
Expand Down
4 changes: 2 additions & 2 deletions lib/async/http/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def call(request)
connection = nil
end

if attempt < @retries
if attempt < @retries and request.rewind!
retry
else
raise
Expand All @@ -132,7 +132,7 @@ def call(request)
connection = nil
end

if request.idempotent? and attempt < @retries
if attempt < @retries and request.retry!
retry
else
raise
Expand Down
4 changes: 4 additions & 0 deletions releases.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Releases

## Unreleased

- Rewind request bodies before retrying requests.

## v0.97.0

- Exposed all supported protocol names from the plaintext HTTP protocol negotiator.
Expand Down
63 changes: 63 additions & 0 deletions test/async/http/client/retry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2026, by Samuel Williams.

require "async/http/client"

class RetryClient < Async::HTTP::Client
def initialize(failures)
@pool = FakePool.new
@protocol = nil
@retries = 3
@scheme = "https"
@authority = "example.com"
@failures = failures
@chunks = []
end

attr :chunks

class FakePool
def acquire
Object.new
end

def release(connection)
end
end

def make_response(request, connection, attempt)
@chunks << request.body&.read

if failure = @failures.shift
raise failure
end

return Protocol::HTTP::Response[200, {}, ["OK"]]
end
end

describe Async::HTTP::Client do
with "retries" do
it "rewinds idempotent request bodies after ambiguous failures" do
client = RetryClient.new([EOFError.new])
request = Protocol::HTTP::Request["PUT", "/", {}, ["Hello"]]

response = client.call(request)

expect(response).to be(:success?)
expect(client.chunks).to be == ["Hello", "Hello"]
end

it "rewinds non-idempotent request bodies after refused requests" do
client = RetryClient.new([Protocol::HTTP::RefusedError.new("Request not processed.")])
request = Protocol::HTTP::Request["POST", "/", {}, ["Hello"]]

response = client.call(request)

expect(response).to be(:success?)
expect(client.chunks).to be == ["Hello", "Hello"]
end
end
end
Loading