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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/async/http/body/hijack.rb +17 -0
  4. data/lib/async/http/body/pipe.rb +3 -0
  5. data/lib/async/http/body.rb +3 -0
  6. data/lib/async/http/client.rb +16 -4
  7. data/lib/async/http/endpoint.rb +33 -0
  8. data/lib/async/http/internet.rb +7 -0
  9. data/lib/async/http/middleware/location_redirector.rb +9 -0
  10. data/lib/async/http/mock/endpoint.rb +10 -0
  11. data/lib/async/http/protocol/configurable.rb +15 -0
  12. data/lib/async/http/protocol/http1/client.rb +3 -0
  13. data/lib/async/http/protocol/http1/connection.rb +13 -0
  14. data/lib/async/http/protocol/http1/finishable.rb +9 -0
  15. data/lib/async/http/protocol/http1/request.rb +22 -0
  16. data/lib/async/http/protocol/http1/response.rb +10 -0
  17. data/lib/async/http/protocol/http1/server.rb +7 -0
  18. data/lib/async/http/protocol/http1.rb +1 -0
  19. data/lib/async/http/protocol/http10.rb +1 -0
  20. data/lib/async/http/protocol/http11.rb +1 -0
  21. data/lib/async/http/protocol/http2/client.rb +10 -0
  22. data/lib/async/http/protocol/http2/connection.rb +16 -0
  23. data/lib/async/http/protocol/http2/input.rb +5 -0
  24. data/lib/async/http/protocol/http2/output.rb +13 -0
  25. data/lib/async/http/protocol/http2/request.rb +17 -0
  26. data/lib/async/http/protocol/http2/response.rb +23 -0
  27. data/lib/async/http/protocol/http2/server.rb +16 -0
  28. data/lib/async/http/protocol/http2/stream.rb +19 -0
  29. data/lib/async/http/protocol/http2.rb +1 -0
  30. data/lib/async/http/protocol/https.rb +7 -0
  31. data/lib/async/http/protocol/request.rb +9 -1
  32. data/lib/async/http/protocol/response.rb +6 -1
  33. data/lib/async/http/proxy.rb +12 -0
  34. data/lib/async/http/server.rb +14 -0
  35. data/lib/async/http/statistics.rb +19 -0
  36. data/lib/async/http/version.rb +3 -1
  37. data/lib/async/http.rb +0 -3
  38. data/readme.md +20 -4
  39. data/releases.md +4 -0
  40. data.tar.gz.sig +0 -0
  41. metadata +3 -3
  42. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e47e3773a940f0a7218dacb8f188531c1d5d42a12ad4708688011953135a6b1
4
- data.tar.gz: 9cb9fa6ca50c1a31ca46f1c9bad7e678ae2bd78d041ae839714e92832e5b1fec
3
+ metadata.gz: 391e059d6c94805257b5e058b02987ad88ce8decaafafb6e7815faeaeb8a5079
4
+ data.tar.gz: 2a260bb8dba2ffb109f612400d9e05c03012f994c275f6c447a09d915cbd186e
5
5
  SHA512:
6
- metadata.gz: aaa2d45e58a66255a71ab802e5a4706d8c09ac85665e7004c5c06e91bc38cc919f18229b5670e411df13364e09ad243bf8d45c3d68a361ad8653861f2527a8cb
7
- data.tar.gz: 16a4894930c5a3ce0611de7b4840aecf9d050b9de59e8dc0da2b0803978bbd47933f9066850c7d0d596f1f6ed609047f31007b742ea2d822fd8b36499580dfb0
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
@@ -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
@@ -6,8 +6,11 @@
6
6
  require "protocol/http/body/buffered"
7
7
  require_relative "body/writable"
8
8
 
9
+ # @namespace
9
10
  module Async
11
+ # @namespace
10
12
  module HTTP
13
+ # @namespace
11
14
  module Body
12
15
  include ::Protocol::HTTP::Body
13
16
  end
@@ -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
- protocol: @protocol,
45
- retries: @retries,
46
- scheme: @scheme,
47
- authority: @authority,
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
@@ -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
@@ -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