ssl: support session resumption on reconnections with same session

when connections get reset due to max number of requests being reached,
the same TLS session is going to be reused, as long as it's valid.

This change is ported from the same feature in net-http, including [the
tls 1.3
improvements](ddf5c52b5f)
This commit is contained in:
HoneyryderChuck 2023-09-03 00:50:28 +01:00
parent f03d9bb648
commit ef2f0cc998
3 changed files with 41 additions and 8 deletions

View File

@ -42,7 +42,7 @@ module HTTPX
def_delegator :@write_buffer, :empty? def_delegator :@write_buffer, :empty?
attr_reader :type, :io, :origin, :origins, :state, :pending, :options attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session
attr_writer :timers attr_writer :timers
@ -138,6 +138,12 @@ module HTTPX
def merge(connection) def merge(connection)
@origins |= connection.instance_variable_get(:@origins) @origins |= connection.instance_variable_get(:@origins)
if connection.ssl_session
@ssl_session = connection.ssl_session
@io.session_new_cb do |sess|
@ssl_session = sess
end if @io
end
connection.purge_pending do |req| connection.purge_pending do |req|
send(req) send(req)
end end
@ -594,14 +600,21 @@ module HTTPX
end end
def build_socket(addrs = nil) def build_socket(addrs = nil)
transport_type = case @type case @type
when "tcp" then TCP when "tcp"
when "ssl" then SSL TCP.new(@origin, addrs, @options)
when "unix" then UNIX when "ssl"
else sock = SSL.new(@origin, addrs, @options)
raise Error, "unsupported transport (#{@type})" sock.ssl_session = @ssl_session
sock.session_new_cb do |sess|
@ssl_session = sess
end
sock
when "unix"
UNIX.new(@origin, addrs, @options)
else
raise Error, "unsupported transport (#{@type})"
end end
transport_type.new(@origin, addrs, @options)
end end
def on_error(error) def on_error(error)

View File

@ -15,6 +15,8 @@ module HTTPX
{}.freeze {}.freeze
end end
attr_writer :ssl_session
def initialize(_, _, options) def initialize(_, _, options)
super super
@ -39,6 +41,15 @@ module HTTPX
@verify_hostname = @ctx.verify_hostname @verify_hostname = @ctx.verify_hostname
end end
if OpenSSL::SSL::SSLContext.method_defined?(:session_new_cb=)
def session_new_cb(&pr)
@ctx.session_new_cb = proc { |_, sess| pr.call(sess) }
end
else
# session_new_cb not implemented under JRuby
def session_new_cb; end
end
def protocol def protocol
@io.alpn_protocol || super @io.alpn_protocol || super
rescue StandardError rescue StandardError
@ -81,6 +92,11 @@ module HTTPX
else else
@io.hostname = @sni_hostname @io.hostname = @sni_hostname
end end
if @ssl_session &&
Process.clock_gettime(Process::CLOCK_REALTIME) < (@ssl_session.time.to_f + @ssl_session.timeout)
puts "reusing session y'all: #{@ssl_session}"
@io.session = @ssl_session
end
@io.sync_close = true @io.sync_close = true
end end
try_ssl_connect try_ssl_connect

View File

@ -93,6 +93,10 @@ class HTTPSTest < Minitest::Test
connection_count = http.pool.connection_count connection_count = http.pool.connection_count
assert connection_count == 2, "expected to have 2 connections, instead have #{connection_count}" assert connection_count == 2, "expected to have 2 connections, instead have #{connection_count}"
assert http.connection_exausted, "expected 1 connnection to have exhausted" assert http.connection_exausted, "expected 1 connnection to have exhausted"
# ssl session ought to be reused
conn = http.pool.connections.first
assert conn.io.instance_variable_get(:@io).session_reused? unless RUBY_ENGINE == "jruby"
end end
end end