diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2015-08-14 17:31:33 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2015-08-14 17:31:33 +0900 |
commit | 881f8c942d450e28db1bc85928068a22ce37a656 (patch) | |
tree | 489528238e7258ca95b515017e51e256e32ba40d /lib/plum/http_connection.rb | |
parent | cdebfcf496dd65cff5d52667b625d49d1643bf16 (diff) | |
download | plum-881f8c942d450e28db1bc85928068a22ce37a656.tar.gz |
add support for HTTP/2 over TCP ('http' URI scheme)
Diffstat (limited to 'lib/plum/http_connection.rb')
-rw-r--r-- | lib/plum/http_connection.rb | 88 |
1 files changed, 73 insertions, 15 deletions
diff --git a/lib/plum/http_connection.rb b/lib/plum/http_connection.rb index bde8b7d..e5e63f6 100644 --- a/lib/plum/http_connection.rb +++ b/lib/plum/http_connection.rb @@ -1,33 +1,91 @@ +using Plum::BinaryString + module Plum class HTTPConnection < Connection def initialize(io, local_settings = {}) + require "http/parser" super + @_http_parser = setup_parser + @_upgrade_retry = false # After sent 426 (Upgrade Required) end private - def negotiate! - if @buffer.bytesize >= 4 - if CLIENT_CONNECTION_PREFACE.start_with?(@buffer) - negotiate_with_knowledge + 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"] + + 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) + } else - negotiate_with_upgrade + respond_not_supported + close end end - # next + + parser end - def negotiate_with_knowledge - if @buffer.bytesize >= 24 - if @buffer.byteshift(24) == CLIENT_CONNECTION_PREFACE - @state = :waiting_settings - settings(@local_settings) - end + def negotiate! + begin + super + rescue ConnectionError + # Upgrade from HTTP/1.1 + offset = @_http_parser << @buffer + @buffer.byteshift(offset) end - # next end - def negotiate_with_upgrade - raise NotImplementedError, "Parsing HTTP/1.1 is hard..." + 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 << "\r\n" + + io.write(resp) + end + + def respond_not_supported + data = "Use modern web browser with HTTP/2 support." + + resp = "" + 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 << "\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) } + 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) } end end end |