async-http 0.90.2 → 0.92.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/getting-started.md +148 -0
- data/context/index.yaml +15 -0
- data/context/testing.md +77 -0
- data/lib/async/http/body/pipe.rb +1 -1
- data/lib/async/http/client.rb +0 -52
- data/lib/async/http/endpoint.rb +1 -3
- data/lib/async/http/middleware/location_redirector.rb +3 -4
- data/lib/async/http/protocol/http1/client.rb +0 -16
- data/lib/async/http/protocol/http1/server.rb +0 -2
- data/lib/async/http/protocol/http2/client.rb +0 -15
- data/lib/async/http/server.rb +0 -38
- data/lib/async/http/version.rb +1 -1
- data/lib/metrics/provider/async/http/client.rb +1 -1
- data/lib/metrics/provider/async/http/server.rb +1 -1
- data/lib/metrics/provider/async/http.rb +3 -0
- data/lib/traces/provider/async/http/client.rb +56 -0
- data/lib/traces/provider/async/http/protocol/http1/client.rb +20 -0
- data/lib/traces/provider/async/http/protocol/http2/client.rb +20 -0
- data/lib/traces/provider/async/http/server.rb +43 -0
- data/lib/traces/provider/async/http.rb +7 -0
- data/license.md +2 -0
- data/readme.md +8 -10
- data/releases.md +9 -1
- data.tar.gz.sig +0 -0
- metadata +26 -3
- metadata.gz.sig +0 -0
- data/lib/async/http/reference.rb +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 913107bd5ebd3ac637bcaea82f20b46069f0a5bf15f128abb1c3ec7e93c23387
|
|
4
|
+
data.tar.gz: c4b20ec61acafd0572e744aadac9b3878f2052a8c5057aabfafbf983a15b79e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f51b80df07971626284803c186ff44dc8367422bd0833952b176d30418f1c0d43aea66deebc5d1d5d444da833ac655ba230c7ac4c8c98797c233c8a161228519
|
|
7
|
+
data.tar.gz: 59ba90830afaa9da4f72c4719abdae629a26cfe9fd218714f88f02bec6d2654a81b2935c75baf2d782b8470726afc6b5b5bd15a51cfd4e70d677bf661413800b
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
This guide explains how to get started with `Async::HTTP`.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add the gem to your project:
|
|
8
|
+
|
|
9
|
+
~~~ bash
|
|
10
|
+
$ bundle add async-http
|
|
11
|
+
~~~
|
|
12
|
+
|
|
13
|
+
## Core Concepts
|
|
14
|
+
|
|
15
|
+
- {ruby Async::HTTP::Client} is the main class for making HTTP requests.
|
|
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.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Making a Request
|
|
24
|
+
|
|
25
|
+
To make a request, use {ruby Async::HTTP::Internet} and call the appropriate method:
|
|
26
|
+
|
|
27
|
+
~~~ ruby
|
|
28
|
+
require 'async/http/internet/instance'
|
|
29
|
+
|
|
30
|
+
Sync do
|
|
31
|
+
Async::HTTP::Internet.get("https://httpbin.org/get") do |response|
|
|
32
|
+
puts response.read
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
~~~
|
|
36
|
+
|
|
37
|
+
The following methods are supported:
|
|
38
|
+
|
|
39
|
+
~~~ ruby
|
|
40
|
+
Async::HTTP::Internet.methods(false)
|
|
41
|
+
# => [:patch, :options, :connect, :post, :get, :delete, :head, :trace, :put]
|
|
42
|
+
~~~
|
|
43
|
+
|
|
44
|
+
Using a block will automatically close the response when the block completes. If you want to keep the response open, you can manage it manually:
|
|
45
|
+
|
|
46
|
+
~~~ ruby
|
|
47
|
+
require 'async/http/internet/instance'
|
|
48
|
+
|
|
49
|
+
Sync do
|
|
50
|
+
response = Async::HTTP::Internet.get("https://httpbin.org/get")
|
|
51
|
+
puts response.read
|
|
52
|
+
ensure
|
|
53
|
+
response&.close
|
|
54
|
+
end
|
|
55
|
+
~~~
|
|
56
|
+
|
|
57
|
+
As responses are streamed, you must ensure it is closed when you are finished with it.
|
|
58
|
+
|
|
59
|
+
#### Persistence
|
|
60
|
+
|
|
61
|
+
By default, {ruby Async::HTTP::Internet} will create a {ruby Async::HTTP::Client} for each remote host you communicate with, and will keep those connections open for as long as possible. This is useful for reducing the latency of subsequent requests to the same host. When you exit the event loop, the connections will be closed automatically.
|
|
62
|
+
|
|
63
|
+
### Downloading a File
|
|
64
|
+
|
|
65
|
+
~~~ ruby
|
|
66
|
+
require 'async/http/internet/instance'
|
|
67
|
+
|
|
68
|
+
Sync do
|
|
69
|
+
# Issue a GET request to Google:
|
|
70
|
+
response = Async::HTTP::Internet.get("https://www.google.com/search?q=kittens")
|
|
71
|
+
|
|
72
|
+
# Save the response body to a local file:
|
|
73
|
+
response.save("/tmp/search.html")
|
|
74
|
+
ensure
|
|
75
|
+
response&.close
|
|
76
|
+
end
|
|
77
|
+
~~~
|
|
78
|
+
|
|
79
|
+
### Posting Data
|
|
80
|
+
|
|
81
|
+
To post data, use the `post` method:
|
|
82
|
+
|
|
83
|
+
~~~ ruby
|
|
84
|
+
require 'async/http/internet/instance'
|
|
85
|
+
|
|
86
|
+
data = {'life' => 42}
|
|
87
|
+
|
|
88
|
+
Sync do
|
|
89
|
+
# Prepare the request:
|
|
90
|
+
headers = [['accept', 'application/json']]
|
|
91
|
+
body = JSON.dump(data)
|
|
92
|
+
|
|
93
|
+
# Issues a POST request:
|
|
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
|
|
100
|
+
end
|
|
101
|
+
~~~
|
|
102
|
+
|
|
103
|
+
For more complex scenarios, including HTTP APIs, consider using [async-rest](https://github.com/socketry/async-rest) instead.
|
|
104
|
+
|
|
105
|
+
### Timeouts
|
|
106
|
+
|
|
107
|
+
To set a timeout for a request, use the `Task#with_timeout` method:
|
|
108
|
+
|
|
109
|
+
~~~ ruby
|
|
110
|
+
require 'async/http/internet/instance'
|
|
111
|
+
|
|
112
|
+
Sync do |task|
|
|
113
|
+
# Request will timeout after 2 seconds
|
|
114
|
+
task.with_timeout(2) do
|
|
115
|
+
response = Async::HTTP::Internet.get "https://httpbin.org/delay/10"
|
|
116
|
+
ensure
|
|
117
|
+
response&.close
|
|
118
|
+
end
|
|
119
|
+
rescue Async::TimeoutError
|
|
120
|
+
puts "The request timed out"
|
|
121
|
+
end
|
|
122
|
+
~~~
|
|
123
|
+
|
|
124
|
+
### Making a Server
|
|
125
|
+
|
|
126
|
+
To create a server, use an instance of {ruby Async::HTTP::Server}:
|
|
127
|
+
|
|
128
|
+
~~~ ruby
|
|
129
|
+
require 'async/http'
|
|
130
|
+
|
|
131
|
+
endpoint = Async::HTTP::Endpoint.parse('http://localhost:9292')
|
|
132
|
+
|
|
133
|
+
Sync do |task|
|
|
134
|
+
Async(transient: true) do
|
|
135
|
+
server = Async::HTTP::Server.for(endpoint) do |request|
|
|
136
|
+
::Protocol::HTTP::Response[200, {}, ["Hello World"]]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
server.run
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
client = Async::HTTP::Client.new(endpoint)
|
|
143
|
+
response = client.get("/")
|
|
144
|
+
puts response.read
|
|
145
|
+
ensure
|
|
146
|
+
response&.close
|
|
147
|
+
end
|
|
148
|
+
~~~
|
data/context/index.yaml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Automatically generated context index for Utopia::Project guides.
|
|
2
|
+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
|
3
|
+
---
|
|
4
|
+
description: A HTTP client and server library.
|
|
5
|
+
metadata:
|
|
6
|
+
documentation_uri: https://socketry.github.io/async-http/
|
|
7
|
+
source_code_uri: https://github.com/socketry/async-http.git
|
|
8
|
+
files:
|
|
9
|
+
- path: getting-started.md
|
|
10
|
+
title: Getting Started
|
|
11
|
+
description: This guide explains how to get started with `Async::HTTP`.
|
|
12
|
+
- path: testing.md
|
|
13
|
+
title: Testing
|
|
14
|
+
description: This guide explains how to use `Async::HTTP` clients and servers in
|
|
15
|
+
your tests.
|
data/context/testing.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
This guide explains how to use `Async::HTTP` clients and servers in your tests.
|
|
4
|
+
|
|
5
|
+
In general, you should avoid making real HTTP requests in your tests. Instead, you should use a mock server or a fake client.
|
|
6
|
+
|
|
7
|
+
## Mocking HTTP Responses
|
|
8
|
+
|
|
9
|
+
The mocking feature of `Async::HTTP` uses a real server running in a separate task, and routes all requests to it. This allows you to intercept requests and return custom responses, but still use the real HTTP client.
|
|
10
|
+
|
|
11
|
+
In order to enable this feature, you must create an instance of {ruby Async::HTTP::Mock::Endpoint} which will handle the requests.
|
|
12
|
+
|
|
13
|
+
~~~ ruby
|
|
14
|
+
require 'async/http'
|
|
15
|
+
require 'async/http/mock'
|
|
16
|
+
|
|
17
|
+
mock_endpoint = Async::HTTP::Mock::Endpoint.new
|
|
18
|
+
|
|
19
|
+
Sync do
|
|
20
|
+
# Start a background server:
|
|
21
|
+
server_task = Async(transient: true) do
|
|
22
|
+
mock_endpoint.run do |request|
|
|
23
|
+
# Respond to the request:
|
|
24
|
+
::Protocol::HTTP::Response[200, {}, ["Hello, World"]]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
endpoint = Async::HTTP::Endpoint.parse("https://www.google.com")
|
|
29
|
+
mocked_endpoint = mock_endpoint.wrap(endpoint)
|
|
30
|
+
client = Async::HTTP::Client.new(mocked_endpoint)
|
|
31
|
+
|
|
32
|
+
response = client.get("/")
|
|
33
|
+
puts response.read
|
|
34
|
+
# => "Hello, World"
|
|
35
|
+
end
|
|
36
|
+
~~~
|
|
37
|
+
|
|
38
|
+
## Transparent Mocking
|
|
39
|
+
|
|
40
|
+
Using your test framework's mocking capabilities, you can easily replace the `Async::HTTP::Client#new` with a method that returns a client with a mocked endpoint.
|
|
41
|
+
|
|
42
|
+
### Sus Integration
|
|
43
|
+
|
|
44
|
+
~~~ ruby
|
|
45
|
+
require 'async/http'
|
|
46
|
+
require 'async/http/mock'
|
|
47
|
+
require 'sus/fixtures/async/reactor_context'
|
|
48
|
+
|
|
49
|
+
include Sus::Fixtures::Async::ReactorContext
|
|
50
|
+
|
|
51
|
+
let(:mock_endpoint) {Async::HTTP::Mock::Endpoint.new}
|
|
52
|
+
|
|
53
|
+
def before
|
|
54
|
+
super
|
|
55
|
+
|
|
56
|
+
# Mock the HTTP client:
|
|
57
|
+
mock(Async::HTTP::Client) do |mock|
|
|
58
|
+
mock.wrap(:new) do |original, endpoint|
|
|
59
|
+
original.call(mock_endpoint.wrap(endpoint))
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Run the mock server:
|
|
64
|
+
Async(transient: true) do
|
|
65
|
+
mock_endpoint.run do |request|
|
|
66
|
+
::Protocol::HTTP::Response[200, {}, ["Hello, World"]]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should perform a web request" do
|
|
72
|
+
client = Async::HTTP::Client.new(Async::HTTP::Endpoint.parse("https://www.google.com"))
|
|
73
|
+
response = client.get("/")
|
|
74
|
+
# The response is mocked:
|
|
75
|
+
expect(response.read).to be == "Hello, World"
|
|
76
|
+
end
|
|
77
|
+
~~~
|
data/lib/async/http/body/pipe.rb
CHANGED
data/lib/async/http/client.rb
CHANGED
|
@@ -11,8 +11,6 @@ require "async/pool/controller"
|
|
|
11
11
|
require "protocol/http/body/completable"
|
|
12
12
|
require "protocol/http/methods"
|
|
13
13
|
|
|
14
|
-
require "traces/provider"
|
|
15
|
-
|
|
16
14
|
require_relative "protocol"
|
|
17
15
|
|
|
18
16
|
module Async
|
|
@@ -168,56 +166,6 @@ module Async
|
|
|
168
166
|
@protocol.client(@endpoint.connect)
|
|
169
167
|
end
|
|
170
168
|
end
|
|
171
|
-
|
|
172
|
-
Traces::Provider(self) do
|
|
173
|
-
def call(request)
|
|
174
|
-
attributes = {
|
|
175
|
-
'http.method': request.method,
|
|
176
|
-
'http.authority': request.authority || self.authority,
|
|
177
|
-
'http.scheme': request.scheme || self.scheme,
|
|
178
|
-
'http.path': request.path,
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if protocol = request.protocol
|
|
182
|
-
attributes["http.protocol"] = protocol
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
if length = request.body&.length
|
|
186
|
-
attributes["http.request.length"] = length
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
Traces.trace("async.http.client.call", attributes: attributes) do |span|
|
|
190
|
-
if context = Traces.trace_context
|
|
191
|
-
request.headers["traceparent"] = context.to_s
|
|
192
|
-
# request.headers['tracestate'] = context.state
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
super.tap do |response|
|
|
196
|
-
if version = response&.version
|
|
197
|
-
span["http.version"] = version
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
if status = response&.status
|
|
201
|
-
span["http.status_code"] = status
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
if length = response.body&.length
|
|
205
|
-
span["http.response.length"] = length
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def make_response(request, connection, attempt)
|
|
212
|
-
attributes = {
|
|
213
|
-
attempt: attempt,
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
Traces.trace("async.http.client.make_response", attributes: attributes) do
|
|
217
|
-
super
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
169
|
end
|
|
222
170
|
end
|
|
223
171
|
end
|
data/lib/async/http/endpoint.rb
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# Copyright, 2024, by Thomas Morgan.
|
|
7
7
|
# Copyright, 2024, by Igor Sidorov.
|
|
8
8
|
# Copyright, 2024, by Hal Brodigan.
|
|
9
|
+
# Copyright, 2025, by William T. Nelson.
|
|
9
10
|
|
|
10
11
|
require "io/endpoint"
|
|
11
12
|
require "io/endpoint/host_endpoint"
|
|
@@ -16,9 +17,6 @@ require_relative "protocol/https"
|
|
|
16
17
|
|
|
17
18
|
require "uri"
|
|
18
19
|
|
|
19
|
-
# Compatibility with Ruby 3.1.2
|
|
20
|
-
require "uri/wss"
|
|
21
|
-
|
|
22
20
|
module Async
|
|
23
21
|
module HTTP
|
|
24
22
|
# Represents a way to connect to a remote HTTP server.
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2024, by Samuel Williams.
|
|
5
|
-
|
|
6
|
-
require_relative "../reference"
|
|
4
|
+
# Copyright, 2024-2025, by Samuel Williams.
|
|
7
5
|
|
|
8
6
|
require "protocol/http/middleware"
|
|
9
7
|
require "protocol/http/body/rewindable"
|
|
8
|
+
require "protocol/url/reference"
|
|
10
9
|
|
|
11
10
|
module Async
|
|
12
11
|
module HTTP
|
|
@@ -71,7 +70,7 @@ module Async
|
|
|
71
70
|
end
|
|
72
71
|
|
|
73
72
|
# Update the path of the request:
|
|
74
|
-
request.path = Reference[request.path] + location
|
|
73
|
+
request.path = ::Protocol::URL::Reference[request.path] + location
|
|
75
74
|
|
|
76
75
|
# Follow the redirect:
|
|
77
76
|
return true
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
require_relative "connection"
|
|
7
7
|
|
|
8
|
-
require "traces/provider"
|
|
9
|
-
|
|
10
8
|
module Async
|
|
11
9
|
module HTTP
|
|
12
10
|
module Protocol
|
|
@@ -88,20 +86,6 @@ module Async
|
|
|
88
86
|
self.close(error)
|
|
89
87
|
raise
|
|
90
88
|
end
|
|
91
|
-
|
|
92
|
-
Traces::Provider(self) do
|
|
93
|
-
def write_request(...)
|
|
94
|
-
Traces.trace("async.http.protocol.http1.client.write_request") do
|
|
95
|
-
super
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def read_response(...)
|
|
100
|
-
Traces.trace("async.http.protocol.http1.client.read_response") do
|
|
101
|
-
super
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
89
|
end
|
|
106
90
|
end
|
|
107
91
|
end
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
require_relative "connection"
|
|
7
7
|
require_relative "response"
|
|
8
8
|
|
|
9
|
-
require "traces/provider"
|
|
10
9
|
require "protocol/http2/client"
|
|
11
10
|
|
|
12
11
|
module Async
|
|
@@ -46,20 +45,6 @@ module Async
|
|
|
46
45
|
def read_response(response)
|
|
47
46
|
response.wait
|
|
48
47
|
end
|
|
49
|
-
|
|
50
|
-
Traces::Provider(self) do
|
|
51
|
-
def write_request(...)
|
|
52
|
-
Traces.trace("async.http.protocol.http2.client.write_request") do
|
|
53
|
-
super
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def read_response(...)
|
|
58
|
-
Traces.trace("async.http.protocol.http2.client.read_response") do
|
|
59
|
-
super
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
48
|
end
|
|
64
49
|
end
|
|
65
50
|
end
|
data/lib/async/http/server.rb
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
require "async"
|
|
8
8
|
require "io/endpoint"
|
|
9
9
|
require "protocol/http/middleware"
|
|
10
|
-
require "traces/provider"
|
|
11
10
|
|
|
12
11
|
require_relative "protocol"
|
|
13
12
|
|
|
@@ -70,43 +69,6 @@ module Async
|
|
|
70
69
|
task.children.each(&:wait)
|
|
71
70
|
end
|
|
72
71
|
end
|
|
73
|
-
|
|
74
|
-
Traces::Provider(self) do
|
|
75
|
-
def call(request)
|
|
76
|
-
if trace_parent = request.headers["traceparent"]
|
|
77
|
-
Traces.trace_context = Traces::Context.parse(trace_parent.join, request.headers["tracestate"], remote: true)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
attributes = {
|
|
81
|
-
'http.version': request.version,
|
|
82
|
-
'http.method': request.method,
|
|
83
|
-
'http.authority': request.authority,
|
|
84
|
-
'http.scheme': request.scheme,
|
|
85
|
-
'http.path': request.path,
|
|
86
|
-
'http.user_agent': request.headers["user-agent"],
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if length = request.body&.length
|
|
90
|
-
attributes["http.request.length"] = length
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
if protocol = request.protocol
|
|
94
|
-
attributes["http.protocol"] = protocol
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
Traces.trace("async.http.server.call", attributes: attributes) do |span|
|
|
98
|
-
super.tap do |response|
|
|
99
|
-
if status = response&.status
|
|
100
|
-
span["http.status_code"] = status
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
if length = response&.body&.length
|
|
104
|
-
span["http.response.length"] = length
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
72
|
end
|
|
111
73
|
end
|
|
112
74
|
end
|
data/lib/async/http/version.rb
CHANGED
|
@@ -9,7 +9,7 @@ require "metrics/provider"
|
|
|
9
9
|
Metrics::Provider(Async::HTTP::Client) do
|
|
10
10
|
ASYNC_HTTP_CLIENT_REQUEST_INITIATED = Metrics.metric("async.http.client.request.initiated", :counter, description: "The number of HTTP client requests initiated.")
|
|
11
11
|
ASYNC_HTTP_CLIENT_REQUEST_FINISHED = Metrics.metric("async.http.client.request.finished", :counter, description: "The number of HTTP client requests finished.")
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
def make_response(request, connection, attempt)
|
|
14
14
|
ASYNC_HTTP_CLIENT_REQUEST_INITIATED.emit(1, tags: ["method:#{request.method}"])
|
|
15
15
|
|
|
@@ -9,7 +9,7 @@ require "metrics/provider"
|
|
|
9
9
|
Metrics::Provider(Async::HTTP::Server) do
|
|
10
10
|
ASYNC_HTTP_SERVER_REQUEST_INITIATED = Metrics.metric("async.http.server.request.initiated", :counter, description: "The number of HTTP server requests initiated.")
|
|
11
11
|
ASYNC_HTTP_SERVER_REQUEST_FINISHED = Metrics.metric("async.http.server.request.finished", :counter, description: "The number of HTTP server requests finished.")
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
def call(request)
|
|
14
14
|
ASYNC_HTTP_SERVER_REQUEST_INITIATED.emit(1, tags: ["method:#{request.method}"])
|
|
15
15
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "../../../../async/http/client"
|
|
7
|
+
|
|
8
|
+
Traces::Provider(Async::HTTP::Client) do
|
|
9
|
+
def call(request)
|
|
10
|
+
attributes = {
|
|
11
|
+
'http.method': request.method,
|
|
12
|
+
'http.authority': request.authority || self.authority,
|
|
13
|
+
'http.scheme': request.scheme || self.scheme,
|
|
14
|
+
'http.path': request.path,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if protocol = request.protocol
|
|
18
|
+
attributes["http.protocol"] = protocol
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if length = request.body&.length
|
|
22
|
+
attributes["http.request.length"] = length
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Traces.trace("async.http.client.call", attributes: attributes) do |span|
|
|
26
|
+
if context = Traces.trace_context
|
|
27
|
+
request.headers["traceparent"] = context.to_s
|
|
28
|
+
# request.headers['tracestate'] = context.state
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
super.tap do |response|
|
|
32
|
+
if version = response&.version
|
|
33
|
+
span["http.version"] = version
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if status = response&.status
|
|
37
|
+
span["http.status_code"] = status
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if length = response.body&.length
|
|
41
|
+
span["http.response.length"] = length
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def make_response(request, connection, attempt)
|
|
48
|
+
attributes = {
|
|
49
|
+
attempt: attempt,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Traces.trace("async.http.client.make_response", attributes: attributes) do
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "../../../../../../async/http/protocol/http1/client"
|
|
7
|
+
|
|
8
|
+
Traces::Provider(Async::HTTP::Protocol::HTTP1::Client) do
|
|
9
|
+
def write_request(...)
|
|
10
|
+
Traces.trace("async.http.protocol.http1.client.write_request") do
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def read_response(...)
|
|
16
|
+
Traces.trace("async.http.protocol.http1.client.read_response") do
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "../../../../../../async/http/protocol/http2/client"
|
|
7
|
+
|
|
8
|
+
Traces::Provider(Async::HTTP::Protocol::HTTP2::Client) do
|
|
9
|
+
def write_request(...)
|
|
10
|
+
Traces.trace("async.http.protocol.http2.client.write_request") do
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def read_response(...)
|
|
16
|
+
Traces.trace("async.http.protocol.http2.client.read_response") do
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "../../../../async/http/server"
|
|
7
|
+
|
|
8
|
+
Traces::Provider(Async::HTTP::Server) do
|
|
9
|
+
def call(request)
|
|
10
|
+
if trace_parent = request.headers["traceparent"]
|
|
11
|
+
Traces.trace_context = Traces::Context.parse(trace_parent.join, request.headers["tracestate"], remote: true)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attributes = {
|
|
15
|
+
'http.version': request.version,
|
|
16
|
+
'http.method': request.method,
|
|
17
|
+
'http.authority': request.authority,
|
|
18
|
+
'http.scheme': request.scheme,
|
|
19
|
+
'http.path': request.path,
|
|
20
|
+
'http.user_agent': request.headers["user-agent"],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if length = request.body&.length
|
|
24
|
+
attributes["http.request.length"] = length
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if protocol = request.protocol
|
|
28
|
+
attributes["http.protocol"] = protocol
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Traces.trace("async.http.server.call", attributes: attributes) do |span|
|
|
32
|
+
super.tap do |response|
|
|
33
|
+
if status = response&.status
|
|
34
|
+
span["http.status_code"] = status
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if length = response&.body&.length
|
|
38
|
+
span["http.response.length"] = length
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/license.md
CHANGED
|
@@ -23,6 +23,8 @@ Copyright, 2023, by Josh Huber.
|
|
|
23
23
|
Copyright, 2024, by Anton Zhuravsky.
|
|
24
24
|
Copyright, 2024, by Hal Brodigan.
|
|
25
25
|
Copyright, 2025, by Jean Boussier.
|
|
26
|
+
Copyright, 2025, by Yuuji Yaginuma.
|
|
27
|
+
Copyright, 2025, by William T. Nelson.
|
|
26
28
|
|
|
27
29
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
30
|
of this software and associated documentation files (the "Software"), to deal
|
data/readme.md
CHANGED
|
@@ -16,6 +16,14 @@ Please see the [project documentation](https://socketry.github.io/async-http/) f
|
|
|
16
16
|
|
|
17
17
|
Please see the [project releases](https://socketry.github.io/async-http/releases/index) for all releases.
|
|
18
18
|
|
|
19
|
+
### v0.92.0
|
|
20
|
+
|
|
21
|
+
- **Breaking**: Remove `Async::HTTP::Reference`. Use `Protocol::URL::Reference` directly instead.
|
|
22
|
+
|
|
23
|
+
### v0.91.0
|
|
24
|
+
|
|
25
|
+
- Move all default trace providers into `traces/provider/async/http`.
|
|
26
|
+
|
|
19
27
|
### v0.90.2
|
|
20
28
|
|
|
21
29
|
- Don't emit `resource:` keyword argument in traces - it's unsupported by OpenTelemetry.
|
|
@@ -49,16 +57,6 @@ Please see the [project releases](https://socketry.github.io/async-http/releases
|
|
|
49
57
|
- Improved HTTP/1 connection handling.
|
|
50
58
|
- The input stream is no longer closed when the output stream is closed.
|
|
51
59
|
|
|
52
|
-
### v0.76.0
|
|
53
|
-
|
|
54
|
-
- `Async::HTTP::Body::Writable` is moved to `Protocol::HTTP::Body::Writable`.
|
|
55
|
-
- Remove `Async::HTTP::Body::Delayed` with no replacement.
|
|
56
|
-
- Remove `Async::HTTP::Body::Slowloris` with no replacement.
|
|
57
|
-
|
|
58
|
-
### v0.75.0
|
|
59
|
-
|
|
60
|
-
- Better handling of HTTP/1 \<-\> HTTP/2 proxying, specifically upgrade/CONNECT requests.
|
|
61
|
-
|
|
62
60
|
## See Also
|
|
63
61
|
|
|
64
62
|
- [benchmark-http](https://github.com/socketry/benchmark-http) — A benchmarking tool to report on web server concurrency.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.92.0
|
|
4
|
+
|
|
5
|
+
- **Breaking**: Remove `Async::HTTP::Reference`. Use `Protocol::URL::Reference` directly instead.
|
|
6
|
+
|
|
7
|
+
## v0.91.0
|
|
8
|
+
|
|
9
|
+
- Move all default trace providers into `traces/provider/async/http`.
|
|
10
|
+
|
|
3
11
|
## v0.90.2
|
|
4
12
|
|
|
5
13
|
- Don't emit `resource:` keyword argument in traces - it's unsupported by OpenTelemetry.
|
|
@@ -141,7 +149,7 @@ internet.get("https://example.com", scheme: "http")
|
|
|
141
149
|
``` ruby
|
|
142
150
|
# Server side:
|
|
143
151
|
def call(request)
|
|
144
|
-
if request.headers[
|
|
152
|
+
if request.headers["expect"].include?("100-continue")
|
|
145
153
|
request.send_interim_response(100)
|
|
146
154
|
end
|
|
147
155
|
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: async-http
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.92.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -11,6 +11,7 @@ authors:
|
|
|
11
11
|
- Thomas Morgan
|
|
12
12
|
- Adam Daniels
|
|
13
13
|
- Igor Sidorov
|
|
14
|
+
- William T. Nelson
|
|
14
15
|
- Anton Zhuravsky
|
|
15
16
|
- Cyril Roelandt
|
|
16
17
|
- Denis Talakevich
|
|
@@ -26,6 +27,7 @@ authors:
|
|
|
26
27
|
- Tim Meusel
|
|
27
28
|
- Trevor Turk
|
|
28
29
|
- Viacheslav Koval
|
|
30
|
+
- Yuuji Yaginuma
|
|
29
31
|
- dependabot[bot]
|
|
30
32
|
bindir: bin
|
|
31
33
|
cert_chain:
|
|
@@ -172,6 +174,20 @@ dependencies:
|
|
|
172
174
|
- - "~>"
|
|
173
175
|
- !ruby/object:Gem::Version
|
|
174
176
|
version: '0.22'
|
|
177
|
+
- !ruby/object:Gem::Dependency
|
|
178
|
+
name: protocol-url
|
|
179
|
+
requirement: !ruby/object:Gem::Requirement
|
|
180
|
+
requirements:
|
|
181
|
+
- - "~>"
|
|
182
|
+
- !ruby/object:Gem::Version
|
|
183
|
+
version: '0.2'
|
|
184
|
+
type: :runtime
|
|
185
|
+
prerelease: false
|
|
186
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
187
|
+
requirements:
|
|
188
|
+
- - "~>"
|
|
189
|
+
- !ruby/object:Gem::Version
|
|
190
|
+
version: '0.2'
|
|
175
191
|
- !ruby/object:Gem::Dependency
|
|
176
192
|
name: traces
|
|
177
193
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -192,6 +208,9 @@ extra_rdoc_files: []
|
|
|
192
208
|
files:
|
|
193
209
|
- bake/async/http.rb
|
|
194
210
|
- bake/async/http/h2spec.rb
|
|
211
|
+
- context/getting-started.md
|
|
212
|
+
- context/index.yaml
|
|
213
|
+
- context/testing.md
|
|
195
214
|
- lib/async/http.rb
|
|
196
215
|
- lib/async/http/body.rb
|
|
197
216
|
- lib/async/http/body/hijack.rb
|
|
@@ -230,7 +249,6 @@ files:
|
|
|
230
249
|
- lib/async/http/protocol/request.rb
|
|
231
250
|
- lib/async/http/protocol/response.rb
|
|
232
251
|
- lib/async/http/proxy.rb
|
|
233
|
-
- lib/async/http/reference.rb
|
|
234
252
|
- lib/async/http/relative_location.rb
|
|
235
253
|
- lib/async/http/server.rb
|
|
236
254
|
- lib/async/http/statistics.rb
|
|
@@ -238,6 +256,11 @@ files:
|
|
|
238
256
|
- lib/metrics/provider/async/http.rb
|
|
239
257
|
- lib/metrics/provider/async/http/client.rb
|
|
240
258
|
- lib/metrics/provider/async/http/server.rb
|
|
259
|
+
- lib/traces/provider/async/http.rb
|
|
260
|
+
- lib/traces/provider/async/http/client.rb
|
|
261
|
+
- lib/traces/provider/async/http/protocol/http1/client.rb
|
|
262
|
+
- lib/traces/provider/async/http/protocol/http2/client.rb
|
|
263
|
+
- lib/traces/provider/async/http/server.rb
|
|
241
264
|
- license.md
|
|
242
265
|
- readme.md
|
|
243
266
|
- releases.md
|
|
@@ -261,7 +284,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
261
284
|
- !ruby/object:Gem::Version
|
|
262
285
|
version: '0'
|
|
263
286
|
requirements: []
|
|
264
|
-
rubygems_version: 3.
|
|
287
|
+
rubygems_version: 3.7.2
|
|
265
288
|
specification_version: 4
|
|
266
289
|
summary: A HTTP client and server library.
|
|
267
290
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|
data/lib/async/http/reference.rb
DELETED