aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2015-11-10 20:19:20 +0900
committerKazuki Yamaguchi <k@rhe.jp>2015-11-10 20:19:20 +0900
commit88763b7f38b97ffe0377c5b0699938326ced61c2 (patch)
tree854149ad8fb666f110f351fa3246abbb488382f2
parent0dea10636e87686119a0aed72bca7564a50cb9f9 (diff)
downloadplum-88763b7f38b97ffe0377c5b0699938326ced61c2.tar.gz
client: add auto_decode option: decode automatically deflate or gzip encoded response
-rw-r--r--lib/plum.rb2
-rw-r--r--lib/plum/client.rb3
-rw-r--r--lib/plum/client/client_session.rb2
-rw-r--r--lib/plum/client/decoders.rb51
-rw-r--r--lib/plum/client/legacy_client_session.rb2
-rw-r--r--lib/plum/client/response.rb26
-rw-r--r--lib/plum/errors.rb22
-rw-r--r--test/plum/client/test_decoders.rb54
-rw-r--r--test/plum/client/test_response.rb5
9 files changed, 150 insertions, 17 deletions
diff --git a/lib/plum.rb b/lib/plum.rb
index 3ae301f..95e5588 100644
--- a/lib/plum.rb
+++ b/lib/plum.rb
@@ -2,6 +2,7 @@ require "openssl"
require "socket"
require "base64"
require "set"
+require "zlib"
require "plum/version"
require "plum/errors"
require "plum/binary_string"
@@ -24,6 +25,7 @@ require "plum/server/https_connection"
require "plum/server/http_connection"
require "plum/client"
require "plum/client/response"
+require "plum/client/decoders"
require "plum/client/connection"
require "plum/client/client_session"
require "plum/client/legacy_client_session"
diff --git a/lib/plum/client.rb b/lib/plum/client.rb
index c2a1b83..8cb9230 100644
--- a/lib/plum/client.rb
+++ b/lib/plum/client.rb
@@ -9,6 +9,7 @@ module Plum
ssl_context: nil,
http2_settings: {},
user_agent: "plum/#{Plum::VERSION}",
+ auto_decode: true,
}.freeze
attr_reader :host, :port, :config
@@ -79,7 +80,7 @@ module Plum
# @param block [Proc] if passed, it will be called when received response headers.
def request(headers, body, options = {}, &block)
raise ArgumentError, ":method and :path headers are required" unless headers[":method"] && headers[":path"]
- @session.request(headers, body, options, &block)
+ @session.request(headers, body, @config.merge(options), &block)
end
# @!method get!
diff --git a/lib/plum/client/client_session.rb b/lib/plum/client/client_session.rb
index 495d099..6e9fc56 100644
--- a/lib/plum/client/client_session.rb
+++ b/lib/plum/client/client_session.rb
@@ -42,7 +42,7 @@ module Plum
":scheme" => @config[:scheme]
}.merge(headers)
- response = Response.new
+ response = Response.new(**options)
@responses << response
stream = @plum.open_stream
stream.send_headers(headers, end_stream: !body)
diff --git a/lib/plum/client/decoders.rb b/lib/plum/client/decoders.rb
new file mode 100644
index 0000000..e6d72e7
--- /dev/null
+++ b/lib/plum/client/decoders.rb
@@ -0,0 +1,51 @@
+module Plum
+ module Decoders
+ class Base
+ def decode(chunk)
+ chunk
+ end
+
+ def finish
+ end
+ end
+
+ # `deflate` is not just deflate, wrapped by zlib format (RFC 1950)
+ class Deflate < Base
+ def initialize
+ @inflate = Zlib::Inflate.new(Zlib::MAX_WBITS)
+ end
+
+ def decode(chunk)
+ @inflate.inflate(chunk)
+ rescue Zlib::Error => e
+ raise DecoderError.new("failed to decode chunk", e)
+ end
+
+ def finish
+ @inflate.finish
+ rescue Zlib::Error => e
+ raise DecoderError.new("failed to finalize", e)
+ end
+ end
+
+ class GZip < Base
+ def initialize
+ @stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
+ end
+
+ def decode(chunk)
+ @stream.inflate(chunk)
+ rescue Zlib::Error => e
+ raise DecoderError.new("failed to decode chunk", e)
+ end
+
+ def finish
+ @stream.finish
+ rescue Zlib::Error => e
+ raise DecoderError.new("failed to finalize", e)
+ end
+ end
+
+ DECODERS = { "gzip" => GZip, "deflate" => Deflate }.freeze
+ end
+end
diff --git a/lib/plum/client/legacy_client_session.rb b/lib/plum/client/legacy_client_session.rb
index a30c237..bc531ac 100644
--- a/lib/plum/client/legacy_client_session.rb
+++ b/lib/plum/client/legacy_client_session.rb
@@ -40,7 +40,7 @@ module Plum
end
end
- response = Response.new
+ response = Response.new(**options)
@requests << [response, headers, body, chunked, headers_cb]
consume_queue
response
diff --git a/lib/plum/client/response.rb b/lib/plum/client/response.rb
index e9bd5e1..9e50f02 100644
--- a/lib/plum/client/response.rb
+++ b/lib/plum/client/response.rb
@@ -6,11 +6,12 @@ module Plum
attr_reader :headers
# @api private
- def initialize
+ def initialize(auto_decode: true, **options)
@body = Queue.new
@finished = false
@failed = false
@body = []
+ @auto_decode = auto_decode
end
# Returns the HTTP status code.
@@ -54,7 +55,7 @@ module Plum
def on_finish(&block)
raise ArgumentError, "block must be given" unless block_given?
if finished?
- block.call
+ yield
else
@on_finish = block
end
@@ -64,21 +65,20 @@ module Plum
# @return [String] the whole response body
def body
raise "Body already read" if @on_chunk
- if finished?
- @body.join
- else
- raise "Response body is not complete"
- end
+ raise "Response body is not complete" unless finished?
+ @body.join
end
# @api private
def _headers(raw_headers)
# response headers should not have duplicates
@headers = raw_headers.to_h.freeze
+ @decoder = setup_decoder
end
# @api private
- def _chunk(chunk)
+ def _chunk(encoded)
+ chunk = @decoder.decode(encoded)
if @on_chunk
@on_chunk.call(chunk)
else
@@ -89,6 +89,7 @@ module Plum
# @api private
def _finish
@finished = true
+ @decoder.finish
@on_finish.call if @on_finish
end
@@ -96,5 +97,14 @@ module Plum
def _fail
@failed = true
end
+
+ private
+ def setup_decoder
+ if @auto_decode
+ klass = Decoders::DECODERS[@headers["content-encoding"]]
+ end
+ klass ||= Decoders::Base
+ klass.new
+ end
end
end
diff --git a/lib/plum/errors.rb b/lib/plum/errors.rb
index 3220c67..26fade1 100644
--- a/lib/plum/errors.rb
+++ b/lib/plum/errors.rb
@@ -31,6 +31,14 @@ module Plum
ERROR_CODES[@http2_error_type]
end
end
+
+ class RemoteHTTPError < HTTPError; end
+ class RemoteConnectionError < RemoteHTTPError; end
+ class RemoteStreamError < RemoteHTTPError; end
+ class LocalHTTPError < HTTPError; end
+ class LocalConnectionError < LocalHTTPError; end
+ class LocalStreamError < LocalHTTPError; end
+
class LegacyHTTPError < Error
attr_reader :headers, :data, :parser
@@ -41,10 +49,12 @@ module Plum
end
end
- class RemoteHTTPError < HTTPError; end
- class RemoteConnectionError < RemoteHTTPError; end
- class RemoteStreamError < RemoteHTTPError; end
- class LocalHTTPError < HTTPError; end
- class LocalConnectionError < LocalHTTPError; end
- class LocalStreamError < LocalHTTPError; end
+ class DecoderError < Error
+ attr_reader :inner_error
+
+ def initialize(message, inner_error = nil)
+ super(message)
+ @inner_error = inner_error
+ end
+ end
end
diff --git a/test/plum/client/test_decoders.rb b/test/plum/client/test_decoders.rb
new file mode 100644
index 0000000..dfd67b1
--- /dev/null
+++ b/test/plum/client/test_decoders.rb
@@ -0,0 +1,54 @@
+require "test_helper"
+
+using Plum::BinaryString
+class DecodersTest < Minitest::Test
+ def test_base_decode
+ decoder = Decoders::Base.new
+ assert_equal("abc", decoder.decode("abc"))
+ end
+
+ def test_base_finish
+ decoder = Decoders::Base.new
+ decoder.finish
+ end
+
+ def test_deflate_decode
+ decoder = Decoders::Deflate.new
+ assert_equal("hello", decoder.decode("\x78\x9c\xcb\x48\xcd\xc9\xc9\x07\x00\x06\x2c\x02\x15"))
+ end
+
+ def test_deflate_decode_error
+ decoder = Decoders::Deflate.new
+ assert_raises(DecoderError) {
+ decoder.decode("\x79\x9c\xcb\x48\xcd\xc9\xc9\x07\x00\x06\x2c\x02\x15")
+ }
+ end
+
+ def test_deflate_finish_error
+ decoder = Decoders::Deflate.new
+ decoder.decode("\x78\x9c\xcb\x48\xcd\xc9\xc9\x07\x00\x06\x2c\x02")
+ assert_raises(DecoderError) {
+ decoder.finish
+ }
+ end
+
+ def test_gzip_decode
+ decoder = Decoders::GZip.new
+ assert_equal("hello", decoder.decode("\x1f\x8b\x08\x00\x1a\x96\xe0\x4c\x00\x03\xcb\x48\xcd\xc9\xc9\x07\x00\x86\xa6\x10\x36\x05\x00\x00\x00"))
+ end
+
+ def test_gzip_decode_error
+ decoder = Decoders::GZip.new
+ assert_raises(DecoderError) {
+ decoder.decode("\x2f\x8b\x08\x00\x1a\x96\xe0\x4c\x00\x03\xcb\x48\xcd\xc9\xc9\x07\x00\x86\xa6\x10\x36\x05\x00\x00\x00")
+ }
+ end
+
+ def test_gzip_finish_error
+ decoder = Decoders::GZip.new
+ decoder.decode("\x1f\x8b\x08\x00\x1a\x96")
+ assert_raises(DecoderError) {
+ decoder.finish
+ }
+ end
+end
diff --git a/test/plum/client/test_response.rb b/test/plum/client/test_response.rb
index 76d9037..511a073 100644
--- a/test/plum/client/test_response.rb
+++ b/test/plum/client/test_response.rb
@@ -4,6 +4,7 @@ using Plum::BinaryString
class ResponseTest < Minitest::Test
def test_finished
resp = Response.new
+ resp._headers({})
assert_equal(false, resp.finished?)
resp._finish
assert_equal(true, resp.finished?)
@@ -34,6 +35,7 @@ class ResponseTest < Minitest::Test
def test_body
resp = Response.new
+ resp._headers({})
resp._chunk("a")
resp._chunk("b")
resp._finish
@@ -42,6 +44,7 @@ class ResponseTest < Minitest::Test
def test_body_not_finished
resp = Response.new
+ resp._headers({})
resp._chunk("a")
resp._chunk("b")
assert_raises { # TODO
@@ -51,6 +54,7 @@ class ResponseTest < Minitest::Test
def test_on_chunk
resp = Response.new
+ resp._headers({})
res = []
resp._chunk("a")
resp._chunk("b")
@@ -63,6 +67,7 @@ class ResponseTest < Minitest::Test
def test_on_finish
resp = Response.new
+ resp._headers({})
ran = false
resp.on_finish { ran = true }
resp._finish