aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2015-08-14 17:31:33 +0900
committerKazuki Yamaguchi <k@rhe.jp>2015-08-14 17:31:33 +0900
commit881f8c942d450e28db1bc85928068a22ce37a656 (patch)
tree489528238e7258ca95b515017e51e256e32ba40d /lib
parentcdebfcf496dd65cff5d52667b625d49d1643bf16 (diff)
downloadplum-881f8c942d450e28db1bc85928068a22ce37a656.tar.gz
add support for HTTP/2 over TCP ('http' URI scheme)
Diffstat (limited to 'lib')
-rw-r--r--lib/plum.rb1
-rw-r--r--lib/plum/connection.rb26
-rw-r--r--lib/plum/http_connection.rb88
-rw-r--r--lib/plum/https_connection.rb15
4 files changed, 93 insertions, 37 deletions
diff --git a/lib/plum.rb b/lib/plum.rb
index 8842d6b..2715ccc 100644
--- a/lib/plum.rb
+++ b/lib/plum.rb
@@ -1,5 +1,6 @@
require "openssl"
require "socket"
+require "base64"
require "plum/version"
require "plum/errors"
require "plum/binary_string"
diff --git a/lib/plum/connection.rb b/lib/plum/connection.rb
index 32a7d2e..2c2536a 100644
--- a/lib/plum/connection.rb
+++ b/lib/plum/connection.rb
@@ -65,10 +65,6 @@ module Plum
receive_frame(frame)
end
end
- rescue ConnectionError => e
- callback(:connection_error, e)
- goaway(e.http2_error_type)
- close
end
alias << receive
@@ -87,8 +83,20 @@ module Plum
@io.write(frame.assemble)
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
+ raise ConnectionError.new(:protocol_error) # (MAY) send GOAWAY. sending.
+ end
+ end
+
def new_stream(stream_id, **args)
- if @streams.size > 0 && @streams.keys.last >= stream_id
+ if @streams.size > 0 && @streams.keys.max >= stream_id
raise Plum::ConnectionError.new(:protocol_error)
end
@@ -140,6 +148,10 @@ module Plum
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)
@@ -164,7 +176,7 @@ module Plum
end
end
- def receive_settings(frame)
+ def receive_settings(frame, send_ack: true)
if frame.flags.include?(:ack)
raise ConnectionError.new(:frame_size_error) if frame.length != 0
return
@@ -178,7 +190,7 @@ module Plum
callback(:remote_settings, @remote_settings, old_remote_settings)
- send_immediately Frame.settings(:ack)
+ send_immediately Frame.settings(:ack) if send_ack
end
def apply_remote_settings(old_remote_settings)
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
diff --git a/lib/plum/https_connection.rb b/lib/plum/https_connection.rb
index d05da40..e96a785 100644
--- a/lib/plum/https_connection.rb
+++ b/lib/plum/https_connection.rb
@@ -5,20 +5,5 @@ module Plum
def initialize(io, local_settings = {})
super
end
-
- private
- def negotiate!
- return if @buffer.empty?
-
- 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
- raise ConnectionError.new(:protocol_error) # (MAY) send GOAWAY. sending.
- end
- end
end
end