Skip to content

Commit e762ee6

Browse files
100% documentation coverage.
1 parent 3057c9c commit e762ee6

35 files changed

+362
-9
lines changed

lib/async/http.rb

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
# Copyright, 2017-2024, by Samuel Williams.
55

66
require_relative "http/version"
7-
87
require_relative "http/client"
98
require_relative "http/server"
10-
119
require_relative "http/internet"
12-
1310
require_relative "http/endpoint"

lib/async/http/body.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
require "protocol/http/body/buffered"
77
require_relative "body/writable"
88

9+
# @namespace
910
module Async
11+
# @namespace
1012
module HTTP
13+
# @namespace
1114
module Body
1215
include ::Protocol::HTTP::Body
1316
end

lib/async/http/body/hijack.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,25 @@ module HTTP
1313
module Body
1414
# 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.
1515
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.
1621
def self.response(request, status, headers, &block)
1722
::Protocol::HTTP::Response[status, headers, self.wrap(request, &block)]
1823
end
1924

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.
2028
def self.wrap(request = nil, &block)
2129
self.new(block, request&.body)
2230
end
2331

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.
2435
def initialize(block, input = nil)
2536
@block = block
2637
@input = input
@@ -35,6 +46,8 @@ def stream?
3546
true
3647
end
3748

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.
3851
def call(stream)
3952
@block.call(stream)
4053
end
@@ -46,6 +59,8 @@ def empty?
4659
@output&.empty?
4760
end
4861

62+
# Whether the body has output ready to be read.
63+
# @returns [Boolean | Nil]
4964
def ready?
5065
@output&.ready?
5166
end
@@ -66,10 +81,12 @@ def read
6681
return @output.read
6782
end
6883

84+
# @returns [String] A detailed representation of this body.
6985
def inspect
7086
"\#<#{self.class} #{@block.inspect}>"
7187
end
7288

89+
# @returns [String] A short summary of this body.
7390
def to_s
7491
"<Hijack #{@block.class}>"
7592
end

lib/async/http/body/pipe.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
module Async
1010
module HTTP
1111
module Body
12+
# A bidirectional pipe that connects an input body to an output body using a Unix socket pair.
1213
class Pipe
1314
# If the input stream is closed first, it's likely the output stream will also be closed.
1415
def initialize(input, output = Writable.new, task: Task.current)
@@ -27,10 +28,12 @@ def initialize(input, output = Writable.new, task: Task.current)
2728
task.async(transient: true, &self.method(:writer))
2829
end
2930

31+
# @returns [IO] The underlying IO object for the tail of the pipe.
3032
def to_io
3133
@tail
3234
end
3335

36+
# Close the pipe and stop the reader and writer tasks.
3437
def close
3538
@reader&.stop
3639
@writer&.stop

lib/async/http/client.rb

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module Async
1717
module HTTP
1818
DEFAULT_RETRIES = 3
1919

20+
# An HTTP client that manages persistent connections to a specific endpoint, with automatic retries for idempotent requests.
2021
class Client < ::Protocol::HTTP::Methods
2122
# Provides a robust interface to a server.
2223
# * If there are no connections, it will create one.
@@ -38,16 +39,18 @@ def initialize(endpoint, protocol: endpoint.protocol, scheme: endpoint.scheme, a
3839
@authority = authority
3940
end
4041

42+
# @returns [Hash] A JSON-compatible representation of this client.
4143
def as_json(...)
4244
{
4345
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,
4850
}
4951
end
5052

53+
# @returns [String] A JSON string representation of this client.
5154
def to_json(...)
5255
as_json.to_json(...)
5356
end
@@ -61,10 +64,14 @@ def to_json(...)
6164
attr :scheme
6265
attr :authority
6366

67+
# @returns [Boolean] Whether the client uses a secure (TLS) connection.
6468
def secure?
6569
@endpoint.secure?
6670
end
6771

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}.
6875
def self.open(*arguments, **options, &block)
6976
client = self.new(*arguments, **options)
7077

