diff options
Diffstat (limited to 'lib/plum')
-rw-r--r-- | lib/plum/connection.rb | 62 | ||||
-rw-r--r-- | lib/plum/hpack/encoder.rb | 4 | ||||
-rw-r--r-- | lib/plum/http_connection.rb | 78 | ||||
-rw-r--r-- | lib/plum/https_connection.rb | 2 |
4 files changed, 71 insertions, 75 deletions
diff --git a/lib/plum/connection.rb b/lib/plum/connection.rb index 2c2536a..2597790 100644 --- a/lib/plum/connection.rb +++ b/lib/plum/connection.rb @@ -55,9 +55,7 @@ module Plum return if new_data.empty? @buffer << new_data - if @state == :negotiation - negotiate! - end + negotiate! if @state == :negotiation if @state != :negotiation while frame = Frame.parse!(@buffer) @@ -65,6 +63,10 @@ module Plum receive_frame(frame) end end + rescue ConnectionError => e + callback(:connection_error, e) + goaway(e.http2_error_type) + close end alias << receive @@ -84,15 +86,15 @@ module Plum end def negotiate! - if CLIENT_CONNECTION_PREFACE.start_with?(@buffer.byteslice(0, 24)) - if @buffer.bytesize >= 24 - @buffer.byteshift(24) - @state = :waiting_settings - settings(@local_settings) - end - else + unless CLIENT_CONNECTION_PREFACE.start_with?(@buffer.byteslice(0, 24)) raise ConnectionError.new(:protocol_error) # (MAY) send GOAWAY. sending. end + + if @buffer.bytesize >= 24 + @buffer.byteshift(24) + @state = :waiting_settings + settings(@local_settings) + end end def new_stream(stream_id, **args) @@ -107,26 +109,27 @@ module Plum end def validate_received_frame(frame) - case @state - when :waiting_settings + if @state == :waiting_settings raise ConnectionError.new(:protocol_error) if frame.type != :settings - @state = :negotiated + @state = :open callback(:negotiated) - when :waiting_continuation + end + + if @state == :waiting_continuation if frame.type != :continuation || frame.stream_id != @continuation_id - raise Plum::ConnectionError.new(:protocol_error) + raise ConnectionError.new(:protocol_error) end if frame.flags.include?(:end_headers) @state = :open @continuation_id = nil end - else - if [:headers].include?(frame.type) - if !frame.flags.include?(:end_headers) - @state = :waiting_continuation - @continuation_id = frame.stream_id - end + end + + if [:headers].include?(frame.type) + if !frame.flags.include?(:end_headers) + @state = :waiting_continuation + @continuation_id = frame.stream_id end end end @@ -141,17 +144,11 @@ module Plum if @streams.key?(frame.stream_id) stream = @streams[frame.stream_id] else - if frame.stream_id.even? # stream started by client must have odd ID - raise Plum::ConnectionError.new(:protocol_error) - end + raise ConnectionError.new(:protocol_error) if frame.stream_id.even? # stream started by client must have odd ID stream = new_stream(frame.stream_id) end stream.receive_frame(frame) end - rescue ConnectionError => e - callback(:connection_error, e) - goaway(e.http2_error_type) - close end def receive_control_frame(frame) @@ -179,11 +176,12 @@ module Plum def receive_settings(frame, send_ack: true) if frame.flags.include?(:ack) raise ConnectionError.new(:frame_size_error) if frame.length != 0 + callback(:settings_ack) return + else + raise ConnectionError.new(:frame_size_error) if frame.length % 6 != 0 end - raise ConnectionError.new(:frame_size_error) if frame.length % 6 != 0 - old_remote_settings = @remote_settings.dup @remote_settings.merge!(frame.parse_settings) apply_remote_settings(old_remote_settings) @@ -202,10 +200,10 @@ module Plum raise Plum::ConnectionError.new(:frame_size_error) if frame.length != 8 if frame.flags.include?(:ack) - on(:ping_ack) + callback(:ping_ack) else - on(:ping) opaque_data = frame.payload + callback(:ping, opaque_data) send_immediately Frame.ping(:ack, opaque_data) end end diff --git a/lib/plum/hpack/encoder.rb b/lib/plum/hpack/encoder.rb index 11e4485..a1a09e8 100644 --- a/lib/plum/hpack/encoder.rb +++ b/lib/plum/hpack/encoder.rb @@ -13,8 +13,8 @@ module Plum def encode(headers) out = "" headers.each do |name, value| - name = name.to_s.force_encoding(Encoding::BINARY) - value = value.to_s.force_encoding(Encoding::BINARY) + name = name.to_s.b + value = value.to_s.b if index = search(name, value) out << encode_indexed(index) elsif index = search(name, nil) diff --git a/lib/plum/http_connection.rb b/lib/plum/http_connection.rb index e5e63f6..4ffea73 100644 --- a/lib/plum/http_connection.rb +++ b/lib/plum/http_connection.rb @@ -5,58 +5,56 @@ module Plum def initialize(io, local_settings = {}) require "http/parser" super + @_headers = nil + @_body = "" @_http_parser = setup_parser - @_upgrade_retry = false # After sent 426 (Upgrade Required) end private + def negotiate! + super + rescue ConnectionError + # Upgrade from HTTP/1.1 + offset = @_http_parser << @buffer + @buffer.byteshift(offset) + end + def setup_parser - headers = nil - body = "" parser = HTTP::Parser.new - parser.on_message_begin = proc { } - parser.on_headers_complete = proc {|_headers| headers = _headers } - parser.on_body = proc {|chunk| body << chunk } - parser.on_message_complete = proc do |env| - # Upgrade from HTTP/1.1 - heads = headers.map {|n, v| [n.downcase, v] }.to_h - connection = heads["connection"] || "" - upgrade = heads["upgrade"] || "" - settings = heads["http2-settings"] + parser.on_headers_complete = proc {|_headers| + @_headers = _headers.map {|n, v| [n.downcase, v] }.to_h + } + parser.on_body = proc {|chunk| @_body << chunk } + parser.on_message_complete = proc {|env| + connection = @_headers["connection"] || "" + upgrade = @_headers["upgrade"] || "" + settings = @_headers["http2-settings"] if (connection.split(", ").sort == ["Upgrade", "HTTP2-Settings"].sort && upgrade.split(", ").include?("h2c") && settings != nil) - respond_switching_protocol - self.on(:negotiated) { - _frame = Frame.new(type: :settings, stream_id: 0, payload: Base64.urlsafe_decode64(settings)) - receive_settings(_frame, send_ack: false) # HTTP2-Settings - process_first_request(parser, heads, body) - } + switch_protocol(settings) else respond_not_supported close end - end + } parser end - def negotiate! - begin - super - rescue ConnectionError - # Upgrade from HTTP/1.1 - offset = @_http_parser << @buffer - @buffer.byteshift(offset) - end - end + def switch_protocol(settings) + self.on(:negotiated) { + _frame = Frame.new(type: :settings, stream_id: 0, payload: Base64.urlsafe_decode64(settings)) + receive_settings(_frame, send_ack: false) # HTTP2-Settings + process_first_request + } - def respond_switching_protocol resp = "" resp << "HTTP/1.1 101 Switching Protocols\r\n" resp << "Connection: Upgrade\r\n" resp << "Upgrade: h2c\r\n" + resp << "Server: plum/#{Plum::VERSION}\r\n" resp << "\r\n" io.write(resp) @@ -69,23 +67,25 @@ module Plum resp << "HTTP/1.1 505 HTTP Version Not Supported\r\n" resp << "Content-Type: text/plain\r\n" resp << "Content-Length: #{data.bytesize}\r\n" + resp << "Server: plum/#{Plum::VERSION}\r\n" resp << "\r\n" resp << data io.write(resp) end - def process_first_request(parser, heads, dat) - stream = new_stream(1) - heads = heads.merge({ ":method" => parser.http_method, - ":path" => parser.request_url, - ":authority" => heads["host"] }) - .reject {|n, v| ["connection", "http2-settings", "upgrade", "host"].include?(n) } + def process_first_request encoder = HPACK::Encoder.new(0, indexing: false) # don't pollute connection's HPACK context - headers = Frame.headers(1, encoder.encode(heads), :end_headers) # stream ID is 1 - headers.split_headers(local_settings[:max_frame_size]).each {|hfrag| stream.receive_frame(hfrag) } - data = Frame.data(1, dat, :end_stream) - data.split_data(local_settings[:max_frame_size]).each {|dfrag| stream.receive_frame(dfrag) } + stream = new_stream(1) + max_frame_size = local_settings[:max_frame_size] + headers = @_headers.merge({ ":method" => @_http_parser.http_method, + ":path" => @_http_parser.request_url, + ":authority" => @_headers["host"] }) + .reject {|n, v| ["connection", "http2-settings", "upgrade", "host"].include?(n) } + + headers_s = Frame.headers(1, encoder.encode(headers), :end_headers).split_headers(max_frame_size) # stream ID is 1 + data_s = Frame.data(1, @_body, :end_stream).split_data(max_frame_size) + (headers_s + data_s).each {|frag| stream.receive_frame(frag) } end end end diff --git a/lib/plum/https_connection.rb b/lib/plum/https_connection.rb index e96a785..a1def19 100644 --- a/lib/plum/https_connection.rb +++ b/lib/plum/https_connection.rb @@ -1,5 +1,3 @@ -using Plum::BinaryString - module Plum class HTTPSConnection < Connection def initialize(io, local_settings = {}) |