|
| 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. |
0 commit comments