moved altsvc-specific connection behaviour to mixin

this mixin applies only for connections built via Session#build_altsvc_connection. This moves out logic which was always being called on the hot path for connections which hadn't been alt-svc enabled, which improves the Connection#match? bottleneck.
This commit is contained in:
HoneyryderChuck 2023-11-14 12:46:17 +00:00
parent a27f735eb8
commit cce68bcd98
9 changed files with 111 additions and 63 deletions

View File

@ -4,6 +4,56 @@ require "strscan"
module HTTPX
module AltSvc
# makes connections able to accept requests destined to primary service.
module ConnectionMixin
def send(request)
request.headers["alt-used"] = @origin.authority if @parser && !@write_buffer.full? && match_altsvcs?(request.uri)
super
end
def match?(uri, options)
return false if !used? && (@state == :closing || @state == :closed)
match_altsvcs?(uri) && match_altsvc_options?(uri, options)
end
private
# checks if this is connection is an alternative service of
# +uri+
def match_altsvcs?(uri)
@origins.any? { |origin| altsvc_match?(uri, origin) } ||
AltSvc.cached_altsvc(@origin).any? do |altsvc|
origin = altsvc["origin"]
altsvc_match?(origin, uri.origin)
end
end
def match_altsvc_options?(uri, options)
return @options == options unless @options.ssl.all? do |k, v|
v == (k == :hostname ? uri.host : options.ssl[k])
end
@options.options_equals?(options, Options::REQUEST_BODY_IVARS + %i[@ssl])
end
def altsvc_match?(uri, other_uri)
other_uri = URI(other_uri)
uri.origin == other_uri.origin || begin
case uri.scheme
when "h2"
(other_uri.scheme == "https" || other_uri.scheme == "h2") &&
uri.host == other_uri.host &&
uri.port == other_uri.port
else
false
end
end
end
end
@altsvc_mutex = Thread::Mutex.new
@altsvcs = Hash.new { |h, k| h[k] = [] }
@ -99,7 +149,10 @@ module HTTPX
end
def parse_altsvc_origin(alt_proto, alt_origin)
alt_scheme = parse_altsvc_scheme(alt_proto) or return
alt_scheme = parse_altsvc_scheme(alt_proto)
return unless alt_scheme
alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
URI.parse("#{alt_scheme}://#{alt_origin}")

View File

@ -95,15 +95,13 @@ module HTTPX
return false if exhausted?
(
(
@origins.include?(uri.origin) &&
# if there is more than one origin to match, it means that this connection
# was the result of coalescing. To prevent blind trust in the case where the
# origin came from an ORIGIN frame, we're going to verify the hostname with the
# SSL certificate
(@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host)))
) && @options == options
) || (match_altsvcs?(uri) && match_altsvc_options?(uri, options))
@origins.include?(uri.origin) &&
# if there is more than one origin to match, it means that this connection
# was the result of coalescing. To prevent blind trust in the case where the
# origin came from an ORIGIN frame, we're going to verify the hostname with the
# SSL certificate
(@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host)))
) && @options == options
end
def expired?
@ -167,24 +165,6 @@ module HTTPX
end
end
# checks if this is connection is an alternative service of
# +uri+
def match_altsvcs?(uri)
@origins.any? { |origin| uri.altsvc_match?(origin) } ||
AltSvc.cached_altsvc(@origin).any? do |altsvc|
origin = altsvc["origin"]
origin.altsvc_match?(uri.origin)
end
end
def match_altsvc_options?(uri, options)
return @options == options unless @options.ssl[:hostname] == uri.host
dup_options = @options.merge(ssl: { hostname: nil })
dup_options.ssl.delete(:hostname)
dup_options == options
end
def connecting?
@state == :idle
end
@ -252,8 +232,6 @@ module HTTPX
def send(request)
if @parser && !@write_buffer.full?
request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
if @response_received_at && @keep_alive_timeout &&
Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
# when pushing a request into an existing connection, we have to check whether there

View File

@ -54,21 +54,6 @@ module HTTPX
def origin
"#{scheme}://#{authority}"
end unless URI::HTTP.method_defined?(:origin)
def altsvc_match?(uri)
uri = URI.parse(uri)
origin == uri.origin || begin
case scheme
when "h2"
(uri.scheme == "https" || uri.scheme == "h2") &&
host == uri.host &&
(port || default_port) == (uri.port || uri.default_port)
else
false
end
end
end
end
end
end

View File

@ -203,9 +203,12 @@ module HTTPX
alt_options = options.merge(ssl: options.ssl.merge(hostname: URI(origin).host))
connection = pool.find_connection(alt_origin, alt_options) || build_connection(alt_origin, alt_options)
# advertised altsvc is the same origin being used, ignore
return if connection == existing_connection
connection.extend(AltSvc::ConnectionMixin) unless connection.is_a?(AltSvc::ConnectionMixin)
set_connection_callbacks(connection, connections, alt_options)
log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }

