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 5, 2023
1 parent e265f3f commit 0a8bc3c
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 4 deletions.
56 changes: 56 additions & 0 deletions lib/async/http/protocol/http.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 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.
# This detection requires a minimum number of bytes and is reliable for HTTP/1.1 and HTTP/2.
# However, it can fail on HTTP/1.0 if the request is too small, such as path == '/' and no
# headers. If you control the client (like a monitoring script), add a dummy header
# or query value. Otherwise, use Async::HTTP::Protocol::HTTP1 instead.
# Using a timeout on the Endpoint is strongly encouraged.
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]
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
30 changes: 30 additions & 0 deletions spec/async/http/protocol/http_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

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

require 'async/http/protocol/http'
require_relative '../server_context'

RSpec.describe Async::HTTP::Protocol::HTTP do
include_context Async::HTTP::Server

context 'http11 client' do
it "should make a successful request" do |example|
response = client.get("/")
expect(response).to be_success
expect(response.version).to be == 'HTTP/1.1'
end
end

context 'http2 client' do
let(:client_endpoint) {endpoint.with(protocol: Async::HTTP::Protocol::HTTP2)}

it "should make a successful request" do |example|
response = client.get("/")
expect(response).to be_success
expect(response.version).to be == 'HTTP/2'
response.read
end
end
end

0 comments on commit 0a8bc3c

Please sign in to comment.