1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
# frozen_string_literal: true
module HTTPX
module Plugins
#
# This plugin adds support for upgrading a plaintext HTTP/1.1 connection to HTTP/2
# (https://datatracker.ietf.org/doc/html/rfc7540#section-3.2)
#
# https://gitlab.com/os85/httpx/wikis/Connection-Upgrade#h2c
#
module H2C
VALID_H2C_VERBS = %w[GET OPTIONS HEAD].freeze
class << self
def load_dependencies(klass)
klass.plugin(:upgrade)
end
def call(connection, request, response)
connection.upgrade_to_h2c(request, response)
end
def extra_options(options)
options.merge(
h2c_class: Class.new(options.http2_class) { include(H2CParser) },
max_concurrent_requests: 1,
upgrade_handlers: options.upgrade_handlers.merge("h2c" => self),
)
end
end
module OptionsMethods
def option_h2c_class(value)
value
end
end
module RequestMethods
def valid_h2c_verb?
VALID_H2C_VERBS.include?(@verb)
end
end
module ConnectionMethods
using URIExtensions
def initialize(*)
super
@h2c_handshake = false
end
def send(request)
return super if @h2c_handshake
return super unless request.valid_h2c_verb? && request.scheme == "http"
return super if @upgrade_protocol == "h2c"
@h2c_handshake = true
# build upgrade request
request.headers.add("connection", "upgrade")
request.headers.add("connection", "http2-settings")
request.headers["upgrade"] = "h2c"
request.headers["http2-settings"] = ::HTTP2::Client.settings_header(request.options.http2_settings)
super
end
def upgrade_to_h2c(request, response)
prev_parser = @parser
if prev_parser
prev_parser.reset
@inflight -= prev_parser.requests.size
end
@parser = request.options.h2c_class.new(@write_buffer, @options)
set_parser_callbacks(@parser)
@inflight += 1
@parser.upgrade(request, response)
@upgrade_protocol = "h2c"
prev_parser.requests.each do |req|
req.transition(:idle)
send(req)
end
end
private
def send_request_to_parser(request)
super
return unless request.headers["upgrade"] == "h2c" && parser.is_a?(Connection::HTTP1)
max_concurrent_requests = parser.max_concurrent_requests
return if max_concurrent_requests == 1
parser.max_concurrent_requests = 1
request.once(:response) do
parser.max_concurrent_requests = max_concurrent_requests
end
end
end
module H2CParser
def upgrade(request, response)
# skip checks, it is assumed that this is the first
# request in the connection
stream = @connection.upgrade
# on_settings
handle_stream(stream, request)
@streams[request] = stream
# clean up data left behind in the buffer, if the server started
# sending frames
data = response.read
@connection << data
end
end
end
register_plugin(:h2c, H2C)
end
end
|