From 40e700d3d85b372e64a1eac3c6661e766cf49423 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 9 Nov 2015 21:19:36 +0900 Subject: client: support HTTP/1.1 upgrade --- lib/plum/client.rb | 54 +++++++++++++++++-------------- lib/plum/client/client_session.rb | 4 ++- lib/plum/client/legacy_client_session.rb | 2 +- lib/plum/client/upgrade_client_session.rb | 46 ++++++++++++++++++++++++++ lib/plum/connection.rb | 29 +++++++++-------- lib/plum/rack/session.rb | 2 +- lib/plum/server/http_connection.rb | 8 ++--- 7 files changed, 100 insertions(+), 45 deletions(-) create mode 100644 lib/plum/client/upgrade_client_session.rb (limited to 'lib/plum') diff --git a/lib/plum/client.rb b/lib/plum/client.rb index cc255f4..b78e25c 100644 --- a/lib/plum/client.rb +++ b/lib/plum/client.rb @@ -2,7 +2,6 @@ module Plum class Client DEFAULT_CONFIG = { - tls: true, scheme: "https", verify_mode: OpenSSL::SSL::VERIFY_PEER, ssl_context: nil, @@ -31,7 +30,7 @@ module Plum @socket = host else @host = host - @port = port || (config[:tls] ? 443 : 80) + @port = port || (config[:scheme] == "https" ? 443 : 80) end @config = DEFAULT_CONFIG.merge(hostname: host).merge(config) @started = false @@ -131,35 +130,40 @@ module Plum } private + # @return [Boolean] http2 nego? + def _connect + @socket = TCPSocket.open(@host, @port) + + if @config[:scheme] == "https" + ctx = @config[:ssl_context] || new_ssl_ctx + @socket = OpenSSL::SSL::SSLSocket.new(@socket, ctx) + @socket.hostname = @config[:hostname] if @socket.respond_to?(:hostname=) + @socket.sync_close = true + @socket.connect + @socket.post_connection_check(@config[:hostname]) if ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE + + @socket.respond_to?(:alpn_protocol) && @socket.alpn_protocol == "h2" || + @socket.respond_to?(:npn_protocol) && @socket.npn_protocol == "h2" + end + end + def _start @started = true - http2 = @config[:http2] - unless @socket - @socket = TCPSocket.open(host, port) - if config[:tls] - ctx = @config[:ssl_context] || new_ssl_ctx - @socket = OpenSSL::SSL::SSLSocket.new(@socket, ctx) - @socket.hostname = @config[:hostname] if @socket.respond_to?(:hostname=) - @socket.sync_close = true - @socket.connect - @socket.post_connection_check(@config[:hostname]) if ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE - - if @socket.respond_to?(:alpn_protocol) - http2 = @socket.alpn_protocol == "h2" - elsif @socket.respond_to?(:npn_protocol) # TODO: remove - http2 = @socket.npn_protocol == "h2" - else - http2 = false - end - end - end + klass = @config[:http2] ? ClientSession : LegacyClientSession + nego = @socket || _connect - if http2 - @session = ClientSession.new(@socket, @config) + if @config[:http2] + if @config[:scheme] == "https" + klass = nego ? ClientSession : LegacyClientSession + else + klass = UpgradeClientSession + end else - @session = LegacyClientSession.new(@socket, @config) + klass = LegacyClientSession end + + @session = klass.new(@socket, @config) end def new_ssl_ctx diff --git a/lib/plum/client/client_session.rb b/lib/plum/client/client_session.rb index 2964769..495d099 100644 --- a/lib/plum/client/client_session.rb +++ b/lib/plum/client/client_session.rb @@ -7,6 +7,8 @@ module Plum initial_window_size: 2 ** 30, # TODO } + attr_reader :plum + def initialize(socket, config) @socket = socket @config = config @@ -17,7 +19,7 @@ module Plum end def succ - @plum << @socket.readpartial(1024) + @plum << @socket.readpartial(16384) rescue => e fail(e) end diff --git a/lib/plum/client/legacy_client_session.rb b/lib/plum/client/legacy_client_session.rb index f98e7bf..a30c237 100644 --- a/lib/plum/client/legacy_client_session.rb +++ b/lib/plum/client/legacy_client_session.rb @@ -15,7 +15,7 @@ module Plum end def succ - @parser << @socket.readpartial(1024) + @parser << @socket.readpartial(16384) rescue => e # including HTTP::Parser::Error fail(e) end diff --git a/lib/plum/client/upgrade_client_session.rb b/lib/plum/client/upgrade_client_session.rb new file mode 100644 index 0000000..c0e0d9e --- /dev/null +++ b/lib/plum/client/upgrade_client_session.rb @@ -0,0 +1,46 @@ +# -*- frozen-string-literal: true -*- +module Plum + # Try upgrade to HTTP/2 + class UpgradeClientSession + def initialize(socket, config) + prepare_session(socket, config) + end + + def succ + @session.succ + end + + def empty? + @session.empty? + end + + def close + @session.close + end + + def request(headers, body, options, &headers_cb) + @session.request(headers, body, options, &headers_cb) + end + + private + def prepare_session(socket, config) + lcs = LegacyClientSession.new(socket, config) + opt_res = lcs.request({ ":method" => "OPTIONS", + ":path" => "*", + "User-Agent" => config[:user_agent], + "Connection" => "Upgrade, HTTP2-Settings", + "Upgrade" => "h2c", + "HTTP2-Settings" => "" }, nil, {}) + lcs.succ until opt_res.finished? + + if opt_res.status == "101" + lcs.close + @session = ClientSession.new(socket, config) + @session.plum.stream(1).set_state(:half_closed_local) + else + @session = lcs + end + end + end +end + diff --git a/lib/plum/connection.rb b/lib/plum/connection.rb index c31836a..f158560 100644 --- a/lib/plum/connection.rb +++ b/lib/plum/connection.rb @@ -58,19 +58,9 @@ module Plum end alias << receive - private - def consume_buffer - while frame = Frame.parse!(@buffer) - callback(:frame, frame) - receive_frame(frame) - end - end - - def send_immediately(frame) - callback(:send_frame, frame) - @writer.call(frame.assemble) - end - + # Returns a Stream object with the specified ID. + # @param stream_id [Integer] the stream id + # @return [Stream] the stream def stream(stream_id) raise ArgumentError, "stream_id can't be 0" if stream_id == 0 @@ -92,6 +82,19 @@ module Plum stream end + private + def consume_buffer + while frame = Frame.parse!(@buffer) + callback(:frame, frame) + receive_frame(frame) + end + end + + def send_immediately(frame) + callback(:send_frame, frame) + @writer.call(frame.assemble) + end + def validate_received_frame(frame) if @state == :waiting_settings && frame.type != :settings raise RemoteConnectionError.new(:protocol_error) diff --git a/lib/plum/rack/session.rb b/lib/plum/rack/session.rb index d0aedc5..d9efb9d 100644 --- a/lib/plum/rack/session.rb +++ b/lib/plum/rack/session.rb @@ -26,7 +26,7 @@ module Plum def run begin while !@sock.closed? && !@sock.eof? - @plum << @sock.readpartial(1024) + @plum << @sock.readpartial(16384) end rescue Errno::EPIPE, Errno::ECONNRESET => e rescue StandardError => e diff --git a/lib/plum/server/http_connection.rb b/lib/plum/server/http_connection.rb index cb49a29..74b7ad8 100644 --- a/lib/plum/server/http_connection.rb +++ b/lib/plum/server/http_connection.rb @@ -59,10 +59,10 @@ module Plum process_first_request } - resp = "HTTP/1.1 101 Switching Protocols\r\n" - "Connection: Upgrade\r\n" - "Upgrade: h2c\r\n" - "Server: plum/#{Plum::VERSION}\r\n" + resp = "HTTP/1.1 101 Switching Protocols\r\n" + + "Connection: Upgrade\r\n" + + "Upgrade: h2c\r\n" + + "Server: plum/#{Plum::VERSION}\r\n" + "\r\n" @sock.write(resp) -- cgit v1.2.3