Async::HTTPSourceAsyncHTTPClient

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 arguments Array

Arguments to pass to Async::HTTP::Client#initialize.

parameter options Hash

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 request Protocol::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