Skip to content

Commit 3cd6942

Browse files
Add guide for choosing a client
Assisted-By: devx/1b826335-8b80-44c3-895e-60cc3d688770
1 parent f2ac865 commit 3cd6942

3 files changed

Lines changed: 142 additions & 2 deletions

File tree

guides/choosing-a-client/readme.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Choosing a Client
2+
3+
This guide explains how to choose between ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and direct ruby:`Protocol::HTTP::Request` handling.
4+
5+
All three approaches 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.
6+
7+
## Quick Decision
8+
9+
| Situation | Interface | Why |
10+
| --- | --- | --- |
11+
| Requests may target different origins and the defaults are suitable. | Shared ruby:`Async::HTTP::Internet` | Selects and reuses a client for each origin automatically. |
12+
| Requests may target different origins, but need common client options or explicit ownership. | Explicit ruby:`Async::HTTP::Internet` | Applies the same options to each managed client and can be injected or closed early. |
13+
| Requests repeatedly target one configured origin. | ruby:`Async::HTTP::Client` | Exposes the endpoint, protocol, retry, and connection-pool configuration directly. |
14+
| A request is constructed separately or passed through middleware. | ruby:`Protocol::HTTP::Request` with `call` | Preserves the complete HTTP message across integration boundaries. |
15+
16+
Start with the shared `Internet` interface unless the application has a specific ownership or configuration requirement.
17+
18+
## Shared Internet for General Requests
19+
20+
The shared `Internet` interface is the simplest choice for requests to arbitrary URLs. It maintains one client for each origin and reuses persistent connections:
21+
22+
~~~ ruby
23+
require "async/http/internet/instance"
24+
25+
urls = [
26+
"https://www.ruby-lang.org/en/",
27+
"https://example.com/",
28+
]
29+
30+
Sync do
31+
urls.each do |url|
32+
Async::HTTP::Internet.get(url) do |response|
33+
puts "#{url}: #{response.status}"
34+
end
35+
end
36+
end
37+
~~~
38+
39+
The class-level interface uses a thread-local `Internet` instance. Its connection pools are bound to the event loop and close when that event loop exits. The response block closes each response after it is processed.
40+
41+
Use the shared interface when:
42+
43+
- The application requests URLs from multiple or dynamically selected origins.
44+
- Default retry and connection-pool settings are suitable.
45+
- The client does not need to be injected as an application dependency.
46+
47+
## Explicit Internet for Shared Configuration
48+
49+
An explicit `Internet` provides the same per-origin client selection while making ownership and client options visible. Options are passed to every client it creates; for example, `limit` applies independently to the pool for each origin:
50+
51+
~~~ ruby
52+
require "async/http/internet"
53+
54+
Sync do
55+
internet = Async::HTTP::Internet.new(retries: 1, limit: 4)
56+
57+
begin
58+
internet.get("https://www.ruby-lang.org/en/") do |response|
59+
puts response.status
60+
end
61+
ensure
62+
internet.close
63+
end
64+
end
65+
~~~
66+
67+
Use an explicit `Internet` when:
68+
69+
- Several origins should share the same retry or pool settings.
70+
- The client should be injected into another object or replaced during testing.
71+
- Connections should be released before the event loop exits.
72+
73+
## Client for One Endpoint
74+
75+
A `Client` targets one ruby:`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:
76+
77+
~~~ ruby
78+
require "async/http"
79+
80+
endpoint = Async::HTTP::Endpoint.parse("https://httpbin.org")
81+
82+
Sync do
83+
Async::HTTP::Client.open(endpoint, retries: 1, limit: 4) do |client|
84+
response = client.get("/status/200")
85+
86+
begin
87+
puts response.status
88+
ensure
89+
response.close
90+
end
91+
end
92+
end
93+
~~~
94+
95+
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.
96+
97+
Reuse a client for repeated requests rather than creating one per request; otherwise the application cannot benefit from persistent connections.
98+
99+
## Prepared Requests and Middleware
100+
101+
A ruby:`Protocol::HTTP::Request` is not another connection-management strategy. It is the complete HTTP message accepted by ruby:`Async::HTTP::Client#call` and by `Protocol::HTTP` middleware:
102+
103+
~~~ ruby
104+
require "async/http"
105+
106+
endpoint = Async::HTTP::Endpoint.parse("https://httpbin.org")
107+
108+
Sync do
109+
Async::HTTP::Client.open(endpoint) do |client|
110+
request = Protocol::HTTP::Request[
111+
"POST",
112+
"/anything",
113+
{"content-type" => "application/json"},
114+
'{"task":"refresh"}',
115+
]
116+
response = client.call(request)
117+
118+
begin
119+
puts response.status
120+
ensure
121+
response.close
122+
end
123+
end
124+
end
125+
~~~
126+
127+
Construct requests directly when another component produces the message, when using middleware, or when sending a custom HTTP method without a convenience method. The client still determines the endpoint and manages the connections.
128+
129+
For direct, in-process middleware tests, see the [Testing guide](../testing/). For the complete message interface, see the [`protocol-http` Getting Started guide](https://socketry.github.io/protocol-http/guides/getting-started/).
130+
131+
## Recommendation
132+
133+
Use the narrowest interface that matches the destination scope:
134+
135+
1. Start with the shared `Internet` interface for general URL-based requests.
136+
2. Use an explicit `Internet` when several origins need common configuration or explicit ownership.
137+
3. Use a `Client` when one endpoint is a named application dependency.
138+
4. Construct requests directly when integrating with middleware or another component that already works with HTTP messages.

guides/getting-started/readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ $ bundle add async-http
2020
- ruby:`Async::HTTP::Endpoint` describes how a client connects or a server listens, including the URL, protocol, and TLS configuration.
2121
- [`protocol-http`](https://github.com/socketry/protocol-http) provides the shared request, response, header, and body interfaces.
2222

23-
Use `Internet` for general-purpose requests to different hosts. Use `Client` when your application repeatedly communicates with one endpoint or needs endpoint-specific configuration.
23+
Use `Internet` for general-purpose requests to different hosts. Use `Client` when your application repeatedly communicates with one endpoint or needs endpoint-specific configuration. See [Choosing a Client](../choosing-a-client/) for the ownership and configuration trade-offs.
2424

2525
## Making a Request
2626

guides/links.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
getting-started:
22
order: 0
3-
testing:
3+
choosing-a-client:
44
order: 1
5+
testing:
6+
order: 2

0 commit comments

Comments
 (0)