Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auto-detection of HTTP1 vs HTTP2 for inbound http:// connections #128

Merged
merged 3 commits into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/async/http/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
require 'io/endpoint/host_endpoint'
require 'io/endpoint/ssl_endpoint'

require_relative 'protocol/http1'
require_relative 'protocol/http'
require_relative 'protocol/https'

module Async
Expand Down Expand Up @@ -84,7 +84,7 @@ def protocol
if secure?
Protocol::HTTPS
else
Protocol::HTTP1
Protocol::HTTP
end
end
end
Expand Down
55 changes: 55 additions & 0 deletions lib/async/http/protocol/http.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2023, by Thomas Morgan.

require_relative 'http1'
require_relative 'http2'

module Async
module HTTP
module Protocol
# HTTP is an http:// server that auto-selects HTTP/1.1 or HTTP/2 by detecting the HTTP/2
# connection preface.
module HTTP
HTTP2_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
HTTP2_PREFACE_SIZE = HTTP2_PREFACE.bytesize

def self.protocol_for(stream)
# Detect HTTP/2 connection preface
# https://www.rfc-editor.org/rfc/rfc9113.html#section-3.4
preface = stream.peek do |read_buffer|
if read_buffer.bytesize >= HTTP2_PREFACE_SIZE
break read_buffer[0, HTTP2_PREFACE_SIZE]
elsif read_buffer.bytesize > 0
# If partial read_buffer already doesn't match, no need to wait for more bytes.
break read_buffer unless HTTP2_PREFACE[read_buffer]
end
end

if preface == HTTP2_PREFACE
HTTP2
else
HTTP1
end
end

# Only inbound connections can detect HTTP1 vs HTTP2 for http://.
# Outbound connections default to HTTP1.
def self.client(peer, **options)
HTTP1.client(peer, **options)
end

def self.server(peer, **options)
stream = ::IO::Stream(peer)

return protocol_for(stream).server(stream, **options)
end

def self.names
["h2", "http/1.1", "http/1.0"]
end
end
end
end
end
7 changes: 4 additions & 3 deletions lib/async/http/protocol/http1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

# Released under the MIT License.
# Copyright, 2017-2024, by Samuel Williams.
# Copyright, 2023, by Thomas Morgan.

require_relative 'http1/client'
require_relative 'http1/server'

require 'io/stream/buffered'
require 'io/stream'

module Async
module HTTP
Expand All @@ -23,13 +24,13 @@ def self.trailer?
end

def self.client(peer)
stream = ::IO::Stream::Buffered.wrap(peer)
stream = ::IO::Stream(peer)

return HTTP1::Client.new(stream, VERSION)
end

def self.server(peer)
stream = ::IO::Stream::Buffered.wrap(peer)
stream = ::IO::Stream(peer)

return HTTP1::Server.new(stream, VERSION)
end
Expand Down
5 changes: 3 additions & 2 deletions lib/async/http/protocol/http10.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# Released under the MIT License.
# Copyright, 2017-2024, by Samuel Williams.
# Copyright, 2023, by Thomas Morgan.

require_relative 'http1'

Expand All @@ -20,13 +21,13 @@ def self.trailer?
end

def self.client(peer)
stream = ::IO::Stream::Buffered.wrap(peer)
stream = ::IO::Stream(peer)

return HTTP1::Client.new(stream, VERSION)
end

def self.server(peer)
stream = ::IO::Stream::Buffered.wrap(peer)
stream = ::IO::Stream(peer)

return HTTP1::Server.new(stream, VERSION)
end
Expand Down
5 changes: 3 additions & 2 deletions lib/async/http/protocol/http11.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Released under the MIT License.
# Copyright, 2017-2024, by Samuel Williams.
# Copyright, 2018, by Janko Marohnić.
# Copyright, 2023, by Thomas Morgan.

require_relative 'http1'

Expand All @@ -21,13 +22,13 @@ def self.trailer?
end

def self.client(peer)
stream = ::IO::Stream::Buffered.wrap(peer)
stream = ::IO::Stream(peer)

return HTTP1::Client.new(stream, VERSION)
end

def self.server(peer)
stream = ::IO::Stream::Buffered.wrap(peer)
stream = ::IO::Stream(peer)

return HTTP1::Server.new(stream, VERSION)
end
Expand Down
7 changes: 4 additions & 3 deletions lib/async/http/protocol/http2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

# Released under the MIT License.
# Copyright, 2018-2024, by Samuel Williams.
# Copyright, 2023, by Thomas Morgan.

require_relative 'http2/client'
require_relative 'http2/server'

require 'io/stream/buffered'
require 'io/stream'

module Async
module HTTP
Expand Down Expand Up @@ -37,7 +38,7 @@ def self.trailer?
}

def self.client(peer, settings = CLIENT_SETTINGS)
stream = ::IO::Stream::Buffered.wrap(peer)
stream = ::IO::Stream(peer)
client = Client.new(stream)

client.send_connection_preface(settings)
Expand All @@ -47,7 +48,7 @@ def self.client(peer, settings = CLIENT_SETTINGS)
end

def self.server(peer, settings = SERVER_SETTINGS)
stream = ::IO::Stream::Buffered.wrap(peer)
stream = ::IO::Stream(peer)
server = Server.new(stream)

server.read_connection_preface(settings)
Expand Down
2 changes: 1 addition & 1 deletion test/async/http/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@

describe Async::HTTP::Endpoint.parse("http://www.google.com/search") do
it "should select the correct protocol" do
expect(subject.protocol).to be == Async::HTTP::Protocol::HTTP1
expect(subject.protocol).to be == Async::HTTP::Protocol::HTTP
end

it "should parse the correct hostname" do
Expand Down
37 changes: 37 additions & 0 deletions test/async/http/protocol/http.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2023, by Thomas Morgan.

require 'async/http/protocol/http'
require 'async/http/a_protocol'

describe Async::HTTP::Protocol::HTTP do
with 'server' do
include Sus::Fixtures::Async::HTTP::ServerContext
let(:protocol) {subject}

with 'http11 client' do
it 'should make a successful request' do
response = client.get('/')
expect(response).to be(:success?)
expect(response.version).to be == 'HTTP/1.1'
response.read
end
end

with 'http2 client' do
def make_client(endpoint, **options)
options[:protocol] = Async::HTTP::Protocol::HTTP2
super
end

it 'should make a successful request' do
response = client.get('/')
expect(response).to be(:success?)
expect(response.version).to be == 'HTTP/2'
response.read
end
end
end
end
Loading