@@ -77,6 +84,7 @@ def self.open(*arguments, **options, &block)
7784
end
7885
end
7986

87+
# Close the client and all associated connections.
8088
def close
8189
@pool.wait_until_free do
8290
Console.warn(self){"Waiting for #{@protocol} pool to drain: #{@pool}"}
@@ -85,6 +93,9 @@ def close
8593
@pool.close
8694
end
8795

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.
8899
def call(request)
89100
request.scheme ||= self.scheme
90101
request.authority ||= self.authority
@@ -133,6 +144,7 @@ def call(request)
133144
end
134145
end
135146

147+
# @returns [String] A summary of this client.
136148
def inspect
137149
"#<#{self.class} authority=#{@authority.inspect}>"
138150
end

lib/async/http/endpoint.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ class Endpoint < ::IO::Endpoint::Generic
2828
"wss" => URI::WSS,
2929
}
3030

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.
3136
def self.parse(string, endpoint = nil, **options)
3237
url = URI.parse(string).normalize
3338

@@ -80,6 +85,7 @@ def initialize(url, endpoint = nil, **options)
8085
end
8186
end
8287

88+
# @returns [URI] The URL representation of this endpoint, including port if non-default.
8389
def to_url
8490
url = @url.dup
8591

@@ -90,24 +96,29 @@ def to_url
9096
return url
9197
end
9298

99+
# @returns [String] A short string representation of this endpoint.
93100
def to_s
94101
"\#<#{self.class} #{self.to_url} #{@options}>"
95102
end
96103

104+
# @returns [String] A detailed string representation of this endpoint.
97105
def inspect
98106
"\#<#{self.class} #{self.to_url} #{@options.inspect}>"
99107
end
100108

101109
attr :url
102110

111+
# @returns [Addrinfo] The address of the underlying endpoint.
103112
def address
104113
endpoint.address
105114
end
106115

116+
# @returns [Boolean] Whether this endpoint uses a secure protocol (HTTPS or WSS).
107117
def secure?
108118
["https", "wss"].include?(self.scheme)
109119
end
110120

121+
# @returns [Protocol] The protocol to use for this endpoint.
111122
def protocol
112123
@options.fetch(:protocol) do
113124
if secure?
@@ -118,14 +129,17 @@ def protocol
118129
end
119130
end
120131

132+
# @returns [Integer] The default port for this endpoint's scheme.
121133
def default_port
122134
secure? ? 443 : 80
123135
end
124136

137+
# @returns [Boolean] Whether the endpoint's port is the default for its scheme.
125138
def default_port?
126139
port == default_port
127140
end
128141

142+
# @returns [Integer] The port number for this endpoint.
129143
def port
130144
@options[:port] || @url.port || default_port
131145
end
@@ -135,10 +149,12 @@ def hostname
135149
@options[:hostname] || @url.hostname
136150
end
137151

152+
# @returns [String] The URL scheme, e.g. `"http"` or `"https"`.
138153
def scheme
139154
@options[:scheme] || @url.scheme
140155
end
141156

157+
# @returns [String] The authority component (hostname and optional port).
142158
def authority(ignore_default_port = true)
143159
if ignore_default_port and default_port?
144160
@url.hostname
@@ -158,10 +174,12 @@ def path
158174
return buffer
159175
end
160176

177+
# @returns [Array(String)] The ALPN protocol names for TLS negotiation.
161178
def alpn_protocols
162179
@options.fetch(:alpn_protocols){self.protocol.names}
163180
end
164181

182+
# @returns [Boolean] Whether the endpoint refers to a localhost address.
165183
def localhost?
166184
@url.hostname =~ /^(.*?\.)?localhost\.?$/
167185
end
@@ -175,6 +193,7 @@ def ssl_verify_mode
175193
end
176194
end
177195

