Choosing a Client
This guide explains how to choose between class Async::HTTP::Internet, class Async::HTTP::Client, and higher-level interfaces for libraries.
class Async::HTTP::Internet and class Async::HTTP::Client use the same request and response model. The important differences are how destinations are selected, where connection settings are applied, and who owns the client life cycle.
Quick Decision
| Situation | Interface | Why |
|---|---|---|
| Requests may target different origins and the defaults are suitable. | Shared class Async::HTTP::Internet |
Selects and reuses a client for each origin automatically. |
| Requests may target different origins, but need common client options or explicit ownership. | Explicit class Async::HTTP::Internet |
Applies the same options to each managed client and can be injected or closed early. |
| Requests repeatedly target one configured origin. | class Async::HTTP::Client |
Exposes the endpoint, protocol, retry, and connection-pool configuration directly. |
| A library wraps one HTTP service directly. | Injected class Async::HTTP::Client or Protocol::HTTP middleware |
Leaves transport configuration, ownership, and testing under application control. |
| A library models an HTTP API as resources and representations. | async-rest |
Provides higher-level API modeling over an injectable Protocol::HTTP delegate. |
| A library uses Faraday as its HTTP abstraction. | async-http-faraday |
Lets the application retain the Faraday interface while using Async::HTTP as the transport. |
Application code can start with the shared Internet interface unless it has a specific ownership or configuration requirement. Library code should accept an explicit HTTP dependency.
Client for One Endpoint
A Client targets one class Async::HTTP::Endpoint. Use it when a remote service is a stable part of the application architecture and needs its own protocol, TLS, retry, or pool configuration:
require "async/http"
endpoint = Async::HTTP::Endpoint.parse("https://httpbin.org")
Sync do
Async::HTTP::Client.open(endpoint, retries: 1, limit: 4) do |client|
response = client.get("/status/200")
begin
puts response.status
ensure
response.close
end
end
end
Client convenience methods accept a path rather than a complete URL. They return a response that the caller must close. Client.open closes the client and its connection pool when the block exits.
Reuse a client for repeated requests rather than creating one per request; otherwise the application cannot benefit from persistent connections.
Building a Library That Makes HTTP Requests
A library should generally accept its HTTP client as an explicit dependency. This lets the application configure connection limits, retries, proxies, instrumentation, and test doubles without the library creating hidden global state:
require "async/http"
class StatusService
def initialize(client)
@client = client
end
def healthy?
response = @client.get("/status/200")
response.status == 200
ensure
response&.close
end
end
endpoint = Async::HTTP::Endpoint.parse("https://httpbin.org")
Sync do
Async::HTTP::Client.open(endpoint) do |client|
puts StatusService.new(client).healthy?
end
end
The library does not close an injected client because the caller owns it and may share it with other components. If the library also provides an open convenience method that constructs a client, that method should close the client it creates when its block exits.
Define the accepted interface precisely. A class Async::HTTP::Client is bound to one endpoint and its convenience methods accept relative paths, while class Async::HTTP::Internet selects an endpoint from a complete URL. They should not be treated as interchangeable merely because both provide methods such as get. If the library constructs Protocol::HTTP::Request objects and only calls call, it can accept a Protocol::HTTP middleware delegate instead of requiring a concrete client.
Modeling Resources with async-rest
Use async-rest when a library benefits from modeling a remote HTTP API as resources and representations rather than exposing request operations directly.
Async::REST::Resource accepts a Protocol::HTTP middleware delegate, so the application can supply and configure the transport. Its open method provides the complementary convenience interface: it creates a class Async::HTTP::Client, yields the resource, and closes the client when the block exits.
Supporting Faraday with async-http-faraday
Use async-http-faraday when a library uses Faraday as its public HTTP abstraction or needs compatibility with the Faraday ecosystem. A new Async-native library can usually accept a class Async::HTTP::Client or Protocol::HTTP middleware delegate directly.
If a library uses Faraday, accept a configured Faraday::Connection rather than changing Faraday.default_adapter globally. The application can then select the Async::HTTP adapter for that connection:
require "async/http/faraday"
class StatusService
def initialize(connection)
@connection = connection
end
def healthy?
@connection.get("/status/200").success?
end
end
connection = Faraday.new("https://httpbin.org") do |builder|
builder.adapter :async_http
end
puts StatusService.new(connection).healthy?