Skip to content

Commit cfa66d7

Browse files
zarqmanioquatix
authored andcommitted
add Async::HTTP::Protocol::HTTP to auto-detect h1,h2 for inbound http:// connections
1 parent fe9fcc4 commit cfa66d7

File tree

6 files changed

+98
-4
lines changed

6 files changed

+98
-4
lines changed

lib/async/http/protocol/http.rb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2023, by Thomas Morgan.
5+
6+
require_relative 'http1'
7+
require_relative 'http2'
8+
9+
module Async
10+
module HTTP
11+
module Protocol
12+
# HTTP is an http:// server that auto-selects HTTP/1.1 or HTTP/2 by detecting the HTTP/2
13+
# connection preface.
14+
module HTTP
15+
HTTP2_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
16+
HTTP2_PREFACE_SIZE = HTTP2_PREFACE.bytesize
17+
18+
def self.protocol_for(stream)
19+
# Detect HTTP/2 connection preface
20+
# https://www.rfc-editor.org/rfc/rfc9113.html#section-3.4
21+
preface = stream.peek do |read_buffer|
22+
if read_buffer.bytesize >= HTTP2_PREFACE_SIZE
23+
break read_buffer[0, HTTP2_PREFACE_SIZE]
24+
elsif read_buffer.bytesize > 0
25+
# If partial read_buffer already doesn't match, no need to wait for more bytes.
26+
break read_buffer unless HTTP2_PREFACE[read_buffer]
27+
end
28+
end
29+
if preface == HTTP2_PREFACE
30+
HTTP2
31+
else
32+
HTTP1
33+
end
34+
end
35+
36+
# Only inbound connections can detect HTTP1 vs HTTP2 for http://.
37+
# Outbound connections default to HTTP1.
38+
def self.client(peer, **kwargs)
39+
HTTP1.client(peer, **kwargs)
40+
end
41+
42+
def self.server(peer, **kwargs)
43+
stream = IO::Stream.new(peer, sync: true)
44+
protocol_for(stream).server(stream, **kwargs)
45+
end
46+
47+
def self.names
48+
["h2", "http/1.1", "http/1.0"]
49+
end
50+
end
51+
end
52+
end
53+
end

lib/async/http/protocol/http1.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# Released under the MIT License.
44
# Copyright, 2017-2023, by Samuel Williams.
5+
# Copyright, 2023, by Thomas Morgan.
56

67
require_relative 'http1/client'
78
require_relative 'http1/server'
@@ -27,7 +28,7 @@ def self.client(peer)
2728
end
2829

2930
def self.server(peer)
30-
stream = IO::Stream.new(peer, sync: true)
31+
stream = peer.is_a?(IO::Stream) ? peer : IO::Stream.new(peer, sync: true)
3132

3233
return HTTP1::Server.new(stream, VERSION)
3334
end

lib/async/http/protocol/http10.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# Released under the MIT License.
44
# Copyright, 2017-2023, by Samuel Williams.
5+
# Copyright, 2023, by Thomas Morgan.
56

67
require_relative 'http1'
78

@@ -26,7 +27,7 @@ def self.client(peer)
2627
end
2728

2829
def self.server(peer)
29-
stream = IO::Stream.new(peer, sync: true)
30+
stream = peer.is_a?(IO::Stream) ? peer : IO::Stream.new(peer, sync: true)
3031

3132
return HTTP1::Server.new(stream, VERSION)
3233
end

lib/async/http/protocol/http11.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Released under the MIT License.
44
# Copyright, 2017-2023, by Samuel Williams.
55
# Copyright, 2018, by Janko Marohnić.
6+
# Copyright, 2023, by Thomas Morgan.
67

78
require_relative 'http1'
89

@@ -27,7 +28,7 @@ def self.client(peer)
2728
end
2829

2930
def self.server(peer)
30-
stream = IO::Stream.new(peer, sync: true)
31+
stream = peer.is_a?(IO::Stream) ? peer : IO::Stream.new(peer, sync: true)
3132

3233
return HTTP1::Server.new(stream, VERSION)
3334
end

lib/async/http/protocol/http2.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# Released under the MIT License.
44
# Copyright, 2018-2023, by Samuel Williams.
5+
# Copyright, 2023, by Thomas Morgan.
56

67
require_relative 'http2/client'
78
require_relative 'http2/server'
@@ -46,7 +47,7 @@ def self.client(peer, settings = CLIENT_SETTINGS)
4647
end
4748

4849
def self.server(peer, settings = SERVER_SETTINGS)
49-
stream = IO::Stream.new(peer, sync: true)
50+
stream = peer.is_a?(IO::Stream) ? peer : IO::Stream.new(peer, sync: true)
5051

5152
server = Server.new(stream)
5253

test/async/http/protocol/http.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2023, by Thomas Morgan.
5+
6+
require 'async/http/protocol/http'
7+
require 'async/http/a_protocol'
8+
9+
describe Async::HTTP::Protocol::HTTP do
10+
with 'server' do
11+
include Sus::Fixtures::Async::HTTP::ServerContext
12+
let(:protocol) {subject}
13+
14+
with 'http11 client' do
15+
it 'should make a successful request' do
16+
response = client.get('/')
17+
expect(response).to be(:success?)
18+
expect(response.version).to be == 'HTTP/1.1'
19+
response.read
20+
end
21+
end
22+
23+
with 'http2 client' do
24+
def make_client(endpoint, **options)
25+
options[:protocol] = Async::HTTP::Protocol::HTTP2
26+
super
27+
end
28+
29+
it 'should make a successful request' do
30+
response = client.get('/')
31+
expect(response).to be(:success?)
32+
expect(response.version).to be == 'HTTP/2'
33+
response.read
34+
end
35+
end
36+
end
37+
end

0 commit comments

Comments
 (0)