lib/async/http/protocol/http.rb



# frozen_string_literal: true

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

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