mirror of
https://github.com/HoneyryderChuck/httpx.git
synced 2025-08-10 00:01:27 -04:00
allow for oportunistic upgrades, such as the apache Upgrade: h2
this is achieved by a rework of the upgrade plugin, and the addition of an h2 upgrade plugin. The idea is the following: if a response carries an Upgrade header, and there's a handler for it, we should go for it. The difference is: * when the response is 101, this means that the negotiation must take place before the actual response comes in; * when the response is 200, upgrading means reconnecting to the channel, and assume the new protocol for subsequent requests only.
This commit is contained in:
parent
72a397b841
commit
a03e93e531
@ -472,14 +472,7 @@ module HTTPX
|
||||
remove_instance_variable(:@total_timeout)
|
||||
end
|
||||
|
||||
@io.close if @io
|
||||
@read_buffer.clear
|
||||
if @keep_alive_timer
|
||||
@keep_alive_timer.cancel
|
||||
remove_instance_variable(:@keep_alive_timer)
|
||||
end
|
||||
|
||||
remove_instance_variable(:@timeout) if defined?(@timeout)
|
||||
purge_after_closed
|
||||
when :already_open
|
||||
nextstate = :open
|
||||
send_pending
|
||||
@ -499,6 +492,17 @@ module HTTPX
|
||||
emit(:close)
|
||||
end
|
||||
|
||||
def purge_after_closed
|
||||
@io.close if @io
|
||||
@read_buffer.clear
|
||||
if @keep_alive_timer
|
||||
@keep_alive_timer.cancel
|
||||
remove_instance_variable(:@keep_alive_timer)
|
||||
end
|
||||
|
||||
remove_instance_variable(:@timeout) if defined?(@timeout)
|
||||
end
|
||||
|
||||
def handle_response
|
||||
@inflight -= 1
|
||||
return unless @inflight.zero?
|
||||
|
@ -5,23 +5,31 @@ module HTTPX
|
||||
module Upgrade
|
||||
extend Registry
|
||||
|
||||
def self.load_dependencies(klass)
|
||||
klass.plugin(:"upgrade/h2")
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def fetch_response(request, connections, options)
|
||||
response = super
|
||||
|
||||
if response && response.status == 101
|
||||
connection = find_connection(request, connections, options)
|
||||
connections << connection unless connections.include?(connection)
|
||||
if response && response.headers.key?("upgrade")
|
||||
|
||||
upgrade_protocol = (request.headers.get("upgrade") & response.headers.get("upgrade")).first
|
||||
upgrade_protocol = response.headers["upgrade"].split(/ *, */).first
|
||||
|
||||
return response unless upgrade_protocol && Upgrade.registry.key?(upgrade_protocol)
|
||||
|
||||
protocol_handler = Upgrade.registry(upgrade_protocol)
|
||||
|
||||
return response unless protocol_handler
|
||||
|
||||
log { "upgrading to #{upgrade_protocol}..." }
|
||||
connection = find_connection(request, connections, options)
|
||||
connections << connection unless connections.include?(connection)
|
||||
|
||||
# do not upgrade already upgraded connections
|
||||
return if connection.upgrade_protocol == upgrade_protocol
|
||||
|
||||
# TODO: exit it connection already upgraded?
|
||||
protocol_handler.call(connection, request, response)
|
||||
|
||||
# keep in the loop if the server is switching, unless
|
||||
@ -29,6 +37,7 @@ module HTTPX
|
||||
# to terminante immediately
|
||||
return if response.status == 101 && !connection.hijacked
|
||||
end
|
||||
|
||||
response
|
||||
end
|
||||
|
||||
|
54
lib/httpx/plugins/upgrade/h2.rb
Normal file
54
lib/httpx/plugins/upgrade/h2.rb
Normal file
@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module HTTPX
|
||||
module Plugins
|
||||
#
|
||||
# This plugin adds support for upgrading a plaintext HTTP/1.1 connection to HTTP/2
|
||||
# (https://tools.ietf.org/html/rfc7540#section-3.2)
|
||||
#
|
||||
# https://gitlab.com/honeyryderchuck/httpx/wikis/Follow-Redirects
|
||||
#
|
||||
module H2
|
||||
class << self
|
||||
def configure(*)
|
||||
Upgrade.register "h2", self
|
||||
end
|
||||
|
||||
def call(connection, _request, _response)
|
||||
connection.upgrade_to_h2
|
||||
end
|
||||
end
|
||||
|
||||
module ConnectionMethods
|
||||
using URIExtensions
|
||||
|
||||
def upgrade_to_h2
|
||||
prev_parser = @parser
|
||||
|
||||
if prev_parser
|
||||
prev_parser.reset
|
||||
@inflight -= prev_parser.requests.size
|
||||
end
|
||||
|
||||
@parser = Connection::HTTP2.new(@write_buffer, @options)
|
||||
set_parser_callbacks(@parser)
|
||||
@upgrade_protocol = :h2
|
||||
|
||||
# what's happening here:
|
||||
# a deviation from the state machine is done to perform the actions when a
|
||||
# connection is closed, without transitioning, so the connection is kept in the pool.
|
||||
# the state is reset to initial, so that the socket reconnect works out of the box,
|
||||
# while the parser is already here.
|
||||
purge_after_closed
|
||||
transition(:idle)
|
||||
|
||||
prev_parser.requests.each do |req|
|
||||
req.transition(:idle)
|
||||
send(req)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
register_plugin(:"upgrade/h2", H2)
|
||||
end
|
||||
end
|
@ -29,6 +29,7 @@ class HTTPSTest < Minitest::Test
|
||||
include Plugins::Persistent unless RUBY_ENGINE == "jruby" || RUBY_VERSION < "2.3"
|
||||
include Plugins::Stream
|
||||
include Plugins::AWSAuthentication
|
||||
include Plugins::Upgrade
|
||||
|
||||
def test_connection_coalescing
|
||||
coalesced_origin = "https://#{ENV["HTTPBIN_COALESCING_HOST"]}"
|
||||
|
33
test/support/requests/plugins/upgrade.rb
Normal file
33
test/support/requests/plugins/upgrade.rb
Normal file
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Requests
|
||||
module Plugins
|
||||
module Upgrade
|
||||
def test_plugin_upgrade_h2
|
||||
http = HTTPX.plugin(SessionWithPool)
|
||||
|
||||
if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
|
||||
http = http.with(ssl: { alpn_protocols: %w[http/1.1] }) # disable alpn negotiation
|
||||
end
|
||||
|
||||
http.plugin(:upgrade).wrap do |session|
|
||||
uri = build_uri("/", "https://stadtschreiber.ruhr")
|
||||
|
||||
request = session.build_request(:get, uri)
|
||||
request2 = session.build_request(:get, uri)
|
||||
|
||||
response = session.request(request)
|
||||
verify_status(response, 200)
|
||||
assert response.version == "1.1", "first request should be in HTTP/1.1"
|
||||
response.close
|
||||
# verifies that first request was used to upgrade the connection
|
||||
verify_header(response.headers, "upgrade", "h2,h2c")
|
||||
response2 = session.request(request2)
|
||||
verify_status(response2, 200)
|
||||
assert response2.version == "2.0", "second request should already be in HTTP/2"
|
||||
response2.close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user