async-http 0.101.0 → 0.102.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/choosing-a-client.md +164 -0
- data/context/concurrent-requests.md +134 -0
- data/context/getting-started.md +106 -69
- data/context/index.yaml +14 -3
- data/context/testing.md +173 -45
- data/lib/async/http/body/hijack.rb +1 -1
- data/lib/async/http/body/pipe.rb +2 -2
- data/lib/async/http/body.rb +1 -1
- data/lib/async/http/internet.rb +1 -1
- data/lib/async/http/middleware/location_redirector.rb +1 -1
- data/lib/async/http/mock/endpoint.rb +1 -1
- data/lib/async/http/protocol/configurable.rb +1 -1
- data/lib/async/http/protocol/http.rb +1 -1
- data/lib/async/http/protocol/http1/finishable.rb +2 -1
- data/lib/async/http/protocol/http1/request.rb +1 -1
- data/lib/async/http/protocol/http1/response.rb +1 -1
- data/lib/async/http/protocol/http1.rb +1 -1
- data/lib/async/http/protocol/http10.rb +1 -1
- data/lib/async/http/protocol/http11.rb +1 -1
- data/lib/async/http/protocol/http2/client.rb +7 -2
- data/lib/async/http/protocol/http2/connection.rb +20 -6
- data/lib/async/http/protocol/http2/input.rb +14 -3
- data/lib/async/http/protocol/http2/output.rb +23 -8
- data/lib/async/http/protocol/http2/response.rb +15 -2
- data/lib/async/http/protocol/http2/server.rb +1 -1
- data/lib/async/http/protocol/http2/stream.rb +61 -4
- data/lib/async/http/protocol/http2.rb +1 -1
- data/lib/async/http/protocol/request.rb +1 -1
- data/lib/async/http/protocol/response.rb +1 -1
- data/lib/async/http/proxy.rb +1 -1
- data/lib/async/http/statistics.rb +1 -1
- data/lib/async/http/version.rb +1 -1
- data/lib/async/http.rb +1 -1
- data/lib/traces/provider/async/http/client.rb +1 -1
- data/lib/traces/provider/async/http/protocol/http1/client.rb +1 -1
- data/lib/traces/provider/async/http/protocol/http2/client.rb +1 -1
- data/lib/traces/provider/async/http/server.rb +1 -1
- data/license.md +2 -1
- data/readme.md +18 -13
- data/releases.md +5 -0
- data.tar.gz.sig +0 -0
- metadata +11 -6
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39dbd69fca4e67586111ac9fc21231b0a4aff0ade936468c8851707004d9c456
|
|
4
|
+
data.tar.gz: c10435cb4d54c2220170624931c87ed77302eb01ff8c9b70a89c22bc56c551cb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0367722b925fb45a0517f1890aac1b724aa025d9db58843f034e8b9cc03ffb18044398a442b6ffff92cd4e0ac0a04773730c22384a21640521865e4dd3858993
|
|
7
|
+
data.tar.gz: 7602b933368edfdbc7e812fab78193a5436ed7a95de8214ecc3ba42618e7c20c2b26c10309c6609952381992387c23d2f4a5ff30b7bff25dd4a315055fd5d24d
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Choosing a Client
|
|
2
|
+
|
|
3
|
+
This guide explains how to choose between ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and higher-level interfaces for libraries.
|
|
4
|
+
|
|
5
|
+
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.
|
|
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 library wraps one HTTP service directly. | Injected ruby:`Async::HTTP::Client` or `Protocol::HTTP` middleware | Leaves transport configuration, ownership, and testing under application control. |
|
|
15
|
+
| 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. |
|
|
16
|
+
| 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. |
|
|
17
|
+
|
|
18
|
+
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.
|
|
19
|
+
|
|
20
|
+
## Shared Internet for General Requests
|
|
21
|
+
|
|
22
|
+
The shared `Internet` interface is the simplest choice for requests to arbitrary URLs. It maintains one client for each origin and reuses persistent connections:
|
|
23
|
+
|
|
24
|
+
~~~ ruby
|
|
25
|
+
require "async/http/internet/instance"
|
|
26
|
+
|
|
27
|
+
urls = [
|
|
28
|
+
"https://www.ruby-lang.org/en/",
|
|
29
|
+
"https://example.com/",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
Sync do
|
|
33
|
+
urls.each do |url|
|
|
34
|
+
Async::HTTP::Internet.get(url) do |response|
|
|
35
|
+
puts "#{url}: #{response.status}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
~~~
|
|
40
|
+
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
Use the shared interface when:
|
|
44
|
+
|
|
45
|
+
- The application requests URLs from multiple or dynamically selected origins.
|
|
46
|
+
- Default retry and connection-pool settings are suitable.
|
|
47
|
+
- The client does not need to be injected as an application dependency.
|
|
48
|
+
|
|
49
|
+
## Explicit Internet for Shared Configuration
|
|
50
|
+
|
|
51
|
+
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:
|
|
52
|
+
|
|
53
|
+
~~~ ruby
|
|
54
|
+
require "async/http/internet"
|
|
55
|
+
|
|
56
|
+
Sync do
|
|
57
|
+
internet = Async::HTTP::Internet.new(retries: 1, limit: 4)
|
|
58
|
+
|
|
59
|
+
begin
|
|
60
|
+
internet.get("https://www.ruby-lang.org/en/") do |response|
|
|
61
|
+
puts response.status
|
|
62
|
+
end
|
|
63
|
+
ensure
|
|
64
|
+
internet.close
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
~~~
|
|
68
|
+
|
|
69
|
+
Use an explicit `Internet` when:
|
|
70
|
+
|
|
71
|
+
- Several origins should share the same retry or pool settings.
|
|
72
|
+
- The client should be injected into another object or replaced during testing.
|
|
73
|
+
- Connections should be released before the event loop exits.
|
|
74
|
+
|
|
75
|
+
## Client for One Endpoint
|
|
76
|
+
|
|
77
|
+
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:
|
|
78
|
+
|
|
79
|
+
~~~ ruby
|
|
80
|
+
require "async/http"
|
|
81
|
+
|
|
82
|
+
endpoint = Async::HTTP::Endpoint.parse("https://httpbin.org")
|
|
83
|
+
|
|
84
|
+
Sync do
|
|
85
|
+
Async::HTTP::Client.open(endpoint, retries: 1, limit: 4) do |client|
|
|
86
|
+
response = client.get("/status/200")
|
|
87
|
+
|
|
88
|
+
begin
|
|
89
|
+
puts response.status
|
|
90
|
+
ensure
|
|
91
|
+
response.close
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
~~~
|
|
96
|
+
|
|
97
|
+
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.
|
|
98
|
+
|
|
99
|
+
Reuse a client for repeated requests rather than creating one per request; otherwise the application cannot benefit from persistent connections.
|
|
100
|
+
|
|
101
|
+
## Building a Library That Makes HTTP Requests
|
|
102
|
+
|
|
103
|
+
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:
|
|
104
|
+
|
|
105
|
+
~~~ ruby
|
|
106
|
+
require "async/http"
|
|
107
|
+
|
|
108
|
+
class StatusService
|
|
109
|
+
def initialize(client)
|
|
110
|
+
@client = client
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def healthy?
|
|
114
|
+
response = @client.get("/status/200")
|
|
115
|
+
response.status == 200
|
|
116
|
+
ensure
|
|
117
|
+
response&.close
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
endpoint = Async::HTTP::Endpoint.parse("https://httpbin.org")
|
|
122
|
+
|
|
123
|
+
Sync do
|
|
124
|
+
Async::HTTP::Client.open(endpoint) do |client|
|
|
125
|
+
puts StatusService.new(client).healthy?
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
~~~
|
|
129
|
+
|
|
130
|
+
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.
|
|
131
|
+
|
|
132
|
+
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.
|
|
133
|
+
|
|
134
|
+
## Modeling Resources with async-rest
|
|
135
|
+
|
|
136
|
+
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.
|
|
137
|
+
|
|
138
|
+
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.
|
|
139
|
+
|
|
140
|
+
## Supporting Faraday with async-http-faraday
|
|
141
|
+
|
|
142
|
+
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.
|
|
143
|
+
|
|
144
|
+
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:
|
|
145
|
+
|
|
146
|
+
~~~ ruby
|
|
147
|
+
require "async/http/faraday"
|
|
148
|
+
|
|
149
|
+
class StatusService
|
|
150
|
+
def initialize(connection)
|
|
151
|
+
@connection = connection
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def healthy?
|
|
155
|
+
@connection.get("/status/200").success?
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
connection = Faraday.new("https://httpbin.org") do |builder|
|
|
160
|
+
builder.adapter :async_http
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
puts StatusService.new(connection).healthy?
|
|
164
|
+
~~~
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Concurrent Requests and Connection Pooling
|
|
2
|
+
|
|
3
|
+
This guide explains how to run HTTP requests concurrently while keeping request fan-out, connection usage, and resource life cycles bounded.
|
|
4
|
+
|
|
5
|
+
Concurrent requests allow independent network operations to overlap. `Async::HTTP` combines this task concurrency with persistent connection pools, but the number of request tasks and the number of connections are separate controls.
|
|
6
|
+
|
|
7
|
+
## Running Independent Requests Concurrently
|
|
8
|
+
|
|
9
|
+
When several requests do not depend on each other, start one child task for each request and then wait for their results:
|
|
10
|
+
|
|
11
|
+
~~~ ruby
|
|
12
|
+
require "async/http/internet/instance"
|
|
13
|
+
require "json"
|
|
14
|
+
|
|
15
|
+
names = ["async", "async-http", "falcon"]
|
|
16
|
+
|
|
17
|
+
versions = Sync do |task|
|
|
18
|
+
tasks = names.map do |name|
|
|
19
|
+
task.async do
|
|
20
|
+
url = "https://rubygems.org/api/v1/gems/#{name}.json"
|
|
21
|
+
|
|
22
|
+
Async::HTTP::Internet.get(url) do |response|
|
|
23
|
+
raise "Could not fetch #{name}: #{response.status}" unless response.success?
|
|
24
|
+
|
|
25
|
+
JSON.parse(response.read).fetch("version")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
tasks.map(&:wait)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
names.zip(versions) do |name, version|
|
|
34
|
+
puts "#{name}: #{version}"
|
|
35
|
+
end
|
|
36
|
+
~~~
|
|
37
|
+
|
|
38
|
+
Each request can make progress while the others are waiting for network I/O. ruby:`Async::Task#wait` returns the task result and re-raises any failure, so request errors are not silently discarded. Results are collected in the original order even if requests finish in a different order.
|
|
39
|
+
|
|
40
|
+
The response block closes each response after its body is processed. General task creation, cancellation, and failure propagation are covered by the [`async` Tasks guide](https://socketry.github.io/async/guides/tasks/).
|
|
41
|
+
|
|
42
|
+
## Limiting Request Concurrency
|
|
43
|
+
|
|
44
|
+
Creating one task per item is appropriate for a small, fixed collection. A large or externally supplied collection should have an explicit concurrency limit so it cannot overwhelm the remote service or retain an unbounded number of pending operations.
|
|
45
|
+
|
|
46
|
+
Use ruby:`Async::Semaphore` to set a fixed request limit and ruby:`Async::Barrier` to wait for all the work:
|
|
47
|
+
|
|
48
|
+
~~~ ruby
|
|
49
|
+
require "async"
|
|
50
|
+
require "async/semaphore"
|
|
51
|
+
require "async/http/internet"
|
|
52
|
+
|
|
53
|
+
names = ["async", "async-http", "falcon", "protocol-http", "io-event", "console"]
|
|
54
|
+
|
|
55
|
+
Sync do
|
|
56
|
+
internet = Async::HTTP::Internet.new(limit: 2)
|
|
57
|
+
|
|
58
|
+
begin
|
|
59
|
+
Barrier(parent: nil) do |barrier|
|
|
60
|
+
requests = Async::Semaphore.new(4, parent: barrier)
|
|
61
|
+
|
|
62
|
+
names.each do |name|
|
|
63
|
+
requests.async do
|
|
64
|
+
url = "https://rubygems.org/api/v1/gems/#{name}.json"
|
|
65
|
+
|
|
66
|
+
internet.get(url) do |response|
|
|
67
|
+
raise "Could not fetch #{name}: #{response.status}" unless response.success?
|
|
68
|
+
|
|
69
|
+
puts "#{name}: #{response.status}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
ensure
|
|
75
|
+
internet.close
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
~~~
|
|
79
|
+
|
|
80
|
+
The semaphore allows at most four request operations to run at once. The barrier tracks those tasks, waits for them to finish, propagates failures, and cancels unfinished work if the block exits early. `parent: nil` disables the barrier's load-based scheduling because the semaphore already provides an explicit limit.
|
|
81
|
+
|
|
82
|
+
See the [`async` Best Practices guide](https://socketry.github.io/async/guides/best-practices/) for general guidance on barriers, semaphores, and large workloads.
|
|
83
|
+
|
|
84
|
+
## Request Limits and Connection Limits
|
|
85
|
+
|
|
86
|
+
Request concurrency and pool capacity solve different problems:
|
|
87
|
+
|
|
88
|
+
| Control | What It Limits | Scope |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| ruby:`Async::Semaphore` | Concurrent request operations and their application work. | The tasks started through that semaphore. |
|
|
91
|
+
| `limit` passed to ruby:`Async::HTTP::Client` | Connections held by one client pool. | One configured endpoint. |
|
|
92
|
+
| `limit` passed to ruby:`Async::HTTP::Internet` | Connections held by each client it creates. | Applied independently to every origin. |
|
|
93
|
+
|
|
94
|
+
The client creates connections lazily and reuses viable persistent connections. When its pool reaches `limit`, a request waits until an existing connection has capacity or is released.
|
|
95
|
+
|
|
96
|
+
For HTTP/1, a connection normally processes one request at a time, so connection capacity also constrains active requests. HTTP/2 can multiplex several request streams over one connection, so a small connection pool may support much higher request concurrency. Use a semaphore when the application needs a protocol-independent request limit; use `limit` to control connection resources.
|
|
97
|
+
|
|
98
|
+
## Releasing Connections for Reuse
|
|
99
|
+
|
|
100
|
+
A response retains pool capacity until its body is finished or closed. Always close responses promptly, especially before starting more requests that use the same limited pool:
|
|
101
|
+
|
|
102
|
+
~~~ ruby
|
|
103
|
+
require "async/http"
|
|
104
|
+
|
|
105
|
+
endpoint = Async::HTTP::Endpoint.parse("https://rubygems.org")
|
|
106
|
+
|
|
107
|
+
Sync do
|
|
108
|
+
Async::HTTP::Client.open(endpoint, limit: 1) do |client|
|
|
109
|
+
response = client.get("/api/v1/gems/async-http.json")
|
|
110
|
+
|
|
111
|
+
begin
|
|
112
|
+
raise "Download failed: #{response.status}" unless response.success?
|
|
113
|
+
|
|
114
|
+
bytes = 0
|
|
115
|
+
|
|
116
|
+
response.each do |chunk|
|
|
117
|
+
bytes += chunk.bytesize
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
puts "Downloaded #{bytes} bytes."
|
|
121
|
+
ensure
|
|
122
|
+
response.close
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
~~~
|
|
127
|
+
|
|
128
|
+
The block form of ruby:`Async::HTTP::Internet` performs this cleanup automatically. When using ruby:`Async::HTTP::Client`, close the response explicitly with an `ensure` block. Holding an unread response while waiting for another request can exhaust an HTTP/1 pool and make the second request wait indefinitely.
|
|
129
|
+
|
|
130
|
+
## Pool Life Cycle
|
|
131
|
+
|
|
132
|
+
Connection pools are bound to the event loop in which they are used. Their internal maintenance task is transient, so it does not keep the event loop alive and closes the pool when the event loop exits.
|
|
133
|
+
|
|
134
|
+
Use the shared ruby:`Async::HTTP::Internet` interface when connections can remain open until the event loop exits. Use ruby:`Async::HTTP::Client.open`, or explicitly close an `Internet` or `Client`, when connections should be released earlier. See [Choosing a Client](../choosing-a-client/) for the ownership trade-offs.
|
data/context/getting-started.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Getting Started
|
|
2
2
|
|
|
3
|
-
This guide explains how to
|
|
3
|
+
This guide explains how to make HTTP requests and serve HTTP responses with `Async::HTTP`.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -12,137 +12,174 @@ $ bundle add async-http
|
|
|
12
12
|
|
|
13
13
|
## Core Concepts
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
- {ruby Async::HTTP::Internet} provides a simple interface for making requests to any server "on the internet".
|
|
17
|
-
- {ruby Async::HTTP::Server} is the main class for handling HTTP requests.
|
|
18
|
-
- {ruby Async::HTTP::Endpoint} can parse HTTP URLs in order to create a client or server.
|
|
19
|
-
- [`protocol-http`](https://github.com/socketry/protocol-http) provides the abstract HTTP protocol interfaces.
|
|
15
|
+
`Async::HTTP` provides several interfaces for different kinds of HTTP applications:
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
- ruby:`Async::HTTP::Internet` makes requests to arbitrary hosts and manages a client for each remote endpoint.
|
|
18
|
+
- ruby:`Async::HTTP::Client` manages persistent connections to a specific endpoint.
|
|
19
|
+
- ruby:`Async::HTTP::Server` accepts connections and dispatches requests to an HTTP application.
|
|
20
|
+
- ruby:`Async::HTTP::Endpoint` describes how a client connects or a server listens, including the URL, protocol, and TLS configuration.
|
|
21
|
+
- [`protocol-http`](https://github.com/socketry/protocol-http) provides the shared request, response, header, and body interfaces.
|
|
22
22
|
|
|
23
|
-
|
|
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.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
## Making a Request
|
|
26
|
+
|
|
27
|
+
The shared ruby:`Async::HTTP::Internet` instance provides a convenient starting point. Run asynchronous HTTP operations inside `Sync`, which creates or reuses the event loop while returning the block result directly:
|
|
26
28
|
|
|
27
29
|
~~~ ruby
|
|
28
|
-
require
|
|
30
|
+
require "async/http/internet/instance"
|
|
29
31
|
|
|
30
32
|
Sync do
|
|
31
33
|
Async::HTTP::Internet.get("https://httpbin.org/get") do |response|
|
|
34
|
+
puts "Status: #{response.status}"
|
|
32
35
|
puts response.read
|
|
33
36
|
end
|
|
34
37
|
end
|
|
35
38
|
~~~
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
Passing a block automatically closes the response when the block exits, including when an exception is raised. Responses are streamed, so callers that do not use the block form must close the response explicitly.
|
|
38
41
|
|
|
39
42
|
~~~ ruby
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
require "async/http/internet/instance"
|
|
44
|
+
|
|
45
|
+
Sync do
|
|
46
|
+
response = Async::HTTP::Internet.get("https://httpbin.org/get")
|
|
47
|
+
puts response.read
|
|
48
|
+
ensure
|
|
49
|
+
response&.close
|
|
50
|
+
end
|
|
42
51
|
~~~
|
|
43
52
|
|
|
44
|
-
|
|
53
|
+
Convenience methods are provided for `GET`, `HEAD`, `POST`, `PUT`, `DELETE`, `CONNECT`, `OPTIONS`, `TRACE`, `PATCH`, and `QUERY` requests.
|
|
54
|
+
|
|
55
|
+
### Connection Persistence
|
|
56
|
+
|
|
57
|
+
`Internet` creates a ruby:`Async::HTTP::Client` for each remote endpoint and reuses its persistent connections. The underlying async pools are bound to the event loop and are closed when that event loop exits.
|
|
58
|
+
|
|
59
|
+
An explicitly created `Internet` can also be closed early when an application wants to release all cached clients before the event loop exits:
|
|
45
60
|
|
|
46
61
|
~~~ ruby
|
|
47
|
-
require
|
|
62
|
+
require "async/http/internet"
|
|
48
63
|
|
|
49
64
|
Sync do
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
internet = Async::HTTP::Internet.new
|
|
66
|
+
|
|
67
|
+
internet.get("https://example.com") do |response|
|
|
68
|
+
puts response.status
|
|
69
|
+
end
|
|
52
70
|
ensure
|
|
53
|
-
|
|
71
|
+
internet&.close
|
|
54
72
|
end
|
|
55
73
|
~~~
|
|
56
74
|
|
|
57
|
-
|
|
75
|
+
See [Concurrent Requests and Connection Pooling](../concurrent-requests/) to run independent requests together and configure separate limits for request tasks and connections.
|
|
58
76
|
|
|
59
|
-
|
|
77
|
+
## Working with Responses
|
|
60
78
|
|
|
61
|
-
|
|
79
|
+
A response contains a status, headers, and a streaming body. Check the status before processing content, and use header names in lower case:
|
|
80
|
+
|
|
81
|
+
~~~ ruby
|
|
82
|
+
require "async/http/internet/instance"
|
|
83
|
+
|
|
84
|
+
Sync do
|
|
85
|
+
Async::HTTP::Internet.get("https://httpbin.org/json") do |response|
|
|
86
|
+
if response.success?
|
|
87
|
+
puts response.headers["content-type"]
|
|
88
|
+
puts response.read
|
|
89
|
+
else
|
|
90
|
+
warn "Request failed with status #{response.status}."
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
~~~
|
|
95
|
+
|
|
96
|
+
For larger responses, process the body incrementally rather than reading it into one string. See the [`protocol-http` message body documentation](https://socketry.github.io/protocol-http/guides/message-body/) for the complete body interface.
|
|
62
97
|
|
|
63
98
|
### Downloading a File
|
|
64
99
|
|
|
100
|
+
Use `response.save` to stream a response directly to a file:
|
|
101
|
+
|
|
65
102
|
~~~ ruby
|
|
66
|
-
require
|
|
103
|
+
require "async/http/internet/instance"
|
|
67
104
|
|
|
68
105
|
Sync do
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
ensure
|
|
75
|
-
response&.close
|
|
106
|
+
Async::HTTP::Internet.get("https://example.com/archive.zip") do |response|
|
|
107
|
+
raise "Download failed with status #{response.status}." unless response.success?
|
|
108
|
+
|
|
109
|
+
response.save("archive.zip")
|
|
110
|
+
end
|
|
76
111
|
end
|
|
77
112
|
~~~
|
|
78
113
|
|
|
79
|
-
|
|
114
|
+
## Posting JSON
|
|
80
115
|
|
|
81
|
-
|
|
116
|
+
Pass headers and a body after the request target. The body may be a string or a compatible `protocol-http` body object.
|
|
82
117
|
|
|
83
118
|
~~~ ruby
|
|
84
|
-
require
|
|
119
|
+
require "async/http/internet/instance"
|
|
120
|
+
require "json"
|
|
85
121
|
|
|
86
|
-
data = {
|
|
122
|
+
data = {life: 42}
|
|
123
|
+
headers = [
|
|
124
|
+
["accept", "application/json"],
|
|
125
|
+
["content-type", "application/json"],
|
|
126
|
+
]
|
|
87
127
|
|
|
88
128
|
Sync do
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
response = Async::HTTP::Internet.post("https://httpbin.org/anything", headers, body)
|
|
95
|
-
|
|
96
|
-
# Save the response body to a local file:
|
|
97
|
-
pp JSON.parse(response.read)
|
|
98
|
-
ensure
|
|
99
|
-
response&.close
|
|
129
|
+
Async::HTTP::Internet.post("https://httpbin.org/anything", headers, JSON.dump(data)) do |response|
|
|
130
|
+
raise "Request failed with status #{response.status}." unless response.success?
|
|
131
|
+
|
|
132
|
+
puts JSON.pretty_generate(JSON.parse(response.read))
|
|
133
|
+
end
|
|
100
134
|
end
|
|
101
135
|
~~~
|
|
102
136
|
|
|
103
|
-
For
|
|
137
|
+
For resource-oriented HTTP APIs, consider using [`async-rest`](https://github.com/socketry/async-rest), which builds on `Async::HTTP`.
|
|
104
138
|
|
|
105
|
-
|
|
139
|
+
## Applying a Timeout
|
|
106
140
|
|
|
107
|
-
|
|
141
|
+
Networks can stall indefinitely, so impose a timeout around operations that must complete within a fixed duration:
|
|
108
142
|
|
|
109
143
|
~~~ ruby
|
|
110
|
-
require
|
|
144
|
+
require "async/http/internet/instance"
|
|
111
145
|
|
|
112
146
|
Sync do |task|
|
|
113
|
-
# Request will timeout after 2 seconds
|
|
114
147
|
task.with_timeout(2) do
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
148
|
+
Async::HTTP::Internet.get("https://httpbin.org/delay/10") do |response|
|
|
149
|
+
puts response.read
|
|
150
|
+
end
|
|
118
151
|
end
|
|
119
152
|
rescue Async::TimeoutError
|
|
120
|
-
|
|
153
|
+
warn "The request timed out."
|
|
121
154
|
end
|
|
122
155
|
~~~
|
|
123
156
|
|
|
124
|
-
|
|
157
|
+
The response block still closes the response if the timeout interrupts the request while its body is being processed.
|
|
158
|
+
|
|
159
|
+
## Making a Server
|
|
125
160
|
|
|
126
|
-
|
|
161
|
+
ruby:`Async::HTTP::Server` accepts an application that maps each request to a ruby:`Protocol::HTTP::Response`. The following example starts a local server, makes one request, and then releases both client and server resources:
|
|
127
162
|
|
|
128
163
|
~~~ ruby
|
|
129
|
-
require
|
|
164
|
+
require "async/http"
|
|
130
165
|
|
|
131
|
-
endpoint = Async::HTTP::Endpoint.parse(
|
|
166
|
+
endpoint = Async::HTTP::Endpoint.parse("http://localhost:9292")
|
|
167
|
+
server = Async::HTTP::Server.for(endpoint) do |request|
|
|
168
|
+
Protocol::HTTP::Response[200, {"content-type" => "text/plain"}, ["Hello World"]]
|
|
169
|
+
end
|
|
132
170
|
|
|
133
|
-
Sync do
|
|
134
|
-
|
|
135
|
-
server = Async::HTTP::Server.for(endpoint) do |request|
|
|
136
|
-
::Protocol::HTTP::Response[200, {}, ["Hello World"]]
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
server.run
|
|
140
|
-
end
|
|
171
|
+
Sync do
|
|
172
|
+
server_task = server.run
|
|
141
173
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
174
|
+
Async::HTTP::Client.open(endpoint) do |client|
|
|
175
|
+
response = client.get("/")
|
|
176
|
+
puts response.read
|
|
177
|
+
ensure
|
|
178
|
+
response&.close
|
|
179
|
+
end
|
|
145
180
|
ensure
|
|
146
|
-
|
|
181
|
+
server_task&.stop
|
|
147
182
|
end
|
|
148
183
|
~~~
|
|
184
|
+
|
|
185
|
+
Use Falcon when you need to host a Rack application or deploy an HTTP server in production. Use `Async::HTTP::Server` directly when building a protocol-level server or embedding HTTP handling into another asynchronous application.
|
data/context/index.yaml
CHANGED
|
@@ -3,13 +3,24 @@
|
|
|
3
3
|
---
|
|
4
4
|
description: A HTTP client and server library.
|
|
5
5
|
metadata:
|
|
6
|
+
bug_tracker_uri: https://github.com/socketry/async-http/issues
|
|
7
|
+
changelog_uri: https://github.com/socketry/async-http/blob/main/releases.md
|
|
6
8
|
documentation_uri: https://socketry.github.io/async-http/
|
|
7
9
|
source_code_uri: https://github.com/socketry/async-http.git
|
|
8
10
|
files:
|
|
9
11
|
- path: getting-started.md
|
|
10
12
|
title: Getting Started
|
|
11
|
-
description: This guide explains how to
|
|
13
|
+
description: This guide explains how to make HTTP requests and serve HTTP responses
|
|
14
|
+
with `Async::HTTP`.
|
|
15
|
+
- path: choosing-a-client.md
|
|
16
|
+
title: Choosing a Client
|
|
17
|
+
description: This guide explains how to choose between ruby:`Async::HTTP::Internet`,
|
|
18
|
+
ruby:`Async::HTTP::Client`, and higher-level interfaces for libraries.
|
|
19
|
+
- path: concurrent-requests.md
|
|
20
|
+
title: Concurrent Requests and Connection Pooling
|
|
21
|
+
description: This guide explains how to run HTTP requests concurrently while keeping
|
|
22
|
+
request fan-out, connection usage, and resource life cycles bounded.
|
|
12
23
|
- path: testing.md
|
|
13
24
|
title: Testing
|
|
14
|
-
description: This guide explains how to
|
|
15
|
-
|
|
25
|
+
description: This guide explains how to test `Async::HTTP` clients and servers without
|
|
26
|
+
depending on external HTTP services.
|