|
| 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, **options) |
| 39 | + HTTP1.client(peer, **options) |
| 40 | + end |
| 41 | + |
| 42 | + def self.server(peer, **options) |
| 43 | + stream = IO::Stream.new(peer, sync: true) |
| 44 | + protocol_for(stream).server(stream, **options) |
| 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 |
0 commit comments