aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2015-11-09 21:19:36 +0900
committerKazuki Yamaguchi <k@rhe.jp>2015-11-09 21:19:36 +0900
commit40e700d3d85b372e64a1eac3c6661e766cf49423 (patch)
tree1c57e4c147d90eedd467124d813e8326fe6ded3b
parent3b460e375507e5393994b3e82a19253d5d0bcddd (diff)
downloadplum-40e700d3d85b372e64a1eac3c6661e766cf49423.tar.gz
client: support HTTP/1.1 upgrade
-rw-r--r--README.md4
-rw-r--r--lib/plum.rb1
-rw-r--r--lib/plum/client.rb54
-rw-r--r--lib/plum/client/client_session.rb4
-rw-r--r--lib/plum/client/legacy_client_session.rb2
-rw-r--r--lib/plum/client/upgrade_client_session.rb46
-rw-r--r--lib/plum/connection.rb29
-rw-r--r--lib/plum/rack/session.rb2
-rw-r--r--lib/plum/server/http_connection.rb8
9 files changed, 103 insertions, 47 deletions
diff --git a/README.md b/README.md
index c1e81cf..2bf6fe7 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,7 @@ client.close
##### Parallel request
```ruby
res1 = res2 = nil
-Plum::Client.start("rhe.jp", http2_settings: { max_frame_size: 32768 }) { |client|
+Plum::Client.start("rhe.jp", 443, http2_settings: { max_frame_size: 32768 }) { |client|
res1 = client.get("/")
res2 = client.post("/post", "data")
# res1.status == nil ; because it's async request
@@ -76,7 +76,7 @@ p res1.status # => "200"
##### Download a large file
```ruby
-Plum::Client.start("http2.rhe.jp", hostname: "assets.rhe.jp") { |client|
+Plum::Client.start("http2.rhe.jp", 443, hostname: "assets.rhe.jp") { |client|
client.get("/large") do |res| # called when received response headers
p res.status # => "200"
File.open("/tmp/large.file", "wb") { |file|
diff --git a/lib/plum.rb b/lib/plum.rb
index 5c754ea..3ae301f 100644
--- a/lib/plum.rb
+++ b/lib/plum.rb
@@ -27,3 +27,4 @@ require "plum/client/response"
require "plum/client/connection"
require "plum/client/client_session"
require "plum/client/legacy_client_session"
+require "plum/client/upgrade_client_session"
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)