async-http 0.94.2 → 0.94.3
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/lib/async/http/body/hijack.rb +17 -0
- data/lib/async/http/body/pipe.rb +3 -0
- data/lib/async/http/body.rb +3 -0
- data/lib/async/http/client.rb +16 -4
- data/lib/async/http/endpoint.rb +33 -0
- data/lib/async/http/internet.rb +7 -0
- data/lib/async/http/middleware/location_redirector.rb +9 -0
- data/lib/async/http/mock/endpoint.rb +10 -0
- data/lib/async/http/protocol/configurable.rb +15 -0
- data/lib/async/http/protocol/http1/client.rb +3 -0
- data/lib/async/http/protocol/http1/connection.rb +13 -0
- data/lib/async/http/protocol/http1/finishable.rb +9 -0
- data/lib/async/http/protocol/http1/request.rb +22 -0
- data/lib/async/http/protocol/http1/response.rb +10 -0
- data/lib/async/http/protocol/http1/server.rb +7 -0
- data/lib/async/http/protocol/http1.rb +1 -0
- data/lib/async/http/protocol/http10.rb +1 -0
- data/lib/async/http/protocol/http11.rb +1 -0
- data/lib/async/http/protocol/http2/client.rb +10 -0
- data/lib/async/http/protocol/http2/connection.rb +16 -0
- data/lib/async/http/protocol/http2/input.rb +5 -0
- data/lib/async/http/protocol/http2/output.rb +13 -0
- data/lib/async/http/protocol/http2/request.rb +17 -0
- data/lib/async/http/protocol/http2/response.rb +23 -0
- data/lib/async/http/protocol/http2/server.rb +16 -0
- data/lib/async/http/protocol/http2/stream.rb +19 -0
- data/lib/async/http/protocol/http2.rb +1 -0
- data/lib/async/http/protocol/https.rb +7 -0
- data/lib/async/http/protocol/request.rb +9 -1
- data/lib/async/http/protocol/response.rb +6 -1
- data/lib/async/http/proxy.rb +12 -0
- data/lib/async/http/server.rb +14 -0
- data/lib/async/http/statistics.rb +19 -0
- data/lib/async/http/version.rb +3 -1
- data/lib/async/http.rb +0 -3
- data/readme.md +20 -4
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +3 -3
- 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: 391e059d6c94805257b5e058b02987ad88ce8decaafafb6e7815faeaeb8a5079
|
|
4
|
+
data.tar.gz: 2a260bb8dba2ffb109f612400d9e05c03012f994c275f6c447a09d915cbd186e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a882c3dcb76b5b601c7601a1875a8708ec301e510cb74f77e9a4060ac5ae9e199fec3c41b3bcdc7d1eb125d81aea8c8f50ba70d9e5fb7d3c832aa8ebbc97d518
|
|
7
|
+
data.tar.gz: ce9455361a23462bec86b7a1272665e411d130dd994df79e2d1c7fc0769a11f33ae3eb6628b9a5ced3b197bfd0bb5dd06a0e252cd0475fd013ef90eaee767682
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -13,14 +13,25 @@ module Async
|
|
|
13
13
|
module Body
|
|
14
14
|
# A body which is designed for hijacked server responses - a response which uses a block to read and write the request and response bodies respectively.
|
|
15
15
|
class Hijack < ::Protocol::HTTP::Body::Readable
|
|
16
|
+
# Create a response with this hijacked body.
|
|
17
|
+
# @parameter request [Protocol::HTTP::Request] The request to hijack.
|
|
18
|
+
# @parameter status [Integer] The HTTP status code.
|
|
19
|
+
# @parameter headers [Hash] The response headers.
|
|
20
|
+
# @returns [Protocol::HTTP::Response] The response with the hijacked body.
|
|
16
21
|
def self.response(request, status, headers, &block)
|
|
17
22
|
::Protocol::HTTP::Response[status, headers, self.wrap(request, &block)]
|
|
18
23
|
end
|
|
19
24
|
|
|
25
|
+
# Wrap a request body with a hijacked body.
|
|
26
|
+
# @parameter request [Protocol::HTTP::Request | Nil] The request to hijack.
|
|
27
|
+
# @returns [Hijack] The hijacked body instance.
|
|
20
28
|
def self.wrap(request = nil, &block)
|
|
21
29
|
self.new(block, request&.body)
|
|
22
30
|
end
|
|
23
31
|
|
|
32
|
+
# Initialize the hijacked body.
|
|
33
|
+
# @parameter block [Proc] The block to call with the stream.
|
|
34
|
+
# @parameter input [Protocol::HTTP::Body::Readable | Nil] The input body to read from.
|
|
24
35
|
def initialize(block, input = nil)
|
|
25
36
|
@block = block
|
|
26
37
|
@input = input
|
|
@@ -35,6 +46,8 @@ module Async
|
|
|
35
46
|
true
|
|
36
47
|
end
|
|
37
48
|
|
|
49
|
+
# Invoke the block with the given stream for bidirectional communication.
|
|
50
|
+
# @parameter stream [Protocol::HTTP::Body::Stream] The stream to pass to the block.
|
|
38
51
|
def call(stream)
|
|
39
52
|
@block.call(stream)
|
|
40
53
|
end
|
|
@@ -46,6 +59,8 @@ module Async
|
|
|
46
59
|
@output&.empty?
|
|
47
60
|
end
|
|
48
61
|
|
|
62
|
+
# Whether the body has output ready to be read.
|
|
63
|
+
# @returns [Boolean | Nil]
|
|
49
64
|
def ready?
|
|
50
65
|
@output&.ready?
|
|
51
66
|
end
|
|
@@ -66,10 +81,12 @@ module Async
|
|
|
66
81
|
return @output.read
|
|
67
82
|
end
|
|
68
83
|
|
|
84
|
+
# @returns [String] A detailed representation of this body.
|
|
69
85
|
def inspect
|
|
70
86
|
"\#<#{self.class} #{@block.inspect}>"
|
|
71
87
|
end
|
|
72
88
|
|
|
89
|
+
# @returns [String] A short summary of this body.
|
|
73
90
|
def to_s
|
|
74
91
|
"<Hijack #{@block.class}>"
|
|
75
92
|
end
|
data/lib/async/http/body/pipe.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative "writable"
|
|
|
9
9
|
module Async
|
|
10
10
|
module HTTP
|
|
11
11
|
module Body
|
|
12
|
+
# A bidirectional pipe that connects an input body to an output body using a Unix socket pair.
|
|
12
13
|
class Pipe
|
|
13
14
|
# If the input stream is closed first, it's likely the output stream will also be closed.
|
|
14
15
|
def initialize(input, output = Writable.new, task: Task.current)
|
|
@@ -27,10 +28,12 @@ module Async
|
|
|
27
28
|
task.async(transient: true, &self.method(:writer))
|
|
28
29
|
end
|
|
29
30
|
|
|
31
|
+
# @returns [IO] The underlying IO object for the tail of the pipe.
|
|
30
32
|
def to_io
|
|
31
33
|
@tail
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
# Close the pipe and stop the reader and writer tasks.
|
|
34
37
|
def close
|
|
35
38
|
@reader&.stop
|
|
36
39
|
@writer&.stop
|
data/lib/async/http/body.rb
CHANGED
data/lib/async/http/client.rb
CHANGED
|
@@ -17,6 +17,7 @@ module Async
|
|
|
17
17
|
module HTTP
|
|
18
18
|
DEFAULT_RETRIES = 3
|
|
19
19
|
|
|
20
|
+
# An HTTP client that manages persistent connections to a specific endpoint, with automatic retries for idempotent requests.
|
|
20
21
|
class Client < ::Protocol::HTTP::Methods
|
|
21
22
|
# Provides a robust interface to a server.
|
|
22
23
|
# * If there are no connections, it will create one.
|
|
@@ -38,16 +39,18 @@ module Async
|
|
|
38
39
|
@authority = authority
|
|
39
40
|
end
|
|
40
41
|
|
|
42
|
+
# @returns [Hash] A JSON-compatible representation of this client.
|
|
41
43
|
def as_json(...)
|
|
42
44
|
{
|
|
43
45
|
endpoint: @endpoint.to_s,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
protocol: @protocol,
|
|
47
|
+
retries: @retries,
|
|
48
|
+
scheme: @scheme,
|
|
49
|
+
authority: @authority,
|
|
48
50
|
}
|
|
49
51
|
end
|
|
50
52
|
|
|
53
|
+
# @returns [String] A JSON string representation of this client.
|
|
51
54
|
def to_json(...)
|
|
52
55
|
as_json.to_json(...)
|
|
53
56
|
end
|
|
@@ -61,10 +64,14 @@ module Async
|
|
|
61
64
|
attr :scheme
|
|
62
65
|
attr :authority
|
|
63
66
|
|
|
67
|
+
# @returns [Boolean] Whether the client uses a secure (TLS) connection.
|
|
64
68
|
def secure?
|
|
65
69
|
@endpoint.secure?
|
|
66
70
|
end
|
|
67
71
|
|
|
72
|
+
# Open a client and optionally yield it, ensuring it is closed afterwards.
|
|
73
|
+
# @parameter arguments [Array] Arguments to pass to {initialize}.
|
|
74
|
+
# @parameter options [Hash] Options to pass to {initialize}.
|
|
68
75
|
def self.open(*arguments, **options, &block)
|
|
69
76
|
client = self.new(*arguments, **options)
|
|
70
77
|
|
|
@@ -77,6 +84,7 @@ module Async
|
|
|
77
84
|
end
|
|
78
85
|
end
|
|
79
86
|
|
|
87
|
+
# Close the client and all associated connections.
|
|
80
88
|
def close
|
|
81
89
|
@pool.wait_until_free do
|
|
82
90
|
Console.warn(self){"Waiting for #{@protocol} pool to drain: #{@pool}"}
|
|
@@ -85,6 +93,9 @@ module Async
|
|
|
85
93
|
@pool.close
|
|
86
94
|
end
|
|
87
95
|
|
|
96
|
+
# Send a request to the remote server, with automatic retries for idempotent requests.
|
|
97
|
+
# @parameter request [Protocol::HTTP::Request] The request to send.
|
|
98
|
+
# @returns [Protocol::HTTP::Response] The response from the server.
|
|
88
99
|
def call(request)
|
|
89
100
|
request.scheme ||= self.scheme
|
|
90
101
|
request.authority ||= self.authority
|
|
@@ -133,6 +144,7 @@ module Async
|
|
|
133
144
|
end
|
|
134
145
|
end
|
|
135
146
|
|
|
147
|
+
# @returns [String] A summary of this client.
|
|
136
148
|
def inspect
|
|
137
149
|
"#<#{self.class} authority=#{@authority.inspect}>"
|
|
138
150
|
end
|
data/lib/async/http/endpoint.rb
CHANGED
|
@@ -28,6 +28,11 @@ module Async
|
|
|
28
28
|
"wss" => URI::WSS,
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
# Parse a URL string into an endpoint.
|
|
32
|
+
# @parameter string [String] The URL to parse.
|
|
33
|
+
# @parameter endpoint [IO::Endpoint::Generic | Nil] An optional underlying endpoint to use.
|
|
34
|
+
# @parameter options [Hash] Additional options to pass to {initialize}.
|
|
35
|
+
# @returns [Endpoint] The parsed endpoint.
|
|
31
36
|
def self.parse(string, endpoint = nil, **options)
|
|
32
37
|
url = URI.parse(string).normalize
|
|
33
38
|
|
|
@@ -80,6 +85,7 @@ module Async
|
|
|
80
85
|
end
|
|
81
86
|
end
|
|
82
87
|
|
|
88
|
+
# @returns [URI] The URL representation of this endpoint, including port if non-default.
|
|
83
89
|
def to_url
|
|
84
90
|
url = @url.dup
|
|
85
91
|
|
|
@@ -90,24 +96,29 @@ module Async
|
|
|
90
96
|
return url
|
|
91
97
|
end
|
|
92
98
|
|
|
99
|
+
# @returns [String] A short string representation of this endpoint.
|
|
93
100
|
def to_s
|
|
94
101
|
"\#<#{self.class} #{self.to_url} #{@options}>"
|
|
95
102
|
end
|
|
96
103
|
|
|
104
|
+
# @returns [String] A detailed string representation of this endpoint.
|
|
97
105
|
def inspect
|
|
98
106
|
"\#<#{self.class} #{self.to_url} #{@options.inspect}>"
|
|
99
107
|
end
|
|
100
108
|
|
|
101
109
|
attr :url
|
|
102
110
|
|
|
111
|
+
# @returns [Addrinfo] The address of the underlying endpoint.
|
|
103
112
|
def address
|
|
104
113
|
endpoint.address
|
|
105
114
|
end
|
|
106
115
|
|
|
116
|
+
# @returns [Boolean] Whether this endpoint uses a secure protocol (HTTPS or WSS).
|
|
107
117
|
def secure?
|
|
108
118
|
["https", "wss"].include?(self.scheme)
|
|
109
119
|
end
|
|
110
120
|
|
|
121
|
+
# @returns [Protocol] The protocol to use for this endpoint.
|
|
111
122
|
def protocol
|
|
112
123
|
@options.fetch(:protocol) do
|
|
113
124
|
if secure?
|
|
@@ -118,14 +129,17 @@ module Async
|
|
|
118
129
|
end
|
|
119
130
|
end
|
|
120
131
|
|
|
132
|
+
# @returns [Integer] The default port for this endpoint's scheme.
|
|
121
133
|
def default_port
|
|
122
134
|
secure? ? 443 : 80
|
|
123
135
|
end
|
|
124
136
|
|
|
137
|
+
# @returns [Boolean] Whether the endpoint's port is the default for its scheme.
|
|
125
138
|
def default_port?
|
|
126
139
|
port == default_port
|
|
127
140
|
end
|
|
128
141
|
|
|
142
|
+
# @returns [Integer] The port number for this endpoint.
|
|
129
143
|
def port
|
|
130
144
|
@options[:port] || @url.port || default_port
|
|
131
145
|
end
|
|
@@ -135,10 +149,12 @@ module Async
|
|
|
135
149
|
@options[:hostname] || @url.hostname
|
|
136
150
|
end
|
|
137
151
|
|
|
152
|
+
# @returns [String] The URL scheme, e.g. `"http"` or `"https"`.
|
|
138
153
|
def scheme
|
|
139
154
|
@options[:scheme] || @url.scheme
|
|
140
155
|
end
|
|
141
156
|
|
|
157
|
+
# @returns [String] The authority component (hostname and optional port).
|
|
142
158
|
def authority(ignore_default_port = true)
|
|
143
159
|
if ignore_default_port and default_port?
|
|
144
160
|
@url.hostname
|
|
@@ -158,10 +174,12 @@ module Async
|
|
|
158
174
|
return buffer
|
|
159
175
|
end
|
|
160
176
|
|
|
177
|
+
# @returns [Array(String)] The ALPN protocol names for TLS negotiation.
|
|
161
178
|
def alpn_protocols
|
|
162
179
|
@options.fetch(:alpn_protocols){self.protocol.names}
|
|
163
180
|
end
|
|
164
181
|
|
|
182
|
+
# @returns [Boolean] Whether the endpoint refers to a localhost address.
|
|
165
183
|
def localhost?
|
|
166
184
|
@url.hostname =~ /^(.*?\.)?localhost\.?$/
|
|
167
185
|
end
|
|
@@ -175,6 +193,7 @@ module Async
|
|
|
175
193
|
end
|
|
176
194
|
end
|
|
177
195
|
|
|
196
|
+
# @returns [OpenSSL::SSL::SSLContext] The SSL context for TLS connections.
|
|
178
197
|
def ssl_context
|
|
179
198
|
@options[:ssl_context] || OpenSSL::SSL::SSLContext.new.tap do |context|
|
|
180
199
|
if alpn_protocols = self.alpn_protocols
|
|
@@ -187,6 +206,9 @@ module Async
|
|
|
187
206
|
end
|
|
188
207
|
end
|
|
189
208
|
|
|
209
|
+
# Build a suitable endpoint, optionally wrapping in TLS for secure connections.
|
|
210
|
+
# @parameter endpoint [IO::Endpoint::Generic | Nil] An optional underlying endpoint to wrap.
|
|
211
|
+
# @returns [IO::Endpoint::Generic] The constructed endpoint.
|
|
190
212
|
def build_endpoint(endpoint = nil)
|
|
191
213
|
endpoint ||= tcp_endpoint
|
|
192
214
|
|
|
@@ -202,22 +224,30 @@ module Async
|
|
|
202
224
|
return endpoint
|
|
203
225
|
end
|
|
204
226
|
|
|
227
|
+
# @returns [IO::Endpoint::Generic] The resolved endpoint, built on demand.
|
|
205
228
|
def endpoint
|
|
206
229
|
@endpoint ||= build_endpoint
|
|
207
230
|
end
|
|
208
231
|
|
|
232
|
+
# Set the underlying endpoint, wrapping it as needed.
|
|
233
|
+
# @parameter endpoint [IO::Endpoint::Generic] The endpoint to assign.
|
|
209
234
|
def endpoint=(endpoint)
|
|
210
235
|
@endpoint = build_endpoint(endpoint)
|
|
211
236
|
end
|
|
212
237
|
|
|
238
|
+
# Bind to the endpoint.
|
|
213
239
|
def bind(*arguments, &block)
|
|
214
240
|
endpoint.bind(*arguments, &block)
|
|
215
241
|
end
|
|
216
242
|
|
|
243
|
+
# Connect to the endpoint.
|
|
217
244
|
def connect(&block)
|
|
218
245
|
endpoint.connect(&block)
|
|
219
246
|
end
|
|
220
247
|
|
|
248
|
+
# Enumerate all resolved endpoints.
|
|
249
|
+
# @yields {|endpoint| ...} Each resolved endpoint.
|
|
250
|
+
# @parameter endpoint [Endpoint] The resolved endpoint.
|
|
221
251
|
def each
|
|
222
252
|
return to_enum unless block_given?
|
|
223
253
|
|
|
@@ -226,14 +256,17 @@ module Async
|
|
|
226
256
|
end
|
|
227
257
|
end
|
|
228
258
|
|
|
259
|
+
# @returns [Array] A key suitable for identifying this endpoint in a hash.
|
|
229
260
|
def key
|
|
230
261
|
[@url, @options]
|
|
231
262
|
end
|
|
232
263
|
|
|
264
|
+
# @returns [Boolean] Whether two endpoints are equal.
|
|
233
265
|
def eql? other
|
|
234
266
|
self.key.eql? other.key
|
|
235
267
|
end
|
|
236
268
|
|
|
269
|
+
# @returns [Integer] The hash code for this endpoint.
|
|
237
270
|
def hash
|
|
238
271
|
self.key.hash
|
|
239
272
|
end
|
data/lib/async/http/internet.rb
CHANGED
|
@@ -13,7 +13,10 @@ require "protocol/http/accept_encoding"
|
|
|
13
13
|
|
|
14
14
|
module Async
|
|
15
15
|
module HTTP
|
|
16
|
+
# A high-level HTTP client for making requests to any URL, managing a pool of persistent connections keyed by host.
|
|
16
17
|
class Internet
|
|
18
|
+
# Initialize the internet client.
|
|
19
|
+
# @parameter options [Hash] Options to pass to the underlying {Client} instances.
|
|
17
20
|
def initialize(**options)
|
|
18
21
|
@clients = Hash.new
|
|
19
22
|
@options = options
|
|
@@ -23,6 +26,9 @@ module Async
|
|
|
23
26
|
# @attribute [Hash(URI, Client)]
|
|
24
27
|
attr :clients
|
|
25
28
|
|
|
29
|
+
# Get or create a client for the given endpoint.
|
|
30
|
+
# @parameter endpoint [Endpoint] The endpoint to connect to.
|
|
31
|
+
# @returns [Client] A client suitable for making requests to the endpoint.
|
|
26
32
|
def client_for(endpoint)
|
|
27
33
|
key = host_key(endpoint)
|
|
28
34
|
|
|
@@ -59,6 +65,7 @@ module Async
|
|
|
59
65
|
end
|
|
60
66
|
end
|
|
61
67
|
|
|
68
|
+
# Close all cached clients and release their resources.
|
|
62
69
|
def close
|
|
63
70
|
# The order of operations here is to avoid a race condition between iterating over clients (#close may yield) and creating new clients.
|
|
64
71
|
clients = @clients.values
|
|
@@ -9,6 +9,7 @@ require "protocol/url/reference"
|
|
|
9
9
|
|
|
10
10
|
module Async
|
|
11
11
|
module HTTP
|
|
12
|
+
# @namespace
|
|
12
13
|
module Middleware
|
|
13
14
|
# A client wrapper which transparently handles redirects to a given maximum number of hops.
|
|
14
15
|
#
|
|
@@ -28,6 +29,7 @@ module Async
|
|
|
28
29
|
# - <https://datatracker.ietf.org/doc/html/rfc7231#section-6-4-7> 307 Temporary Redirect.
|
|
29
30
|
#
|
|
30
31
|
class LocationRedirector < ::Protocol::HTTP::Middleware
|
|
32
|
+
# Raised when the maximum number of redirects has been exceeded.
|
|
31
33
|
class TooManyRedirects < StandardError
|
|
32
34
|
end
|
|
33
35
|
|
|
@@ -49,6 +51,10 @@ module Async
|
|
|
49
51
|
# The maximum number of hops which will limit the number of redirects until an error is thrown.
|
|
50
52
|
attr :maximum_hops
|
|
51
53
|
|
|
54
|
+
# Determine whether the redirect should switch the request method to GET.
|
|
55
|
+
# @parameter request [Protocol::HTTP::Request] The original request.
|
|
56
|
+
# @parameter response [Protocol::HTTP::Response] The redirect response.
|
|
57
|
+
# @returns [Boolean] Whether the method should be changed to GET.
|
|
52
58
|
def redirect_with_get?(request, response)
|
|
53
59
|
# We only want to switch to GET if the request method is something other than get, e.g. POST.
|
|
54
60
|
if request.method != GET
|
|
@@ -76,6 +82,9 @@ module Async
|
|
|
76
82
|
return true
|
|
77
83
|
end
|
|
78
84
|
|
|
85
|
+
# Make a request, transparently following redirects up to {maximum_hops} times.
|
|
86
|
+
# @parameter request [Protocol::HTTP::Request] The request to send.
|
|
87
|
+
# @returns [Protocol::HTTP::Response] The final response.
|
|
79
88
|
def call(request)
|
|
80
89
|
# We don't want to follow redirects for HEAD requests:
|
|
81
90
|
return super if request.head?
|
|
@@ -9,9 +9,14 @@ require "async/queue"
|
|
|
9
9
|
|
|
10
10
|
module Async
|
|
11
11
|
module HTTP
|
|
12
|
+
# @namespace
|
|
12
13
|
module Mock
|
|
13
14
|
# This is an endpoint which bridges a client with a local server.
|
|
14
15
|
class Endpoint
|
|
16
|
+
# Initialize a mock endpoint for testing.
|
|
17
|
+
# @parameter protocol [Protocol] The protocol to use for connections.
|
|
18
|
+
# @parameter scheme [String] The URL scheme.
|
|
19
|
+
# @parameter authority [String] The hostname authority.
|
|
15
20
|
def initialize(protocol = Protocol::HTTP2, scheme = "http", authority = "localhost", queue: Queue.new)
|
|
16
21
|
@protocol = protocol
|
|
17
22
|
@scheme = scheme
|
|
@@ -36,6 +41,8 @@ module Async
|
|
|
36
41
|
end
|
|
37
42
|
end
|
|
38
43
|
|
|
44
|
+
# Create a new client-side connection by enqueuing the server-side socket.
|
|
45
|
+
# @returns [Socket] The client-side socket.
|
|
39
46
|
def connect
|
|
40
47
|
local, remote = ::Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM)
|
|
41
48
|
|
|
@@ -44,6 +51,9 @@ module Async
|
|
|
44
51
|
return local
|
|
45
52
|
end
|
|
46
53
|
|
|
54
|
+
# Create a new mock endpoint that shares the same connection queue but adopts another endpoint's scheme and authority.
|
|
55
|
+
# @parameter endpoint [Endpoint] The endpoint to mirror the scheme and authority from.
|
|
56
|
+
# @returns [Endpoint] A new mock endpoint.
|
|
47
57
|
def wrap(endpoint)
|
|
48
58
|
self.class.new(@protocol, endpoint.scheme, endpoint.authority, queue: @queue)
|
|
49
59
|
end
|
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
module Async
|
|
7
7
|
module HTTP
|
|
8
8
|
module Protocol
|
|
9
|
+
# A protocol wrapper that forwards pre-configured options to client and server creation.
|
|
9
10
|
class Configured
|
|
11
|
+
# Initialize with a protocol and options.
|
|
12
|
+
# @parameter protocol [Protocol] The underlying protocol to configure.
|
|
13
|
+
# @parameter options [Hash] Options to forward to client and server creation.
|
|
10
14
|
def initialize(protocol, **options)
|
|
11
15
|
@protocol = protocol
|
|
12
16
|
@options = options
|
|
@@ -18,22 +22,33 @@ module Async
|
|
|
18
22
|
# @attribute [Hash] The options to pass to the protocol.
|
|
19
23
|
attr :options
|
|
20
24
|
|
|
25
|
+
# Create a client connection using the configured protocol options.
|
|
26
|
+
# @parameter peer [IO] The peer to communicate with.
|
|
27
|
+
# @parameter options [Hash] Additional options merged with the configured defaults.
|
|
21
28
|
def client(peer, **options)
|
|
22
29
|
options = @options.merge(options)
|
|
23
30
|
@protocol.client(peer, **options)
|
|
24
31
|
end
|
|
25
32
|
|
|
33
|
+
# Create a server connection using the configured protocol options.
|
|
34
|
+
# @parameter peer [IO] The peer to communicate with.
|
|
35
|
+
# @parameter options [Hash] Additional options merged with the configured defaults.
|
|
26
36
|
def server(peer, **options)
|
|
27
37
|
options = @options.merge(options)
|
|
28
38
|
@protocol.server(peer, **options)
|
|
29
39
|
end
|
|
30
40
|
|
|
41
|
+
# @returns [Array(String)] The protocol names from the underlying protocol.
|
|
31
42
|
def names
|
|
32
43
|
@protocol.names
|
|
33
44
|
end
|
|
34
45
|
end
|
|
35
46
|
|
|
47
|
+
# Provides a `new` method that creates a {Configured} wrapper, allowing protocols to be instantiated with custom options.
|
|
36
48
|
module Configurable
|
|
49
|
+
# Create a new {Configured} instance wrapping this protocol with the given options.
|
|
50
|
+
# @parameter options [Hash] Configuration options for client and server creation.
|
|
51
|
+
# @returns [Configured] A configured protocol instance.
|
|
37
52
|
def new(**options)
|
|
38
53
|
Configured.new(self, **options)
|
|
39
54
|
end
|
|
@@ -9,7 +9,9 @@ module Async
|
|
|
9
9
|
module HTTP
|
|
10
10
|
module Protocol
|
|
11
11
|
module HTTP1
|
|
12
|
+
# An HTTP/1 client connection that sends requests and reads responses.
|
|
12
13
|
class Client < Connection
|
|
14
|
+
# Initialize the HTTP/1 client connection.
|
|
13
15
|
def initialize(...)
|
|
14
16
|
super
|
|
15
17
|
|
|
@@ -18,6 +20,7 @@ module Async
|
|
|
18
20
|
|
|
19
21
|
attr_accessor :pool
|
|
20
22
|
|
|
23
|
+
# Called when the connection is closed, releasing it back to the pool.
|
|
21
24
|
def closed(error = nil)
|
|
22
25
|
super
|
|
23
26
|
|
|
@@ -13,7 +13,12 @@ module Async
|
|
|
13
13
|
module HTTP
|
|
14
14
|
module Protocol
|
|
15
15
|
module HTTP1
|
|
16
|
+
# An HTTP/1 connection that wraps an IO stream with version and state tracking.
|
|
16
17
|
class Connection < ::Protocol::HTTP1::Connection
|
|
18
|
+
# Initialize the connection with an IO stream and HTTP version.
|
|
19
|
+
# @parameter stream [IO::Stream] The underlying stream.
|
|
20
|
+
# @parameter version [String] The negotiated HTTP version string.
|
|
21
|
+
# @parameter options [Hash] Additional options for the connection.
|
|
17
22
|
def initialize(stream, version, **options)
|
|
18
23
|
super(stream, **options)
|
|
19
24
|
|
|
@@ -21,34 +26,41 @@ module Async
|
|
|
21
26
|
@version = version
|
|
22
27
|
end
|
|
23
28
|
|
|
29
|
+
# @returns [String] A string representation of this connection.
|
|
24
30
|
def to_s
|
|
25
31
|
"\#<#{self.class} negotiated #{@version}, #{@state}>"
|
|
26
32
|
end
|
|
27
33
|
|
|
34
|
+
# @returns [String] A JSON-compatible representation.
|
|
28
35
|
def as_json(...)
|
|
29
36
|
to_s
|
|
30
37
|
end
|
|
31
38
|
|
|
39
|
+
# @returns [String] A JSON string representation.
|
|
32
40
|
def to_json(...)
|
|
33
41
|
as_json.to_json(...)
|
|
34
42
|
end
|
|
35
43
|
|
|
36
44
|
attr :version
|
|
37
45
|
|
|
46
|
+
# @returns [Boolean] Whether this is an HTTP/1 connection.
|
|
38
47
|
def http1?
|
|
39
48
|
true
|
|
40
49
|
end
|
|
41
50
|
|
|
51
|
+
# @returns [Boolean] Whether this is an HTTP/2 connection.
|
|
42
52
|
def http2?
|
|
43
53
|
false
|
|
44
54
|
end
|
|
45
55
|
|
|
56
|
+
# @returns [Protocol::HTTP::Peer] The peer information for this connection.
|
|
46
57
|
def peer
|
|
47
58
|
@peer ||= ::Protocol::HTTP::Peer.for(@stream.io)
|
|
48
59
|
end
|
|
49
60
|
|
|
50
61
|
attr :count
|
|
51
62
|
|
|
63
|
+
# @returns [Integer] The maximum number of concurrent requests (always 1 for HTTP/1).
|
|
52
64
|
def concurrency
|
|
53
65
|
1
|
|
54
66
|
end
|
|
@@ -58,6 +70,7 @@ module Async
|
|
|
58
70
|
self.idle? && @stream&.readable?
|
|
59
71
|
end
|
|
60
72
|
|
|
73
|
+
# @returns [Boolean] Whether the connection can be reused for another request.
|
|
61
74
|
def reusable?
|
|
62
75
|
@persistent && @stream && !@stream.closed?
|
|
63
76
|
end
|
|
@@ -12,6 +12,8 @@ module Async
|
|
|
12
12
|
module HTTP1
|
|
13
13
|
# Keeps track of whether a body is being read, and if so, waits for it to be closed.
|
|
14
14
|
class Finishable < ::Protocol::HTTP::Body::Wrapper
|
|
15
|
+
# Initialize the finishable wrapper.
|
|
16
|
+
# @parameter body [Protocol::HTTP::Body::Readable] The body to wrap.
|
|
15
17
|
def initialize(body)
|
|
16
18
|
super(body)
|
|
17
19
|
|
|
@@ -21,16 +23,20 @@ module Async
|
|
|
21
23
|
@reading = false
|
|
22
24
|
end
|
|
23
25
|
|
|
26
|
+
# @returns [Boolean] Whether the body has started reading.
|
|
24
27
|
def reading?
|
|
25
28
|
@reading
|
|
26
29
|
end
|
|
27
30
|
|
|
31
|
+
# Read the next chunk from the body.
|
|
32
|
+
# @returns [String | Nil] The next chunk of data.
|
|
28
33
|
def read
|
|
29
34
|
@reading = true
|
|
30
35
|
|
|
31
36
|
super
|
|
32
37
|
end
|
|
33
38
|
|
|
39
|
+
# Close the body and signal any waiting tasks.
|
|
34
40
|
def close(error = nil)
|
|
35
41
|
super
|
|
36
42
|
|
|
@@ -40,6 +46,8 @@ module Async
|
|
|
40
46
|
end
|
|
41
47
|
end
|
|
42
48
|
|
|
49
|
+
# Wait for the body to be fully consumed or discard it.
|
|
50
|
+
# @parameter persistent [Boolean] Whether the connection will be reused.
|
|
43
51
|
def wait(persistent = true)
|
|
44
52
|
if @reading
|
|
45
53
|
@closed.wait
|
|
@@ -52,6 +60,7 @@ module Async
|
|
|
52
60
|
end
|
|
53
61
|
end
|
|
54
62
|
|
|
63
|
+
# @returns [String] A detailed representation of this finishable body.
|
|
55
64
|
def inspect
|
|
56
65
|
"#<#{self.class} closed=#{@closed} error=#{@error}> | #{super}"
|
|
57
66
|
end
|
|
@@ -9,7 +9,11 @@ module Async
|
|
|
9
9
|
module HTTP
|
|
10
10
|
module Protocol
|
|
11
11
|
module HTTP1
|
|
12
|
+
# An incoming HTTP/1 request parsed from the connection.
|
|
12
13
|
class Request < Protocol::Request
|
|
14
|
+
# Check whether the given request target is a valid path.
|
|
15
|
+
# @parameter target [String] The request target to validate.
|
|
16
|
+
# @returns [Boolean] Whether the target is a valid path.
|
|
13
17
|
def self.valid_path?(target)
|
|
14
18
|
if target.start_with?("/")
|
|
15
19
|
return true
|
|
@@ -22,6 +26,9 @@ module Async
|
|
|
22
26
|
|
|
23
27
|
URI_PATTERN = %r{\A(?<scheme>[^:/]+)://(?<authority>[^/]+)(?<path>.*)\z}
|
|
24
28
|
|
|
29
|
+
# Read and parse the next request from the connection.
|
|
30
|
+
# @parameter connection [Connection] The HTTP/1 connection to read from.
|
|
31
|
+
# @returns [Request | Nil] The parsed request, or `nil` if the connection is closed.
|
|
25
32
|
def self.read(connection)
|
|
26
33
|
connection.read_request do |authority, method, target, version, headers, body|
|
|
27
34
|
if method == ::Protocol::HTTP::Methods::CONNECT
|
|
@@ -42,6 +49,15 @@ module Async
|
|
|
42
49
|
|
|
43
50
|
UPGRADE = "upgrade"
|
|
44
51
|
|
|
52
|
+
# Initialize the request from the parsed components.
|
|
53
|
+
# @parameter connection [Connection] The underlying connection.
|
|
54
|
+
# @parameter scheme [String | Nil] The request scheme.
|
|
55
|
+
# @parameter authority [String | Nil] The request authority.
|
|
56
|
+
# @parameter method [String] The HTTP method.
|
|
57
|
+
# @parameter path [String | Nil] The request path.
|
|
58
|
+
# @parameter version [String] The HTTP version.
|
|
59
|
+
# @parameter headers [Protocol::HTTP::Headers] The request headers.
|
|
60
|
+
# @parameter body [Protocol::HTTP::Body::Readable | Nil] The request body.
|
|
45
61
|
def initialize(connection, scheme, authority, method, path, version, headers, body)
|
|
46
62
|
@connection = connection
|
|
47
63
|
|
|
@@ -51,18 +67,24 @@ module Async
|
|
|
51
67
|
super(scheme, authority, method, path, version, headers, body, protocol, self.public_method(:write_interim_response))
|
|
52
68
|
end
|
|
53
69
|
|
|
70
|
+
# @returns [Connection] The underlying HTTP/1 connection.
|
|
54
71
|
def connection
|
|
55
72
|
@connection
|
|
56
73
|
end
|
|
57
74
|
|
|
75
|
+
# @returns [Boolean] Whether connection hijacking is supported.
|
|
58
76
|
def hijack?
|
|
59
77
|
true
|
|
60
78
|
end
|
|
61
79
|
|
|
80
|
+
# Hijack the underlying connection for bidirectional communication.
|
|
62
81
|
def hijack!
|
|
63
82
|
@connection.hijack!
|
|
64
83
|
end
|
|
65
84
|
|
|
85
|
+
# Write an interim (1xx) response to the client.
|
|
86
|
+
# @parameter status [Integer] The interim HTTP status code.
|
|
87
|
+
# @parameter headers [Hash | Nil] Optional interim response headers.
|
|
66
88
|
def write_interim_response(status, headers = nil)
|
|
67
89
|
@connection.write_interim_response(@version, status, headers)
|
|
68
90
|
end
|