Skip to content

Commit 182bff8

Browse files
committed
Add configurable and defaultable concepts.
1 parent 21e0198 commit 182bff8

File tree

9 files changed

+157
-22
lines changed

9 files changed

+157
-22
lines changed
+43
Original file line numberDiff line numberDiff line change
@@ -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
+36
Original file line numberDiff line numberDiff line change
@@ -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+
module Defaultable
10+
def self.extended(base)
11+
base.const_set(:DEFAULT, base.new)
12+
end
13+
14+
# The default instance of the protocol.
15+
def default
16+
self::DEFAULT
17+
end
18+
19+
# Create a client for an outbound connection, using the default instance.
20+
def client(peer, **options)
21+
default.client(peer, **options)
22+
end
23+
24+
# Create a server for an inbound connection, using the default instance.
25+
def server(peer, **options)
26+
default.server(peer, **options)
27+
end
28+
29+
# @returns [Array] The names of the supported protocol, used for Application Layer Protocol Negotiation (ALPN), using the default instance.
30+
def names
31+
default.names
32+
end
33+
end
34+
end
35+
end
36+
end

lib/async/http/protocol/http.rb

+22-15
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,33 @@
44
# Copyright, 2024, by Thomas Morgan.
55
# Copyright, 2024, by Samuel Williams.
66

7+
require_relative "defaultable"
8+
79
require_relative "http1"
810
require_relative "http2"
911

1012
module Async
1113
module HTTP
1214
module Protocol
13-
# HTTP is an http:// server that auto-selects HTTP/1.1 or HTTP/2 by detecting the HTTP/2
14-
# connection preface.
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
1617
HTTP2_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
1718
HTTP2_PREFACE_SIZE = HTTP2_PREFACE.bytesize
1819

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+
1929
# Determine if the inbound connection is HTTP/1 or HTTP/2.
2030
#
2131
# @parameter stream [IO::Stream] The stream to detect the protocol for.
2232
# @returns [Class] The protocol class to use.
23-
def self.protocol_for(stream)
33+
def protocol_for(stream)
2434
# Detect HTTP/2 connection preface
2535
# https://www.rfc-editor.org/rfc/rfc9113.html#section-3.4
2636
preface = stream.peek do |read_buffer|
@@ -33,38 +43,35 @@ def self.protocol_for(stream)
3343
end
3444

3545
if preface == HTTP2_PREFACE
36-
HTTP2
46+
@http2
3747
else
38-
HTTP1
48+
@http1
3949
end
4050
end
4151

4252
# Create a client for an outbound connection. Defaults to HTTP/1 for plaintext connections.
4353
#
4454
# @parameter peer [IO] The peer to communicate with.
4555
# @parameter options [Hash] Options to pass to the protocol, keyed by protocol class.
46-
def self.client(peer, **options)
47-
options = options[protocol] || {}
56+
def client(peer, **options)
57+
options = options[@http1] || {}
4858

49-
HTTP1.client(peer, **options)
59+
@http1.client(peer, **options)
5060
end
5161

52-
# Create a server for an inbound connection. Able to detect HTTP1 vs HTTP2.
62+
# Create a server for an inbound connection. Able to detect HTTP1 and HTTP2.
5363
#
5464
# @parameter peer [IO] The peer to communicate with.
5565
# @parameter options [Hash] Options to pass to the protocol, keyed by protocol class.
56-
def self.server(peer, **options)
66+
def server(peer, **options)
5767
stream = ::IO::Stream(peer)
5868
protocol = protocol_for(stream)
5969
options = options[protocol] || {}
6070

6171
return protocol.server(stream, **options)
6272
end
6373

64-
# @returns [Array] The names of the supported protocols.
65-
def self.names
66-
["h2", "http/1.1", "http/1.0"]
67-
end
74+
extend Defaultable
6875
end
6976
end
7077
end

lib/async/http/protocol/http1.rb

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
# Copyright, 2017-2024, by Samuel Williams.
55
# Copyright, 2024, by Thomas Morgan.
66

7+
require_relative "configurable"
8+
79
require_relative "http1/client"
810
require_relative "http1/server"
911

@@ -13,6 +15,8 @@ module Async
1315
module HTTP
1416
module Protocol
1517
module HTTP1
18+
extend Configurable
19+
1620
VERSION = "HTTP/1.1"
1721

