class Client
An HTTP client that manages persistent connections to a specific endpoint, with automatic retries for idempotent requests.
Definitions
def initialize(endpoint, protocol: endpoint.protocol, scheme: endpoint.scheme, authority: endpoint.authority, retries: DEFAULT_RETRIES, **options)
Provides a robust interface to a server.
- If there are no connections, it will create one.
- If there are already connections, it will reuse it.
- If a request fails, it will retry it up to N times if it was idempotent. The client object will never become unusable. It internally manages persistent connections (or non-persistent connections if that's required).
Implementation
def initialize(endpoint, protocol: endpoint.protocol, scheme: endpoint.scheme, authority: endpoint.authority, retries: DEFAULT_RETRIES, **options)
@endpoint = endpoint
@protocol = protocol
@retries = retries
@pool = make_pool(**options)
@scheme = scheme
@authority = authority
end
def as_json(...)
Signature
-
returns
Hash A JSON-compatible representation of this client.
Implementation
def as_json(...)
{
endpoint: @endpoint.to_s,
protocol: @protocol,
retries: @retries,
scheme: @scheme,
authority: @authority,
}
end
def to_json(...)
Signature
-
returns
String A JSON string representation of this client.
Implementation
def to_json(...)
as_json.to_json(...)
end
def secure?
Signature
-
returns
Boolean Whether the client uses a secure (TLS) connection.
Implementation
def secure?
@endpoint.secure?
end
def self.open(*arguments, **options, &block)
Open a client and optionally yield it, ensuring it is closed afterwards.
Signature
-
parameter
argumentsArray Arguments to pass to
Async::HTTP::Client#initialize.-
parameter
optionsHash Options to pass to
Async::HTTP::Client#initialize.
Implementation
def self.open(*arguments, **options, &block)
client = self.new(*arguments, **options)
return client unless block_given?
begin
yield client
ensure
client.close
end
end
def close
Close the client and all associated connections.
Implementation
def close
@pool.wait_until_free do
Console.warn(self){"Waiting for #{@protocol} pool to drain: #{@pool}"}
end
@pool.close
end
def call(request)
Send a request to the remote server, with automatic retries for idempotent requests.
Signature
-
parameter
requestProtocol::HTTP::Request The request to send.
-
returns
Protocol::HTTP::Response The response from the server.
Implementation
def call(request)
request.scheme ||= self.scheme
request.authority ||= self.authority
attempt = 0
# We may retry the request if it is possible to do so. https://tools.ietf.org/html/draft-nottingham-httpbis-retry-01 is a good guide for how retrying requests should work.
begin
attempt += 1
# As we cache pool, it's possible these pool go bad (e.g. closed by remote host). In this case, we need to try again. It's up to the caller to impose a timeout on this. If this is the last attempt, we force a new connection.
connection = @pool.acquire
response = make_response(request, connection, attempt)
# This signals that the ensure block below should not try to release the connection, because it's bound into the response which will be returned:
connection = nil
return response
rescue ::Protocol::HTTP::RefusedError
# This is a specific case where the request was not processed by the server. So, we can resend even non-idempotent requests.
if connection
@pool.release(connection)
connection = nil
end
if attempt < @retries
retry
else
raise
end
rescue SocketError, IOError, EOFError, Errno::ECONNRESET, Errno::EPIPE
if connection
@pool.release(connection)
connection = nil
end
if request.idempotent? and attempt < @retries
retry
else
raise
end
ensure
if connection
@pool.release(connection)
end
end
end
def inspect
Signature
-
returns
String A summary of this client.
Implementation
def inspect
"#<#{self.class} authority=#{@authority.inspect}>"
end