From 3cd69427ce1b2f8ba2ec698658bf881d4be45464 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 21 Aug 2026 14:11:51 +1200 Subject: [PATCH 1/6] Add guide for choosing a client Assisted-By: devx/1b826335-8b80-44c3-895e-60cc3d688770 --- guides/choosing-a-client/readme.md | 138 +++++++++++++++++++++++++++++ guides/getting-started/readme.md | 2 +- guides/links.yaml | 4 +- 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 guides/choosing-a-client/readme.md diff --git a/guides/choosing-a-client/readme.md b/guides/choosing-a-client/readme.md new file mode 100644 index 00000000..66466b41 --- /dev/null +++ b/guides/choosing-a-client/readme.md @@ -0,0 +1,138 @@ +# Choosing a Client + +This guide explains how to choose between ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and direct ruby:`Protocol::HTTP::Request` handling. + +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. + +## Quick Decision + +| Situation | Interface | Why | +| --- | --- | --- | +| Requests may target different origins and the defaults are suitable. | Shared ruby:`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 ruby:`Async::HTTP::Internet` | Applies the same options to each managed client and can be injected or closed early. | +| Requests repeatedly target one configured origin. | ruby:`Async::HTTP::Client` | Exposes the endpoint, protocol, retry, and connection-pool configuration directly. | +| A request is constructed separately or passed through middleware. | ruby:`Protocol::HTTP::Request` with `call` | Preserves the complete HTTP message across integration boundaries. | + +Start with the shared `Internet` interface unless the application has a specific ownership or configuration requirement. + +## Shared Internet for General Requests + +The shared `Internet` interface is the simplest choice for requests to arbitrary URLs. It maintains one client for each origin and reuses persistent connections: + +~~~ ruby +require "async/http/internet/instance" + +urls = [ + "https://www.ruby-lang.org/en/", + "https://example.com/", +] + +Sync do + urls.each do |url| + Async::HTTP::Internet.get(url) do |response| + puts "#{url}: #{response.status}" + end + end +end +~~~ + +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. + +Use the shared interface when: + +- The application requests URLs from multiple or dynamically selected origins. +- Default retry and connection-pool settings are suitable. +- The client does not need to be injected as an application dependency. + +## Explicit Internet for Shared Configuration + +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: + +~~~ ruby +require "async/http/internet" + +Sync do + internet = Async::HTTP::Internet.new(retries: 1, limit: 4) + + begin + internet.get("https://www.ruby-lang.org/en/") do |response| + puts response.status + end + ensure + internet.close + end +end +~~~ + +Use an explicit `Internet` when: + +- Several origins should share the same retry or pool settings. +- The client should be injected into another object or replaced during testing. +- Connections should be released before the event loop exits. + +## Client for One Endpoint + +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: + +~~~ ruby +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. + +## Prepared Requests and Middleware + +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: + +~~~ ruby +require "async/http" + +endpoint = Async::HTTP::Endpoint.parse("https://httpbin.org") + +Sync do + Async::HTTP::Client.open(endpoint) do |client| + request = Protocol::HTTP::Request[ + "POST", + "/anything", + {"content-type" => "application/json"}, + '{"task":"refresh"}', + ] + response = client.call(request) + + begin + puts response.status + ensure + response.close + end + end +end +~~~ + +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. + +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/). + +## Recommendation + +Use the narrowest interface that matches the destination scope: + +1. Start with the shared `Internet` interface for general URL-based requests. +2. Use an explicit `Internet` when several origins need common configuration or explicit ownership. +3. Use a `Client` when one endpoint is a named application dependency. +4. Construct requests directly when integrating with middleware or another component that already works with HTTP messages. diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index 2dbe1292..403901de 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -20,7 +20,7 @@ $ bundle add async-http - ruby:`Async::HTTP::Endpoint` describes how a client connects or a server listens, including the URL, protocol, and TLS configuration. - [`protocol-http`](https://github.com/socketry/protocol-http) provides the shared request, response, header, and body interfaces. -Use `Internet` for general-purpose requests to different hosts. Use `Client` when your application repeatedly communicates with one endpoint or needs endpoint-specific configuration. +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. ## Making a Request diff --git a/guides/links.yaml b/guides/links.yaml index 89fed4cd..b57e2666 100644 --- a/guides/links.yaml +++ b/guides/links.yaml @@ -1,4 +1,6 @@ getting-started: order: 0 -testing: +choosing-a-client: order: 1 +testing: + order: 2 From a2eb80c237bf89e190521e8d9651c4cf3ebf364c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 21 Aug 2026 16:26:53 +1200 Subject: [PATCH 2/6] Document HTTP dependencies for libraries Assisted-By: devx/1b826335-8b80-44c3-895e-60cc3d688770 --- guides/choosing-a-client/readme.md | 65 +++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/guides/choosing-a-client/readme.md b/guides/choosing-a-client/readme.md index 66466b41..9c72e227 100644 --- a/guides/choosing-a-client/readme.md +++ b/guides/choosing-a-client/readme.md @@ -1,6 +1,6 @@ # Choosing a Client -This guide explains how to choose between ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and direct ruby:`Protocol::HTTP::Request` handling. +This guide explains how to choose between ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and direct ruby:`Protocol::HTTP::Request` handling. It also explains how libraries should expose their HTTP dependency. 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. @@ -13,7 +13,7 @@ All three approaches use the same request and response model. The important diff | Requests repeatedly target one configured origin. | ruby:`Async::HTTP::Client` | Exposes the endpoint, protocol, retry, and connection-pool configuration directly. | | A request is constructed separately or passed through middleware. | ruby:`Protocol::HTTP::Request` with `call` | Preserves the complete HTTP message across integration boundaries. | -Start with the shared `Internet` interface unless the application has a specific ownership or configuration requirement. +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. ## Shared Internet for General Requests @@ -96,6 +96,66 @@ Client convenience methods accept a path rather than a complete URL. They return 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: + +~~~ ruby +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 ruby:`Async::HTTP::Client` is bound to one endpoint and its convenience methods accept relative paths, while ruby:`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 ruby:`Protocol::HTTP::Request` objects and only calls `call`, it can accept a `Protocol::HTTP` middleware delegate instead of requiring a concrete client. + +For higher-level library APIs, consider these established abstractions: + +- [`async-rest`](https://socketry.github.io/async-rest/guides/getting-started/) provides resource and representation abstractions for modeling a remote HTTP API. ruby:`Async::REST::Resource` accepts a `Protocol::HTTP` middleware delegate, while its `open` method is an ownership convenience that creates and closes a ruby:`Async::HTTP::Client`. +- [`async-http-faraday`](https://socketry.github.io/async-http-faraday/guides/getting-started/) lets a library use Faraday as its public HTTP abstraction while applications select Async::HTTP as the adapter. This is useful when compatibility with the Faraday ecosystem matters; a new Async-native interface can usually accept a ruby:`Async::HTTP::Client` 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: + +~~~ ruby +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? +~~~ + ## Prepared Requests and Middleware 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: @@ -136,3 +196,4 @@ Use the narrowest interface that matches the destination scope: 2. Use an explicit `Internet` when several origins need common configuration or explicit ownership. 3. Use a `Client` when one endpoint is a named application dependency. 4. Construct requests directly when integrating with middleware or another component that already works with HTTP messages. +5. When building a library, accept and document the narrowest HTTP interface it needs; leave transport configuration and injected-client ownership to the application. From e45f47561b38171c5c2bad5d334d82198e7f781f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 21 Aug 2026 17:06:33 +1200 Subject: [PATCH 3/6] Remove redundant guide recommendation Assisted-By: devx/1b826335-8b80-44c3-895e-60cc3d688770 --- guides/choosing-a-client/readme.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/guides/choosing-a-client/readme.md b/guides/choosing-a-client/readme.md index 9c72e227..9fd7ebc6 100644 --- a/guides/choosing-a-client/readme.md +++ b/guides/choosing-a-client/readme.md @@ -187,13 +187,3 @@ end 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. 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/). - -## Recommendation - -Use the narrowest interface that matches the destination scope: - -1. Start with the shared `Internet` interface for general URL-based requests. -2. Use an explicit `Internet` when several origins need common configuration or explicit ownership. -3. Use a `Client` when one endpoint is a named application dependency. -4. Construct requests directly when integrating with middleware or another component that already works with HTTP messages. -5. When building a library, accept and document the narrowest HTTP interface it needs; leave transport configuration and injected-client ownership to the application. From a71f27b56fa92ca4e8fb84cf37f87290b33a4be1 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 21 Aug 2026 17:11:19 +1200 Subject: [PATCH 4/6] Separate higher-level client choices Assisted-By: devx/1b826335-8b80-44c3-895e-60cc3d688770 --- guides/choosing-a-client/readme.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/guides/choosing-a-client/readme.md b/guides/choosing-a-client/readme.md index 9fd7ebc6..85be49e5 100644 --- a/guides/choosing-a-client/readme.md +++ b/guides/choosing-a-client/readme.md @@ -1,8 +1,8 @@ # Choosing a Client -This guide explains how to choose between ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and direct ruby:`Protocol::HTTP::Request` handling. It also explains how libraries should expose their HTTP dependency. +This guide explains how to choose between ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, direct ruby:`Protocol::HTTP::Request` handling, and higher-level interfaces for libraries. -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. +ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and `Protocol::HTTP` middleware 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 @@ -12,6 +12,9 @@ All three approaches use the same request and response model. The important diff | 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. | | Requests repeatedly target one configured origin. | ruby:`Async::HTTP::Client` | Exposes the endpoint, protocol, retry, and connection-pool configuration directly. | | A request is constructed separately or passed through middleware. | ruby:`Protocol::HTTP::Request` with `call` | Preserves the complete HTTP message across integration boundaries. | +| A library wraps one HTTP service directly. | Injected ruby:`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`](https://socketry.github.io/async-rest/guides/getting-started/) | Provides higher-level API modeling over an injectable `Protocol::HTTP` delegate. | +| A library uses Faraday as its HTTP abstraction. | [`async-http-faraday`](https://socketry.github.io/async-http-faraday/guides/getting-started/) | 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. @@ -129,10 +132,15 @@ The library does not close an injected client because the caller owns it and may Define the accepted interface precisely. A ruby:`Async::HTTP::Client` is bound to one endpoint and its convenience methods accept relative paths, while ruby:`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 ruby:`Protocol::HTTP::Request` objects and only calls `call`, it can accept a `Protocol::HTTP` middleware delegate instead of requiring a concrete client. -For higher-level library APIs, consider these established abstractions: +## Modeling Resources with async-rest -- [`async-rest`](https://socketry.github.io/async-rest/guides/getting-started/) provides resource and representation abstractions for modeling a remote HTTP API. ruby:`Async::REST::Resource` accepts a `Protocol::HTTP` middleware delegate, while its `open` method is an ownership convenience that creates and closes a ruby:`Async::HTTP::Client`. -- [`async-http-faraday`](https://socketry.github.io/async-http-faraday/guides/getting-started/) lets a library use Faraday as its public HTTP abstraction while applications select Async::HTTP as the adapter. This is useful when compatibility with the Faraday ecosystem matters; a new Async-native interface can usually accept a ruby:`Async::HTTP::Client` directly. +Use [`async-rest`](https://socketry.github.io/async-rest/guides/getting-started/) when a library benefits from modeling a remote HTTP API as resources and representations rather than exposing request operations directly. + +ruby:`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 ruby:`Async::HTTP::Client`, yields the resource, and closes the client when the block exits. + +## Supporting Faraday with async-http-faraday + +Use [`async-http-faraday`](https://socketry.github.io/async-http-faraday/guides/getting-started/) 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 ruby:`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: From 7796f5533537d3800abde33875e096f3b85840bb Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 21 Aug 2026 17:28:58 +1200 Subject: [PATCH 5/6] Format Async HTTP references Assisted-By: devx/1b826335-8b80-44c3-895e-60cc3d688770 --- guides/choosing-a-client/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/choosing-a-client/readme.md b/guides/choosing-a-client/readme.md index 85be49e5..895c3df6 100644 --- a/guides/choosing-a-client/readme.md +++ b/guides/choosing-a-client/readme.md @@ -14,7 +14,7 @@ ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and `Protocol::HTTP` m | A request is constructed separately or passed through middleware. | ruby:`Protocol::HTTP::Request` with `call` | Preserves the complete HTTP message across integration boundaries. | | A library wraps one HTTP service directly. | Injected ruby:`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`](https://socketry.github.io/async-rest/guides/getting-started/) | Provides higher-level API modeling over an injectable `Protocol::HTTP` delegate. | -| A library uses Faraday as its HTTP abstraction. | [`async-http-faraday`](https://socketry.github.io/async-http-faraday/guides/getting-started/) | Lets the application retain the Faraday interface while using Async::HTTP as the transport. | +| A library uses Faraday as its HTTP abstraction. | [`async-http-faraday`](https://socketry.github.io/async-http-faraday/guides/getting-started/) | 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. @@ -142,7 +142,7 @@ ruby:`Async::REST::Resource` accepts a `Protocol::HTTP` middleware delegate, so Use [`async-http-faraday`](https://socketry.github.io/async-http-faraday/guides/getting-started/) 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 ruby:`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: +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: ~~~ ruby require "async/http/faraday" From 7822860ec7b3d62e4684f118f326fa2014ea46d2 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 21 Aug 2026 18:00:28 +1200 Subject: [PATCH 6/6] Remove prepared request guidance Assisted-By: devx/1b826335-8b80-44c3-895e-60cc3d688770 --- guides/choosing-a-client/readme.md | 37 ++---------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/guides/choosing-a-client/readme.md b/guides/choosing-a-client/readme.md index 895c3df6..eebba6da 100644 --- a/guides/choosing-a-client/readme.md +++ b/guides/choosing-a-client/readme.md @@ -1,8 +1,8 @@ # Choosing a Client -This guide explains how to choose between ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, direct ruby:`Protocol::HTTP::Request` handling, and higher-level interfaces for libraries. +This guide explains how to choose between ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and higher-level interfaces for libraries. -ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and `Protocol::HTTP` middleware 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. +ruby:`Async::HTTP::Internet` and ruby:`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 @@ -11,7 +11,6 @@ ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and `Protocol::HTTP` m | Requests may target different origins and the defaults are suitable. | Shared ruby:`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 ruby:`Async::HTTP::Internet` | Applies the same options to each managed client and can be injected or closed early. | | Requests repeatedly target one configured origin. | ruby:`Async::HTTP::Client` | Exposes the endpoint, protocol, retry, and connection-pool configuration directly. | -| A request is constructed separately or passed through middleware. | ruby:`Protocol::HTTP::Request` with `call` | Preserves the complete HTTP message across integration boundaries. | | A library wraps one HTTP service directly. | Injected ruby:`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`](https://socketry.github.io/async-rest/guides/getting-started/) | Provides higher-level API modeling over an injectable `Protocol::HTTP` delegate. | | A library uses Faraday as its HTTP abstraction. | [`async-http-faraday`](https://socketry.github.io/async-http-faraday/guides/getting-started/) | Lets the application retain the Faraday interface while using `Async::HTTP` as the transport. | @@ -163,35 +162,3 @@ end puts StatusService.new(connection).healthy? ~~~ - -## Prepared Requests and Middleware - -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: - -~~~ ruby -require "async/http" - -endpoint = Async::HTTP::Endpoint.parse("https://httpbin.org") - -Sync do - Async::HTTP::Client.open(endpoint) do |client| - request = Protocol::HTTP::Request[ - "POST", - "/anything", - {"content-type" => "application/json"}, - '{"task":"refresh"}', - ] - response = client.call(request) - - begin - puts response.status - ensure - response.close - end - end -end -~~~ - -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. - -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/).