196+
# @returns [OpenSSL::SSL::SSLContext] The SSL context for TLS connections.
178197
def ssl_context
179198
@options[:ssl_context] || OpenSSL::SSL::SSLContext.new.tap do |context|
180199
if alpn_protocols = self.alpn_protocols
@@ -187,6 +206,9 @@ def ssl_context
187206
end
188207
end
189208

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.
190212
def build_endpoint(endpoint = nil)
191213
endpoint ||= tcp_endpoint
192214

@@ -202,22 +224,30 @@ def build_endpoint(endpoint = nil)
202224
return endpoint
203225
end
204226

227+
# @returns [IO::Endpoint::Generic] The resolved endpoint, built on demand.
205228
def endpoint
206229
@endpoint ||= build_endpoint
207230
end
208231

232+
# Set the underlying endpoint, wrapping it as needed.
233+
# @parameter endpoint [IO::Endpoint::Generic] The endpoint to assign.
209234
def endpoint=(endpoint)
210235
@endpoint = build_endpoint(endpoint)
211236
end
212237

238+
# Bind to the endpoint.
213239
def bind(*arguments, &block)
214240
endpoint.bind(*arguments, &block)
215241
end
216242

243+
# Connect to the endpoint.
217244
def connect(&block)
218245
endpoint.connect(&block)
219246
end
220247

248+
# Enumerate all resolved endpoints.
249+
# @yields {|endpoint| ...} Each resolved endpoint.
250+
# @parameter endpoint [Endpoint] The resolved endpoint.
221251
def each
222252
return to_enum unless block_given?
223253

@@ -226,14 +256,17 @@ def each
226256
end
227257
end
228258

259+
# @returns [Array] A key suitable for identifying this endpoint in a hash.
229260
def key
230261
[@url, @options]
231262
end
232263

264+
# @returns [Boolean] Whether two endpoints are equal.
233265
def eql? other
234266
self.key.eql? other.key
235267
end
236268

269+
# @returns [Integer] The hash code for this endpoint.
237270
def hash
238271
self.key.hash
239272
end

lib/async/http/internet.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313

1414
module Async
1515
module HTTP
16+
# A high-level HTTP client for making requests to any URL, managing a pool of persistent connections keyed by host.
1617
class Internet
18+
# Initialize the internet client.
19+
# @parameter options [Hash] Options to pass to the underlying {Client} instances.
1720
def initialize(**options)
1821
@clients = Hash.new
1922
@options = options
@@ -23,6 +26,9 @@ def initialize(**options)
2326
# @attribute [Hash(URI, Client)]
2427
attr :clients
2528

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.
2632
def client_for(endpoint)
2733
key = host_key(endpoint)
2834

@@ -59,6 +65,7 @@ def call(verb, url, *arguments, **options, &block)
5965
end
6066
end
6167

68+
# Close all cached clients and release their resources.
6269
def close
6370
# The order of operations here is to avoid a race condition between iterating over clients (#close may yield) and creating new clients.
6471
clients = @clients.values

lib/async/http/middleware/location_redirector.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
module Async
1111
module HTTP
12+
# @namespace
1213
module Middleware
1314
# A client wrapper which transparently handles redirects to a given maximum number of hops.
1415
#
@@ -28,6 +29,7 @@ module Middleware
2829
# - <https://datatracker.ietf.org/doc/html/rfc7231#section-6-4-7> 307 Temporary Redirect.
2930
#
3031
class LocationRedirector < ::Protocol::HTTP::Middleware
32+
# Raised when the maximum number of redirects has been exceeded.
3133
class TooManyRedirects < StandardError
3234
end
3335

@@ -49,6 +51,10 @@ def initialize(app, maximum_hops = 3)
4951
# The maximum number of hops which will limit the number of redirects until an error is thrown.
5052
attr :maximum_hops
5153

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.
5258
def redirect_with_get?(request, response)
5359
# We only want to switch to GET if the request method is something other than get, e.g. POST.
5460
if request.method != GET
@@ -76,6 +82,9 @@ def handle_redirect(request, location)
7682
return true
7783
end
7884

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.
7988
def call(request)
8089
# We don't want to follow redirects for HEAD requests:
8190
return super if request.head?

0 commit comments

Comments
 (0)