HoneyryderChuck 874bb6f1cf immprove h2c to not misuse max_concurrrent_requests
the h2c was relying heavily on rewriting connection options to only allow the first request to upgrade; this changes by instead changing the parser on first incoming request, so that if it's upgrade and contains the header, blocks the remainder until the upgrade is successful or not, and if it's not reverts the max concurrent requests anyway
2023-12-06 14:24:33 +00:00

109 lines
3.1 KiB
Ruby

# 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(max_concurrent_requests: 1, upgrade_handlers: options.upgrade_handlers.merge("h2c" => self))
end
end
module InstanceMethods
def send_requests(*requests)
upgrade_request, *remainder = requests
return super unless VALID_H2C_VERBS.include?(upgrade_request.verb) && upgrade_request.scheme == "http"
connection = pool.find_connection(upgrade_request.uri, upgrade_request.options)
return super if connection && connection.upgrade_protocol == "h2c"
# build upgrade request
upgrade_request.headers.add("connection", "upgrade")
upgrade_request.headers.add("connection", "http2-settings")
upgrade_request.headers["upgrade"] = "h2c"
upgrade_request.headers["http2-settings"] = HTTP2Next::Client.settings_header(upgrade_request.options.http2_settings)
super(upgrade_request, *remainder)
end
end
class H2CParser < Connection::HTTP2
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
module ConnectionMethods
using URIExtensions
def upgrade_to_h2c(request, response)
prev_parser = @parser
if prev_parser
prev_parser.reset
@inflight -= prev_parser.requests.size
end
@parser = H2CParser.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
end
register_plugin(:h2c, H2C)
end
end