Skip to content

Commit

Permalink
add Async::HTTP::Protocol::HTTP to auto-detect h1,h2 for inbound http…
Browse files Browse the repository at this point in the history
…:// connections
  • Loading branch information
zarqman committed Sep 12, 2023
1 parent b49f02a commit b7e2386
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 4 deletions.
53 changes: 53 additions & 0 deletions lib/async/http/protocol/http.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 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, **kwargs)
HTTP1.client(peer, **kwargs)
end

def self.server(peer, **kwargs)
stream = IO::Stream.new(peer, sync: true)
protocol_for(stream).server(stream, **kwargs)
end

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

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

require_relative 'http1/client'
require_relative 'http1/server'
Expand All @@ -27,7 +28,7 @@ def self.client(peer)
end

def self.server(peer)
stream = IO::Stream.new(peer, sync: true)
stream = peer.is_a?(IO::Stream) ? peer : IO::Stream.new(peer, sync: true)

return HTTP1::Server.new(stream, VERSION)
end
Expand Down
3 changes: 2 additions & 1 deletion 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-2023, by Samuel Williams.
# Copyright, 2023, by Thomas Morgan.

require_relative 'http1'

Expand All @@ -26,7 +27,7 @@ def self.client(peer)
end

def self.server(peer)
stream = IO::Stream.new(peer, sync: true)
stream = peer.is_a?(IO::Stream) ? peer : IO::Stream.new(peer, sync: true)

return HTTP1::Server.new(stream, VERSION)
end
Expand Down
3 changes: 2 additions & 1 deletion 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-2023, by Samuel Williams.
# Copyright, 2018, by Janko Marohnić.
# Copyright, 2023, by Thomas Morgan.

require_relative 'http1'

Expand All @@ -27,7 +28,7 @@ def self.client(peer)
end

def self.server(peer)
stream = IO::Stream.new(peer, sync: true)
stream = peer.is_a?(IO::Stream) ? peer : IO::Stream.new(peer, sync: true)

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

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

require_relative 'http2/client'
require_relative 'http2/server'
Expand Down Expand Up @@ -46,7 +47,7 @@ def self.client(peer, settings = CLIENT_SETTINGS)
end

def self.server(peer, settings = SERVER_SETTINGS)
stream = IO::Stream.new(peer, sync: true)
stream = peer.is_a?(IO::Stream) ? peer : IO::Stream.new(peer, sync: true)

server = Server.new(stream)

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

0 comments on commit b7e2386

Please sign in to comment.