1822
# @returns [Boolean] Whether the protocol supports bidirectional communication.

lib/async/http/protocol/http10.rb

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ module Async
1010
module HTTP
1111
module Protocol
1212
module HTTP10
13+
extend Configurable
14+
1315
VERSION = "HTTP/1.0"
1416

1517
# @returns [Boolean] Whether the protocol supports bidirectional communication.

lib/async/http/protocol/http11.rb

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ module Async
1111
module HTTP
1212
module Protocol
1313
module HTTP11
14+
extend Configurable
15+
1416
VERSION = "HTTP/1.1"
1517

1618
# @returns [Boolean] Whether the protocol supports bidirectional communication.

lib/async/http/protocol/http2.rb

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
# Copyright, 2018-2024, by Samuel Williams.
55
# Copyright, 2024, by Thomas Morgan.
66

7+
require_relative "configurable"
8+
79
require_relative "http2/client"
810
require_relative "http2/server"
911

@@ -13,6 +15,8 @@ module Async
1315
module HTTP
1416
module Protocol
1517
module HTTP2
18+
extend Configurable
19+
1620
VERSION = "HTTP/2"
1721

1822
# @returns [Boolean] Whether the protocol supports bidirectional communication.

lib/async/http/protocol/https.rb

+20-7
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
# Copyright, 2018-2024, by Samuel Williams.
55
# Copyright, 2019, by Brian Morearty.
66

7+
require_relative "defaultable"
8+
79
require_relative "http10"
810
require_relative "http11"
9-
1011
require_relative "http2"
1112

1213
module Async
1314
module HTTP
1415
module Protocol
1516
# A server that supports both HTTP1.0 and HTTP1.1 semantics by detecting the version of the request.
16-
module HTTPS
17+
class HTTPS
1718
# The protocol classes for each supported protocol.
1819
HANDLERS = {
1920
"h2" => HTTP2,
@@ -22,13 +23,23 @@ module HTTPS
2223
nil => HTTP11,
2324
}
2425

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+
2536
# Determine the protocol of the peer and return the appropriate protocol class.
2637
#
2738
# Use TLS Application Layer Protocol Negotiation (ALPN) to determine the protocol.
2839
#
2940
# @parameter peer [IO] The peer to communicate with.
3041
# @returns [Class] The protocol class to use.
31-
def self.protocol_for(peer)
42+
def protocol_for(peer)
3243
# alpn_protocol is only available if openssl v1.0.2+
3344
name = peer.alpn_protocol
3445

@@ -45,7 +56,7 @@ def self.protocol_for(peer)
4556
#
4657
# @parameter peer [IO] The peer to communicate with.
4758
# @parameter options [Hash] Options to pass to the client instance.
48-
def self.client(peer, **options)
59+
def client(peer, **options)
4960
protocol = protocol_for(peer)
5061
options = options[protocol] || {}
5162

@@ -56,17 +67,19 @@ def self.client(peer, **options)
5667
#
5768
# @parameter peer [IO] The peer to communicate with.
5869
# @parameter options [Hash] Options to pass to the server instance.
59-
def self.server(peer, **options)
70+
def server(peer, **options)
6071
protocol = protocol_for(peer)
6172
options = options[protocol] || {}
6273

6374
protocol.server(peer, **options)
6475
end
6576

6677
# @returns [Array] The names of the supported protocol, used for Application Layer Protocol Negotiation (ALPN).
67-
def self.names
68-
HANDLERS.keys.compact
78+
def names
79+
@handlers.keys.compact
6980
end
81+
82+
extend Defaultable
7083
end
7184
end
7285
end

test/async/http/protocol/http1.rb

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2024, by Thomas Morgan.
5+
# Copyright, 2024, by Samuel Williams.
6+
7+
require "async/http/protocol/http"
8+
require "async/http/a_protocol"
9+
10+
describe Async::HTTP::Protocol::HTTP1 do
11+
with ".new" do
12+
it "can configure the protocol" do
13+
protocol = subject.new(
14+
persistent: false,
15+
maximum_line_length: 4096,
16+
)
17+
18+
expect(protocol.options).to have_keys(
19+
persistent: be == false,
20+
maximum_line_length: be == 4096,
21+
)
22+
end
23+
end
24+
end

0 commit comments

Comments
 (0)