async-http 0.87.0 → 0.88.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/bake/async/http/h2spec.rb +1 -1
- data/lib/async/http/client.rb +2 -2
- data/lib/async/http/endpoint.rb +1 -1
- data/lib/async/http/protocol/configurable.rb +43 -0
- data/lib/async/http/protocol/defaulton.rb +36 -0
- data/lib/async/http/protocol/http.rb +38 -16
- data/lib/async/http/protocol/http1/client.rb +0 -2
- data/lib/async/http/protocol/http1/connection.rb +3 -2
- data/lib/async/http/protocol/http1/server.rb +1 -1
- data/lib/async/http/protocol/http1.rb +19 -4
- data/lib/async/http/protocol/http10.rb +17 -4
- data/lib/async/http/protocol/http11.rb +17 -4
- data/lib/async/http/protocol/http2/client.rb +0 -2
- data/lib/async/http/protocol/http2/connection.rb +2 -3
- data/lib/async/http/protocol/http2/server.rb +0 -2
- data/lib/async/http/protocol/http2/stream.rb +1 -1
- data/lib/async/http/protocol/http2.rb +19 -2
- data/lib/async/http/protocol/https.rb +45 -11
- data/lib/async/http/server.rb +2 -2
- data/lib/async/http/version.rb +1 -1
- data/readme.md +4 -4
- data/releases.md +57 -0
- data.tar.gz.sig +0 -0
- metadata +4 -2
- 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: 605cb4a26c37dbfdaa3225fb306b3c2bc9e01da814094babb8e46cdb05594999
|
4
|
+
data.tar.gz: 40c28864c03492bc20d8aa02947210841ca5f2c75b841e222c90d8f3a36102af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95eb6bb5d079d59b9ed9698d648bd709a7f60ae1a5833547cadd9a11def31e6e8d99b929650df676c0ed215a0719bd604ffc7c57c6e047cdefaa0ac11ff2d71b
|
7
|
+
data.tar.gz: 0cdbfe53cbcf3acb1529536ddac851c09e53dd9b7c40fb1d1059abc073138f67cda426a5825966052d52a2d35f39170047c814f7ca6168b25f149fb2baf1e7e0
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/bake/async/http/h2spec.rb
CHANGED
@@ -30,7 +30,7 @@ def server
|
|
30
30
|
|
31
31
|
container = Async::Container.new
|
32
32
|
|
33
|
-
Console.
|
33
|
+
Console.info(self){"Starting server..."}
|
34
34
|
|
35
35
|
container.run(count: 1) do
|
36
36
|
server = Async::HTTP::Server.for(endpoint, protocol: Async::HTTP::Protocol::HTTP2, scheme: "https") do |request|
|
data/lib/async/http/client.rb
CHANGED
@@ -81,7 +81,7 @@ module Async
|
|
81
81
|
|
82
82
|
def close
|
83
83
|
while @pool.busy?
|
84
|
-
Console.
|
84
|
+
Console.warn(self) {"Waiting for #{@protocol} pool to drain: #{@pool}"}
|
85
85
|
@pool.wait
|
86
86
|
end
|
87
87
|
|
@@ -164,7 +164,7 @@ module Async
|
|
164
164
|
self.assign_default_tags(options[:tags] ||= {})
|
165
165
|
|
166
166
|
Async::Pool::Controller.wrap(**options) do
|
167
|
-
Console.
|
167
|
+
Console.debug(self) {"Making connection to #{@endpoint.inspect}"}
|
168
168
|
|
169
169
|
@protocol.client(@endpoint.connect)
|
170
170
|
end
|
data/lib/async/http/endpoint.rb
CHANGED
@@ -40,7 +40,7 @@ module Async
|
|
40
40
|
#
|
41
41
|
# @parameter scheme [String] The scheme to use, e.g. "http" or "https".
|
42
42
|
# @parameter hostname [String] The hostname to connect to (or bind to).
|
43
|
-
# @parameter *options [Hash] Additional options, passed to {
|
43
|
+
# @parameter *options [Hash] Additional options, passed to {initialize}.
|
44
44
|
def self.for(scheme, hostname, path = "/", **options)
|
45
45
|
# TODO: Consider using URI.for once it becomes available:
|
46
46
|
uri_klass = SCHEMES.fetch(scheme.downcase) do
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
module Async
|
7
|
+
module HTTP
|
8
|
+
module Protocol
|
9
|
+
class Configured
|
10
|
+
def initialize(protocol, **options)
|
11
|
+
@protocol = protocol
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
# @attribute [Protocol] The underlying protocol.
|
16
|
+
attr :protocol
|
17
|
+
|
18
|
+
# @attribute [Hash] The options to pass to the protocol.
|
19
|
+
attr :options
|
20
|
+
|
21
|
+
def client(peer, **options)
|
22
|
+
options = @options.merge(options)
|
23
|
+
@protocol.client(peer, **options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def server(peer, **options)
|
27
|
+
options = @options.merge(options)
|
28
|
+
@protocol.server(peer, **options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def names
|
32
|
+
@protocol.names
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module Configurable
|
37
|
+
def new(**options)
|
38
|
+
Configured.new(self, **options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
module Async
|
7
|
+
module HTTP
|
8
|
+
module Protocol
|
9
|
+
# This module provides a default instance of the protocol, which can be used to create clients and servers. The name is a play on "Default" + "Singleton".
|
10
|
+
module Defaulton
|
11
|
+
def self.extended(base)
|
12
|
+
base.instance_variable_set(:@default, base.new)
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :default
|
16
|
+
|
17
|
+
# Create a client for an outbound connection, using the default instance.
|
18
|
+
def client(peer, **options)
|
19
|
+
default.client(peer, **options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Create a server for an inbound connection, using the default instance.
|
23
|
+
def server(peer, **options)
|
24
|
+
default.server(peer, **options)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @returns [Array] The names of the supported protocol, used for Application Layer Protocol Negotiation (ALPN), using the default instance.
|
28
|
+
def names
|
29
|
+
default.names
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private_constant :Defaulton
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -4,19 +4,33 @@
|
|
4
4
|
# Copyright, 2024, by Thomas Morgan.
|
5
5
|
# Copyright, 2024, by Samuel Williams.
|
6
6
|
|
7
|
+
require_relative "defaulton"
|
8
|
+
|
7
9
|
require_relative "http1"
|
8
10
|
require_relative "http2"
|
9
11
|
|
10
12
|
module Async
|
11
13
|
module HTTP
|
12
14
|
module Protocol
|
13
|
-
# HTTP is an http:// server that auto-selects HTTP/1.1 or HTTP/2 by detecting the HTTP/2
|
14
|
-
|
15
|
-
module HTTP
|
15
|
+
# HTTP is an http:// server that auto-selects HTTP/1.1 or HTTP/2 by detecting the HTTP/2 connection preface.
|
16
|
+
class HTTP
|
16
17
|
HTTP2_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
17
18
|
HTTP2_PREFACE_SIZE = HTTP2_PREFACE.bytesize
|
18
19
|
|
19
|
-
|
20
|
+
# Create a new HTTP protocol instance.
|
21
|
+
#
|
22
|
+
# @parameter http1 [HTTP1] The HTTP/1 protocol instance.
|
23
|
+
# @parameter http2 [HTTP2] The HTTP/2 protocol instance.
|
24
|
+
def initialize(http1: HTTP1, http2: HTTP2)
|
25
|
+
@http1 = http1
|
26
|
+
@http2 = http2
|
27
|
+
end
|
28
|
+
|
29
|
+
# Determine if the inbound connection is HTTP/1 or HTTP/2.
|
30
|
+
#
|
31
|
+
# @parameter stream [IO::Stream] The stream to detect the protocol for.
|
32
|
+
# @returns [Class] The protocol class to use.
|
33
|
+
def protocol_for(stream)
|
20
34
|
# Detect HTTP/2 connection preface
|
21
35
|
# https://www.rfc-editor.org/rfc/rfc9113.html#section-3.4
|
22
36
|
preface = stream.peek do |read_buffer|
|
@@ -29,27 +43,35 @@ module Async
|
|
29
43
|
end
|
30
44
|
|
31
45
|
if preface == HTTP2_PREFACE
|
32
|
-
|
46
|
+
@http2
|
33
47
|
else
|
34
|
-
|
48
|
+
@http1
|
35
49
|
end
|
36
50
|
end
|
37
51
|
|
38
|
-
#
|
39
|
-
#
|
40
|
-
|
41
|
-
|
52
|
+
# Create a client for an outbound connection. Defaults to HTTP/1 for plaintext connections.
|
53
|
+
#
|
54
|
+
# @parameter peer [IO] The peer to communicate with.
|
55
|
+
# @parameter options [Hash] Options to pass to the protocol, keyed by protocol class.
|
56
|
+
def client(peer, **options)
|
57
|
+
options = options[@http1] || {}
|
58
|
+
|
59
|
+
return @http1.client(peer, **options)
|
42
60
|
end
|
43
61
|
|
44
|
-
|
45
|
-
|
62
|
+
# Create a server for an inbound connection. Able to detect HTTP1 and HTTP2.
|
63
|
+
#
|
64
|
+
# @parameter peer [IO] The peer to communicate with.
|
65
|
+
# @parameter options [Hash] Options to pass to the protocol, keyed by protocol class.
|
66
|
+
def server(peer, **options)
|
67
|
+
stream = IO::Stream(peer)
|
68
|
+
protocol = protocol_for(stream)
|
69
|
+
options = options[protocol] || {}
|
46
70
|
|
47
|
-
return
|
71
|
+
return protocol.server(stream, **options)
|
48
72
|
end
|
49
73
|
|
50
|
-
|
51
|
-
["h2", "http/1.1", "http/1.0"]
|
52
|
-
end
|
74
|
+
extend Defaulton
|
53
75
|
end
|
54
76
|
end
|
55
77
|
end
|
@@ -32,8 +32,6 @@ module Async
|
|
32
32
|
|
33
33
|
# Used by the client to send requests to the remote server.
|
34
34
|
def call(request, task: Task.current)
|
35
|
-
Console.logger.debug(self) {"#{request.method} #{request.path} #{request.headers.inspect}"}
|
36
|
-
|
37
35
|
# Mark the start of the trailers:
|
38
36
|
trailer = request.headers.trailer!
|
39
37
|
|
@@ -14,9 +14,10 @@ module Async
|
|
14
14
|
module Protocol
|
15
15
|
module HTTP1
|
16
16
|
class Connection < ::Protocol::HTTP1::Connection
|
17
|
-
def initialize(stream, version)
|
18
|
-
super(stream)
|
17
|
+
def initialize(stream, version, **options)
|
18
|
+
super(stream, **options)
|
19
19
|
|
20
|
+
# On the client side, we need to send the HTTP version with the initial request. On the server side, there are some scenarios (bad request) where we don't know the request version. In those cases, we use this value, which is either hard coded based on the protocol being used, OR could be negotiated during the connection setup (e.g. ALPN).
|
20
21
|
@version = version
|
21
22
|
end
|
22
23
|
|
@@ -34,7 +34,7 @@ module Async
|
|
34
34
|
write_body(@version, nil)
|
35
35
|
rescue => error
|
36
36
|
# At this point, there is very little we can do to recover:
|
37
|
-
Console
|
37
|
+
Console.debug(self, "Failed to write failure response!", error)
|
38
38
|
end
|
39
39
|
|
40
40
|
def next_request
|
@@ -4,6 +4,8 @@
|
|
4
4
|
# Copyright, 2017-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2024, by Thomas Morgan.
|
6
6
|
|
7
|
+
require_relative "configurable"
|
8
|
+
|
7
9
|
require_relative "http1/client"
|
8
10
|
require_relative "http1/server"
|
9
11
|
|
@@ -13,28 +15,41 @@ module Async
|
|
13
15
|
module HTTP
|
14
16
|
module Protocol
|
15
17
|
module HTTP1
|
18
|
+
extend Configurable
|
19
|
+
|
16
20
|
VERSION = "HTTP/1.1"
|
17
21
|
|
22
|
+
# @returns [Boolean] Whether the protocol supports bidirectional communication.
|
18
23
|
def self.bidirectional?
|
19
24
|
true
|
20
25
|
end
|
21
26
|
|
27
|
+
# @returns [Boolean] Whether the protocol supports trailers.
|
22
28
|
def self.trailer?
|
23
29
|
true
|
24
30
|
end
|
25
31
|
|
26
|
-
|
32
|
+
# Create a client for an outbound connection.
|
33
|
+
#
|
34
|
+
# @parameter peer [IO] The peer to communicate with.
|
35
|
+
# @parameter options [Hash] Options to pass to the client instance.
|
36
|
+
def self.client(peer, **options)
|
27
37
|
stream = ::IO::Stream(peer)
|
28
38
|
|
29
|
-
return HTTP1::Client.new(stream, VERSION)
|
39
|
+
return HTTP1::Client.new(stream, VERSION, **options)
|
30
40
|
end
|
31
41
|
|
32
|
-
|
42
|
+
# Create a server for an inbound connection.
|
43
|
+
#
|
44
|
+
# @parameter peer [IO] The peer to communicate with.
|
45
|
+
# @parameter options [Hash] Options to pass to the server instance.
|
46
|
+
def self.server(peer, **options)
|
33
47
|
stream = ::IO::Stream(peer)
|
34
48
|
|
35
|
-
return HTTP1::Server.new(stream, VERSION)
|
49
|
+
return HTTP1::Server.new(stream, VERSION, **options)
|
36
50
|
end
|
37
51
|
|
52
|
+
# @returns [Array] The names of the supported protocol.
|
38
53
|
def self.names
|
39
54
|
["http/1.1", "http/1.0"]
|
40
55
|
end
|
@@ -10,28 +10,41 @@ module Async
|
|
10
10
|
module HTTP
|
11
11
|
module Protocol
|
12
12
|
module HTTP10
|
13
|
+
extend Configurable
|
14
|
+
|
13
15
|
VERSION = "HTTP/1.0"
|
14
16
|
|
17
|
+
# @returns [Boolean] Whether the protocol supports bidirectional communication.
|
15
18
|
def self.bidirectional?
|
16
19
|
false
|
17
20
|
end
|
18
21
|
|
22
|
+
# @returns [Boolean] Whether the protocol supports trailers.
|
19
23
|
def self.trailer?
|
20
24
|
false
|
21
25
|
end
|
22
26
|
|
23
|
-
|
27
|
+
# Create a client for an outbound connection.
|
28
|
+
#
|
29
|
+
# @parameter peer [IO] The peer to communicate with.
|
30
|
+
# @parameter options [Hash] Options to pass to the client instance.
|
31
|
+
def self.client(peer, **options)
|
24
32
|
stream = ::IO::Stream(peer)
|
25
33
|
|
26
|
-
return HTTP1::Client.new(stream, VERSION)
|
34
|
+
return HTTP1::Client.new(stream, VERSION, **options)
|
27
35
|
end
|
28
36
|
|
29
|
-
|
37
|
+
# Create a server for an inbound connection.
|
38
|
+
#
|
39
|
+
# @parameter peer [IO] The peer to communicate with.
|
40
|
+
# @parameter options [Hash] Options to pass to the server instance.
|
41
|
+
def self.server(peer, **options)
|
30
42
|
stream = ::IO::Stream(peer)
|
31
43
|
|
32
|
-
return HTTP1::Server.new(stream, VERSION)
|
44
|
+
return HTTP1::Server.new(stream, VERSION, **options)
|
33
45
|
end
|
34
46
|
|
47
|
+
# @returns [Array] The names of the supported protocol.
|
35
48
|
def self.names
|
36
49
|
["http/1.0"]
|
37
50
|
end
|
@@ -11,28 +11,41 @@ module Async
|
|
11
11
|
module HTTP
|
12
12
|
module Protocol
|
13
13
|
module HTTP11
|
14
|
+
extend Configurable
|
15
|
+
|
14
16
|
VERSION = "HTTP/1.1"
|
15
17
|
|
18
|
+
# @returns [Boolean] Whether the protocol supports bidirectional communication.
|
16
19
|
def self.bidirectional?
|
17
20
|
true
|
18
21
|
end
|
19
22
|
|
23
|
+
# @returns [Boolean] Whether the protocol supports trailers.
|
20
24
|
def self.trailer?
|
21
25
|
true
|
22
26
|
end
|
23
27
|
|
24
|
-
|
28
|
+
# Create a client for an outbound connection.
|
29
|
+
#
|
30
|
+
# @parameter peer [IO] The peer to communicate with.
|
31
|
+
# @parameter options [Hash] Options to pass to the client instance.
|
32
|
+
def self.client(peer, **options)
|
25
33
|
stream = ::IO::Stream(peer)
|
26
34
|
|
27
|
-
return HTTP1::Client.new(stream, VERSION)
|
35
|
+
return HTTP1::Client.new(stream, VERSION, **options)
|
28
36
|
end
|
29
37
|
|
30
|
-
|
38
|
+
# Create a server for an inbound connection.
|
39
|
+
#
|
40
|
+
# @parameter peer [IO] The peer to communicate with.
|
41
|
+
# @parameter options [Hash] Options to pass to the server instance.
|
42
|
+
def self.server(peer, **options)
|
31
43
|
stream = ::IO::Stream(peer)
|
32
44
|
|
33
|
-
return HTTP1::Server.new(stream, VERSION)
|
45
|
+
return HTTP1::Server.new(stream, VERSION, **options)
|
34
46
|
end
|
35
47
|
|
48
|
+
# @returns [Array] The names of the supported protocol.
|
36
49
|
def self.names
|
37
50
|
["http/1.1"]
|
38
51
|
end
|
@@ -26,10 +26,9 @@ module Async
|
|
26
26
|
TRAILER = "trailer".freeze
|
27
27
|
|
28
28
|
module Connection
|
29
|
-
def initialize(
|
29
|
+
def initialize(...)
|
30
30
|
super
|
31
31
|
|
32
|
-
@count = 0
|
33
32
|
@reader = nil
|
34
33
|
|
35
34
|
# Writing multiple frames at the same time can cause odd problems if frames are only partially written. So we use a semaphore to ensure frames are written in their entirety.
|
@@ -41,7 +40,7 @@ module Async
|
|
41
40
|
end
|
42
41
|
|
43
42
|
def to_s
|
44
|
-
"\#<#{self.class} #{@
|
43
|
+
"\#<#{self.class} #{@streams.count} active streams>"
|
45
44
|
end
|
46
45
|
|
47
46
|
def as_json(...)
|
@@ -4,6 +4,8 @@
|
|
4
4
|
# Copyright, 2018-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2024, by Thomas Morgan.
|
6
6
|
|
7
|
+
require_relative "configurable"
|
8
|
+
|
7
9
|
require_relative "http2/client"
|
8
10
|
require_relative "http2/server"
|
9
11
|
|
@@ -13,16 +15,21 @@ module Async
|
|
13
15
|
module HTTP
|
14
16
|
module Protocol
|
15
17
|
module HTTP2
|
18
|
+
extend Configurable
|
19
|
+
|
16
20
|
VERSION = "HTTP/2"
|
17
21
|
|
22
|
+
# @returns [Boolean] Whether the protocol supports bidirectional communication.
|
18
23
|
def self.bidirectional?
|
19
24
|
true
|
20
25
|
end
|
21
26
|
|
27
|
+
# @returns [Boolean] Whether the protocol supports trailers.
|
22
28
|
def self.trailer?
|
23
29
|
true
|
24
30
|
end
|
25
31
|
|
32
|
+
# The default settings for the client.
|
26
33
|
CLIENT_SETTINGS = {
|
27
34
|
::Protocol::HTTP2::Settings::ENABLE_PUSH => 0,
|
28
35
|
::Protocol::HTTP2::Settings::MAXIMUM_FRAME_SIZE => 0x100000,
|
@@ -30,6 +37,7 @@ module Async
|
|
30
37
|
::Protocol::HTTP2::Settings::NO_RFC7540_PRIORITIES => 1,
|
31
38
|
}
|
32
39
|
|
40
|
+
# The default settings for the server.
|
33
41
|
SERVER_SETTINGS = {
|
34
42
|
# We choose a lower maximum concurrent streams to avoid overloading a single connection/thread.
|
35
43
|
::Protocol::HTTP2::Settings::MAXIMUM_CONCURRENT_STREAMS => 128,
|
@@ -39,7 +47,11 @@ module Async
|
|
39
47
|
::Protocol::HTTP2::Settings::NO_RFC7540_PRIORITIES => 1,
|
40
48
|
}
|
41
49
|
|
42
|
-
|
50
|
+
# Create a client for an outbound connection.
|
51
|
+
#
|
52
|
+
# @parameter peer [IO] The peer to communicate with.
|
53
|
+
# @parameter options [Hash] Options to pass to the client instance.
|
54
|
+
def self.client(peer, settings: CLIENT_SETTINGS)
|
43
55
|
stream = ::IO::Stream(peer)
|
44
56
|
client = Client.new(stream)
|
45
57
|
|
@@ -49,7 +61,11 @@ module Async
|
|
49
61
|
return client
|
50
62
|
end
|
51
63
|
|
52
|
-
|
64
|
+
# Create a server for an inbound connection.
|
65
|
+
#
|
66
|
+
# @parameter peer [IO] The peer to communicate with.
|
67
|
+
# @parameter options [Hash] Options to pass to the server instance.
|
68
|
+
def self.server(peer, settings: SERVER_SETTINGS)
|
53
69
|
stream = ::IO::Stream(peer)
|
54
70
|
server = Server.new(stream)
|
55
71
|
|
@@ -59,6 +75,7 @@ module Async
|
|
59
75
|
return server
|
60
76
|
end
|
61
77
|
|
78
|
+
# @returns [Array] The names of the supported protocol.
|
62
79
|
def self.names
|
63
80
|
["h2"]
|
64
81
|
end
|
@@ -4,16 +4,18 @@
|
|
4
4
|
# Copyright, 2018-2024, by Samuel Williams.
|
5
5
|
# Copyright, 2019, by Brian Morearty.
|
6
6
|
|
7
|
+
require_relative "defaulton"
|
8
|
+
|
7
9
|
require_relative "http10"
|
8
10
|
require_relative "http11"
|
9
|
-
|
10
11
|
require_relative "http2"
|
11
12
|
|
12
13
|
module Async
|
13
14
|
module HTTP
|
14
15
|
module Protocol
|
15
16
|
# A server that supports both HTTP1.0 and HTTP1.1 semantics by detecting the version of the request.
|
16
|
-
|
17
|
+
class HTTPS
|
18
|
+
# The protocol classes for each supported protocol.
|
17
19
|
HANDLERS = {
|
18
20
|
"h2" => HTTP2,
|
19
21
|
"http/1.1" => HTTP11,
|
@@ -21,11 +23,27 @@ module Async
|
|
21
23
|
nil => HTTP11,
|
22
24
|
}
|
23
25
|
|
24
|
-
def
|
26
|
+
def initialize(handlers = HANDLERS, **options)
|
27
|
+
@handlers = handlers
|
28
|
+
@options = options
|
29
|
+
end
|
30
|
+
|
31
|
+
def add(name, protocol, **options)
|
32
|
+
@handlers[name] = protocol
|
33
|
+
@options[protocol] = options
|
34
|
+
end
|
35
|
+
|
36
|
+
# Determine the protocol of the peer and return the appropriate protocol class.
|
37
|
+
#
|
38
|
+
# Use TLS Application Layer Protocol Negotiation (ALPN) to determine the protocol.
|
39
|
+
#
|
40
|
+
# @parameter peer [IO] The peer to communicate with.
|
41
|
+
# @returns [Class] The protocol class to use.
|
42
|
+
def protocol_for(peer)
|
25
43
|
# alpn_protocol is only available if openssl v1.0.2+
|
26
44
|
name = peer.alpn_protocol
|
27
45
|
|
28
|
-
Console.
|
46
|
+
Console.debug(self) {"Negotiating protocol #{name.inspect}..."}
|
29
47
|
|
30
48
|
if protocol = HANDLERS[name]
|
31
49
|
return protocol
|
@@ -34,18 +52,34 @@ module Async
|
|
34
52
|
end
|
35
53
|
end
|
36
54
|
|
37
|
-
|
38
|
-
|
55
|
+
# Create a client for an outbound connection.
|
56
|
+
#
|
57
|
+
# @parameter peer [IO] The peer to communicate with.
|
58
|
+
# @parameter options [Hash] Options to pass to the client instance.
|
59
|
+
def client(peer, **options)
|
60
|
+
protocol = protocol_for(peer)
|
61
|
+
options = options[protocol] || {}
|
62
|
+
|
63
|
+
protocol.client(peer, **options)
|
39
64
|
end
|
40
65
|
|
41
|
-
|
42
|
-
|
66
|
+
# Create a server for an inbound connection.
|
67
|
+
#
|
68
|
+
# @parameter peer [IO] The peer to communicate with.
|
69
|
+
# @parameter options [Hash] Options to pass to the server instance.
|
70
|
+
def server(peer, **options)
|
71
|
+
protocol = protocol_for(peer)
|
72
|
+
options = options[protocol] || {}
|
73
|
+
|
74
|
+
protocol.server(peer, **options)
|
43
75
|
end
|
44
76
|
|
45
|
-
#
|
46
|
-
def
|
47
|
-
|
77
|
+
# @returns [Array] The names of the supported protocol, used for Application Layer Protocol Negotiation (ALPN).
|
78
|
+
def names
|
79
|
+
@handlers.keys.compact
|
48
80
|
end
|
81
|
+
|
82
|
+
extend Defaulton
|
49
83
|
end
|
50
84
|
end
|
51
85
|
end
|
data/lib/async/http/server.rb
CHANGED
@@ -45,14 +45,14 @@ module Async
|
|
45
45
|
def accept(peer, address, task: Task.current)
|
46
46
|
connection = @protocol.server(peer)
|
47
47
|
|
48
|
-
Console.
|
48
|
+
Console.debug(self) {"Incoming connnection from #{address.inspect} to #{@protocol}"}
|
49
49
|
|
50
50
|
connection.each do |request|
|
51
51
|
# We set the default scheme unless it was otherwise specified.
|
52
52
|
# https://tools.ietf.org/html/rfc7230#section-5.5
|
53
53
|
request.scheme ||= self.scheme
|
54
54
|
|
55
|
-
# Console.
|
55
|
+
# Console.debug(self) {"Incoming request from #{address.inspect}: #{request.method} #{request.path}"}
|
56
56
|
|
57
57
|
# If this returns nil, we assume that the connection has been hijacked.
|
58
58
|
self.call(request)
|
data/lib/async/http/version.rb
CHANGED
data/readme.md
CHANGED
@@ -16,6 +16,10 @@ 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.88.0
|
20
|
+
|
21
|
+
- [Support custom protocols with options](https://socketry.github.io/async-http/releases/index#support-custom-protocols-with-options)
|
22
|
+
|
19
23
|
### v0.87.0
|
20
24
|
|
21
25
|
- [Unify HTTP/1 and HTTP/2 `CONNECT` semantics](https://socketry.github.io/async-http/releases/index#unify-http/1-and-http/2-connect-semantics)
|
@@ -55,10 +59,6 @@ Please see the [project releases](https://socketry.github.io/async-http/releases
|
|
55
59
|
|
56
60
|
- [`Async::HTTP::Internet` accepts keyword arguments](https://socketry.github.io/async-http/releases/index#async::http::internet-accepts-keyword-arguments)
|
57
61
|
|
58
|
-
### v0.73.0
|
59
|
-
|
60
|
-
- [Update support for `interim_response`](https://socketry.github.io/async-http/releases/index#update-support-for-interim_response)
|
61
|
-
|
62
62
|
## See Also
|
63
63
|
|
64
64
|
- [benchmark-http](https://github.com/socketry/benchmark-http) — A benchmarking tool to report on web server concurrency.
|
data/releases.md
CHANGED
@@ -1,5 +1,62 @@
|
|
1
1
|
# Releases
|
2
2
|
|
3
|
+
## v0.88.0
|
4
|
+
|
5
|
+
### Support custom protocols with options
|
6
|
+
|
7
|
+
{ruby Async::HTTP::Protocol} contains classes for specific protocols, e.g. {ruby Async::HTTP::Protocol::HTTP1} and {ruby Async::HTTP::Protocol::HTTP2}. It also contains classes for aggregating protocols, e.g. {ruby Async::HTTP::Protocol::HTTP} and {ruby Async::HTTP::Protocol::HTTPS}. They serve as factories for creating client and server instances.
|
8
|
+
|
9
|
+
These classes are now configurable with various options, which are passed as keyword arguments to the relevant connection classes. For example, to configure an HTTP/1.1 protocol without keep-alive:
|
10
|
+
|
11
|
+
``` ruby
|
12
|
+
protocol = Async::HTTP::Protocol::HTTP1.new(persistent: false, maximum_line_length: 32)
|
13
|
+
endpoint = Async::HTTP::Endpoint.parse("http://localhost:9292", protocol: protocol)
|
14
|
+
server = Async::HTTP::Server.for(endpoint) do |request|
|
15
|
+
Protocol::HTTP::Response[200, {}, ["Hello, world"]]
|
16
|
+
end.run
|
17
|
+
```
|
18
|
+
|
19
|
+
Making a request to the server will now close the connection after the response is received:
|
20
|
+
|
21
|
+
> curl -v http://localhost:9292
|
22
|
+
* Host localhost:9292 was resolved.
|
23
|
+
* IPv6: ::1
|
24
|
+
* IPv4: 127.0.0.1
|
25
|
+
* Trying [::1]:9292...
|
26
|
+
* Connected to localhost (::1) port 9292
|
27
|
+
* using HTTP/1.x
|
28
|
+
> GET / HTTP/1.1
|
29
|
+
> Host: localhost:9292
|
30
|
+
> User-Agent: curl/8.12.1
|
31
|
+
> Accept: */*
|
32
|
+
>
|
33
|
+
* Request completely sent off
|
34
|
+
< HTTP/1.1 200 OK
|
35
|
+
< connection: close
|
36
|
+
< content-length: 12
|
37
|
+
<
|
38
|
+
* shutting down connection #0
|
39
|
+
Hello, world
|
40
|
+
|
41
|
+
In addition, any line longer than 32 bytes will be rejected:
|
42
|
+
|
43
|
+
curl -v http://localhost:9292/012345678901234567890123456789012
|
44
|
+
* Host localhost:9292 was resolved.
|
45
|
+
* IPv6: ::1
|
46
|
+
* IPv4: 127.0.0.1
|
47
|
+
* Trying [::1]:9292...
|
48
|
+
* Connected to localhost (::1) port 9292
|
49
|
+
* using HTTP/1.x
|
50
|
+
> GET /012345678901234567890123456789012 HTTP/1.1
|
51
|
+
> Host: localhost:9292
|
52
|
+
> User-Agent: curl/8.12.1
|
53
|
+
> Accept: */*
|
54
|
+
>
|
55
|
+
* Request completely sent off
|
56
|
+
* Empty reply from server
|
57
|
+
* shutting down connection #0
|
58
|
+
curl: (52) Empty reply from server
|
59
|
+
|
3
60
|
## v0.87.0
|
4
61
|
|
5
62
|
### Unify HTTP/1 and HTTP/2 `CONNECT` semantics
|
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.88.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -57,7 +57,7 @@ cert_chain:
|
|
57
57
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
58
58
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
59
59
|
-----END CERTIFICATE-----
|
60
|
-
date: 2025-
|
60
|
+
date: 2025-03-13 00:00:00.000000000 Z
|
61
61
|
dependencies:
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
63
|
name: async
|
@@ -204,6 +204,8 @@ files:
|
|
204
204
|
- lib/async/http/mock.rb
|
205
205
|
- lib/async/http/mock/endpoint.rb
|
206
206
|
- lib/async/http/protocol.rb
|
207
|
+
- lib/async/http/protocol/configurable.rb
|
208
|
+
- lib/async/http/protocol/defaulton.rb
|
207
209
|
- lib/async/http/protocol/http.rb
|
208
210
|
- lib/async/http/protocol/http1.rb
|
209
211
|
- lib/async/http/protocol/http1/client.rb
|
metadata.gz.sig
CHANGED
Binary file
|