33
sig/altsvc.rbs Normal file
View File

@ -0,0 +1,33 @@
module HTTPX
module AltSvc
module ConnectionMixin
def send: (Request request) -> void
def match?: (URI::Generic uri, Options options) -> bool
private
def match_altsvcs?: (URI::Generic uri) -> bool
def match_altsvc_options?: (URI::Generic uri, Options options) -> bool
end
type altsvc_params = Hash[String, untyped]
def self?.cached_altsvc: (String origin) -> Array[altsvc_params]
def self?.cached_altsvc_set: (String origin, altsvc_params) -> void
def self?.lookup: (String origin, Integer | Float ttl) -> Array[altsvc_params]
def self?.emit: (Request request, response response) { (http_uri alt_origin, String origin, altsvc_params alt_params) -> void } -> void
def self?.parse: (String altsvc) { (http_uri alt_origin, altsvc_params alt_params) -> void } -> void
| (String altsvc) -> Enumerable[[http_uri, altsvc_params]]
def self?.parse_altsvc_scheme: (String alt_proto) -> String?
def self.parse_altsvc_origin: (string alt_proto, String alt_origin) -> http_uri?
end
end

View File

@ -44,25 +44,23 @@ module HTTPX
def addresses: () -> Array[ipaddr]?
def addresses=: (Array[ipaddr]) -> void
def addresses=: (Array[ipaddr] addresses) -> void
def send: (Request request) -> void
def match?: (URI::Generic uri, Options options) -> bool
def expired?: () -> boolish
def mergeable?: (Connection) -> bool
def mergeable?: (Connection connection) -> bool
def coalescable?: (Connection) -> bool
def coalescable?: (Connection connection) -> bool
def create_idle: (?Hash[Symbol, untyped] options) -> Connection
def merge: (Connection) -> void
def merge: (Connection connection) -> void
def purge_pending: () { (Request) -> void } -> void
def match_altsvcs?: (URI::Generic uri) -> bool
def match_altsvc_options?: (URI::Generic uri, Options options) -> bool
def purge_pending: () { (Request request) -> void } -> void
def connecting?: () -> bool
@ -78,8 +76,6 @@ module HTTPX
def reset: () -> void
def send: (Request) -> void
def timeout: () -> Numeric?
def idling: () -> void
@ -112,15 +108,15 @@ module HTTPX
def set_parser_callbacks: (HTTP1 | HTTP2 parser) -> void
def transition: (Symbol) -> void
def transition: (Symbol nextstate) -> void
def handle_transition: (Symbol) -> void
def handle_transition: (Symbol nextstate) -> void
def build_socket: (?Array[ipaddr]? addrs) -> (TCP | SSL | UNIX)
def on_error: (HTTPX::TimeoutError | Error | StandardError) -> void
def on_error: (HTTPX::TimeoutError | Error | StandardError error) -> void
def handle_error: (StandardError) -> void
def handle_error: (StandardError error) -> void
def purge_after_closed: () -> void

View File

@ -33,7 +33,7 @@ module HTTPX
def set_connection_callbacks: (Connection connection, Array[Connection] connections, Options options) -> void
def build_altsvc_connection: (Connection existing_connection, Array[Connection] connections, URI::Generic alt_origin, String origin, Hash[String, String] alt_params, Options options) -> Connection?
def build_altsvc_connection: (Connection existing_connection, Array[Connection] connections, URI::Generic alt_origin, String origin, Hash[String, String] alt_params, Options options) -> (Connection & AltSvc::ConnectionMixin)?
def build_requests: (verb, uri, options) -> Array[Request]
| (Array[[verb, uri, options]], options) -> Array[Request]

View File

@ -65,7 +65,7 @@ class AltSvcTest < Minitest::Test
req = Request.new("GET", "http://www.example-clear-cache.com/")
res = Response.new(req, 200, "2.0", { "alt-svc" => "clear" })
AltSvc.emit(req, res)
AltSvc.emit(req, res) {}
entries = AltSvc.cached_altsvc("http://www.example-clear-cache.com")
assert entries.empty?

View File

@ -6,7 +6,7 @@ module Requests
altsvc_host = ENV["HTTPBIN_ALTSVC_HOST"]
altsvc_origin = origin(altsvc_host)
HTTPX.wrap do |http|
HTTPX.plugin(SessionWithPool).wrap do |http|
altsvc_uri = build_uri("/get", altsvc_origin)
response = http.get(altsvc_uri)
verify_status(response, 200)