http 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -0
- data/.travis.yml +12 -11
- data/CHANGES.md +12 -0
- data/Gemfile +2 -2
- data/README.md +1 -1
- data/lib/http/chainable.rb +12 -2
- data/lib/http/client.rb +19 -12
- data/lib/http/feature.rb +8 -0
- data/lib/http/features/auto_deflate.rb +45 -0
- data/lib/http/features/auto_inflate.rb +14 -0
- data/lib/http/headers/known.rb +4 -0
- data/lib/http/options.rb +47 -1
- data/lib/http/request.rb +4 -1
- data/lib/http/response.rb +14 -1
- data/lib/http/response/body.rb +4 -3
- data/lib/http/response/inflater.rb +30 -0
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/features/auto_deflate_spec.rb +109 -0
- data/spec/lib/http/features/auto_inflate_spec.rb +68 -0
- data/spec/lib/http/options/features_spec.rb +32 -0
- data/spec/lib/http/options/merge_spec.rb +4 -2
- data/spec/lib/http/response/body_spec.rb +29 -1
- data/spec/lib/http_spec.rb +43 -0
- data/spec/spec_helper.rb +1 -6
- data/spec/support/dummy_server/servlet.rb +22 -0
- data/spec/support/http_handling_shared.rb +6 -19
- metadata +14 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 516d371c9aa3a19abeaabeefea99bd16be506f36
|
4
|
+
data.tar.gz: 030d18563e33fc3aad5832cb98508013cba1a956
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33f746cb66e0493bb3ca2a272bbf7364a6a949e804dc7f581738a05b373367b08baf0798e838e3a7865d7ef782f0e74dd823204a63bb97b7f334964673e9ed0f
|
7
|
+
data.tar.gz: c426e9ff531bbc8268b7bfd3d49276c0fb890e063a69fc3fd6000c8580631dfb62fd83d04088c1a77ed246078fe77c30364c42cdaa527a1f2cedcb1e55499c77
|
data/.rubocop.yml
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.0
|
data/.travis.yml
CHANGED
@@ -1,28 +1,29 @@
|
|
1
1
|
language: ruby
|
2
2
|
sudo: false
|
3
3
|
|
4
|
-
|
4
|
+
before_install:
|
5
|
+
- gem update --system 2.6.10
|
6
|
+
- gem --version
|
7
|
+
- gem install bundler --version 1.14.3 --no-rdoc --no-ri
|
8
|
+
- bundle --version
|
9
|
+
|
10
|
+
install: bundle _1.14.3_ install --without development doc
|
11
|
+
|
12
|
+
script: bundle _1.14.3_ exec rake
|
5
13
|
|
6
14
|
env:
|
7
15
|
global:
|
8
16
|
- JRUBY_OPTS="$JRUBY_OPTS --debug"
|
9
17
|
|
10
18
|
rvm:
|
19
|
+
- jruby-9.1.7.0
|
11
20
|
- 2.0.0
|
12
21
|
- 2.1
|
13
22
|
- 2.2
|
14
|
-
- 2.3.
|
15
|
-
- 2.
|
16
|
-
- jruby-9.1.0.0
|
17
|
-
- jruby-head
|
18
|
-
- ruby-head
|
19
|
-
- rbx-2
|
23
|
+
- 2.3.3
|
24
|
+
- 2.4.0
|
20
25
|
|
21
26
|
matrix:
|
22
|
-
allow_failures:
|
23
|
-
- rvm: jruby-head
|
24
|
-
- rvm: ruby-head
|
25
|
-
- rvm: rbx-2
|
26
27
|
fast_finish: true
|
27
28
|
|
28
29
|
branches:
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## 2.2.0 (2017-02-03)
|
2
|
+
|
3
|
+
* [#375](https://github.com/httprb/http/pull/375)
|
4
|
+
Add support for automatic Gzip/Inflate
|
5
|
+
([@Bonias])
|
6
|
+
|
7
|
+
* [#390](https://github.com/httprb/http/pull/390)
|
8
|
+
Add REPORT to the list of valid HTTP verbs
|
9
|
+
([@ixti])
|
10
|
+
|
11
|
+
|
1
12
|
## 2.1.0 (2016-11-08)
|
2
13
|
|
3
14
|
* [#370](https://github.com/httprb/http/issues/370)
|
@@ -566,3 +577,4 @@ end
|
|
566
577
|
[@jhbabon]: https://github.com/jhbabon
|
567
578
|
[@britishtea]: https://github.com/britishtea
|
568
579
|
[@janko-m]: https://github.com/janko-m
|
580
|
+
[@Bonias]: https://github.com/Bonias
|
data/Gemfile
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
|
+
ruby RUBY_VERSION
|
2
3
|
|
3
4
|
gem "rake"
|
4
5
|
|
@@ -17,13 +18,12 @@ group :test do
|
|
17
18
|
gem "backports"
|
18
19
|
gem "coveralls", :require => false
|
19
20
|
gem "simplecov", ">= 0.9"
|
20
|
-
gem "json", ">= 1.8.1"
|
21
21
|
gem "rubocop", "= 0.40.0"
|
22
22
|
gem "rspec", "~> 3.0"
|
23
23
|
gem "rspec-its"
|
24
24
|
gem "yardstick"
|
25
25
|
gem "certificate_authority", :require => false
|
26
|
-
gem "activemodel",
|
26
|
+
gem "activemodel", :require => false # Used by certificate_authority
|
27
27
|
end
|
28
28
|
|
29
29
|
group :doc do
|
data/README.md
CHANGED
data/lib/http/chainable.rb
CHANGED
@@ -75,10 +75,12 @@ module HTTP
|
|
75
75
|
branch(options).request verb, uri
|
76
76
|
end
|
77
77
|
|
78
|
-
# @overload(options = {})
|
78
|
+
# @overload timeout(options = {})
|
79
79
|
# Syntax sugar for `timeout(:per_operation, options)`
|
80
|
-
# @overload(klass, options = {})
|
80
|
+
# @overload timeout(klass, options = {})
|
81
|
+
# Adds a timeout to the request.
|
81
82
|
# @param [#to_sym] klass
|
83
|
+
# either :null, :global, or :per_operation
|
82
84
|
# @param [Hash] options
|
83
85
|
# @option options [Float] :read Read timeout
|
84
86
|
# @option options [Float] :write Write timeout
|
@@ -228,6 +230,14 @@ module HTTP
|
|
228
230
|
branch default_options.with_nodelay(true)
|
229
231
|
end
|
230
232
|
|
233
|
+
# Turn on given features. Available features are:
|
234
|
+
# * auto_inflate
|
235
|
+
# * auto_deflate
|
236
|
+
# @param features
|
237
|
+
def use(*features)
|
238
|
+
branch default_options.with_features(features)
|
239
|
+
end
|
240
|
+
|
231
241
|
private
|
232
242
|
|
233
243
|
# :nodoc:
|
data/lib/http/client.rb
CHANGED
@@ -71,6 +71,7 @@ module HTTP
|
|
71
71
|
:proxy_headers => @connection.proxy_response_headers,
|
72
72
|
:connection => @connection,
|
73
73
|
:encoding => options.encoding,
|
74
|
+
:auto_inflate => options.feature(:auto_inflate),
|
74
75
|
:uri => req.uri
|
75
76
|
)
|
76
77
|
|
@@ -150,18 +151,24 @@ module HTTP
|
|
150
151
|
|
151
152
|
# Create the request body object to send
|
152
153
|
def make_request_body(opts, headers)
|
153
|
-
|
154
|
-
|
155
|
-
opts.body
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
154
|
+
request_body =
|
155
|
+
case
|
156
|
+
when opts.body
|
157
|
+
opts.body
|
158
|
+
when opts.form
|
159
|
+
form = HTTP::FormData.create opts.form
|
160
|
+
headers[Headers::CONTENT_TYPE] ||= form.content_type
|
161
|
+
headers[Headers::CONTENT_LENGTH] ||= form.content_length
|
162
|
+
form.to_s
|
163
|
+
when opts.json
|
164
|
+
body = MimeType[:json].encode opts.json
|
165
|
+
headers[Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name}"
|
166
|
+
body
|
167
|
+
end
|
168
|
+
if (auto_deflate = opts.feature(:auto_deflate))
|
169
|
+
auto_deflate.deflate(headers, request_body)
|
170
|
+
else
|
171
|
+
request_body
|
165
172
|
end
|
166
173
|
end
|
167
174
|
end
|
data/lib/http/feature.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zlib"
|
4
|
+
|
5
|
+
module HTTP
|
6
|
+
module Features
|
7
|
+
class AutoDeflate < Feature
|
8
|
+
attr_reader :method
|
9
|
+
|
10
|
+
def initialize(*)
|
11
|
+
super
|
12
|
+
|
13
|
+
@method = @opts.key?(:method) ? @opts[:method].to_s : "gzip"
|
14
|
+
|
15
|
+
raise Error, "Only gzip and deflate methods are supported" unless %w(gzip deflate).include?(@method)
|
16
|
+
end
|
17
|
+
|
18
|
+
def deflate(headers, body)
|
19
|
+
return body unless body
|
20
|
+
return body unless body.is_a?(String)
|
21
|
+
|
22
|
+
# We need to delete Content-Length header. It will be set automatically
|
23
|
+
# by HTTP::Request::Writer
|
24
|
+
headers.delete(Headers::CONTENT_LENGTH)
|
25
|
+
|
26
|
+
headers[Headers::CONTENT_ENCODING] = method
|
27
|
+
|
28
|
+
case method
|
29
|
+
when "gzip" then
|
30
|
+
StringIO.open do |out|
|
31
|
+
Zlib::GzipWriter.wrap(out) do |gz|
|
32
|
+
gz.write body
|
33
|
+
gz.finish
|
34
|
+
out.tap(&:rewind).read
|
35
|
+
end
|
36
|
+
end
|
37
|
+
when "deflate" then
|
38
|
+
Zlib::Deflate.deflate(body)
|
39
|
+
else
|
40
|
+
raise ArgumentError, "Unsupported deflate method: #{method}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module HTTP
|
3
|
+
module Features
|
4
|
+
class AutoInflate < Feature
|
5
|
+
def stream_for(connection, response)
|
6
|
+
if %w(deflate gzip x-gzip).include?(response.headers[:content_encoding])
|
7
|
+
Response::Inflater.new(connection)
|
8
|
+
else
|
9
|
+
connection
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/http/headers/known.rb
CHANGED
@@ -68,6 +68,10 @@ module HTTP
|
|
68
68
|
# Currently defined methods are: chunked, compress, deflate, gzip, identity.
|
69
69
|
TRANSFER_ENCODING = "Transfer-Encoding".freeze
|
70
70
|
|
71
|
+
# Indicates what additional content codings have been applied to the
|
72
|
+
# entity-body.
|
73
|
+
CONTENT_ENCODING = "Content-Encoding".freeze
|
74
|
+
|
71
75
|
# The user agent string of the user agent.
|
72
76
|
USER_AGENT = "User-Agent".freeze
|
73
77
|
|
data/lib/http/options.rb
CHANGED
@@ -3,15 +3,24 @@ require "http/headers"
|
|
3
3
|
require "openssl"
|
4
4
|
require "socket"
|
5
5
|
require "http/uri"
|
6
|
+
require "http/feature"
|
7
|
+
require "http/features/auto_inflate"
|
8
|
+
require "http/features/auto_deflate"
|
6
9
|
|
7
10
|
module HTTP
|
11
|
+
# rubocop:disable Metrics/ClassLength
|
8
12
|
class Options
|
9
13
|
@default_socket_class = TCPSocket
|
10
14
|
@default_ssl_socket_class = OpenSSL::SSL::SSLSocket
|
11
15
|
@default_timeout_class = HTTP::Timeout::Null
|
16
|
+
@available_features = {
|
17
|
+
:auto_inflate => Features::AutoInflate,
|
18
|
+
:auto_deflate => Features::AutoDeflate
|
19
|
+
}
|
12
20
|
|
13
21
|
class << self
|
14
22
|
attr_accessor :default_socket_class, :default_ssl_socket_class, :default_timeout_class
|
23
|
+
attr_reader :available_features
|
15
24
|
|
16
25
|
def new(options = {})
|
17
26
|
return options if options.is_a?(self)
|
@@ -50,7 +59,8 @@ module HTTP
|
|
50
59
|
:keep_alive_timeout => 5,
|
51
60
|
:headers => {},
|
52
61
|
:cookies => {},
|
53
|
-
:encoding => nil
|
62
|
+
:encoding => nil,
|
63
|
+
:features => {}
|
54
64
|
}
|
55
65
|
|
56
66
|
opts_w_defaults = defaults.merge(options)
|
@@ -73,6 +83,38 @@ module HTTP
|
|
73
83
|
self.encoding = Encoding.find(encoding)
|
74
84
|
end
|
75
85
|
|
86
|
+
def_option :features do |features|
|
87
|
+
# Normalize features from:
|
88
|
+
#
|
89
|
+
# [{feature_one: {opt: 'val'}}, :feature_two]
|
90
|
+
#
|
91
|
+
# into:
|
92
|
+
#
|
93
|
+
# {feature_one: {opt: 'val'}, feature_two: {}}
|
94
|
+
features = features.each_with_object({}) do |feature, h|
|
95
|
+
if feature.is_a?(Hash)
|
96
|
+
h.merge!(feature)
|
97
|
+
else
|
98
|
+
h[feature] = {}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
self.features.merge(features)
|
103
|
+
end
|
104
|
+
|
105
|
+
def features=(features)
|
106
|
+
@features = features.each_with_object({}) do |(name, opts_or_feature), h|
|
107
|
+
h[name] = if opts_or_feature.is_a?(Feature)
|
108
|
+
opts_or_feature
|
109
|
+
else
|
110
|
+
unless (feature = self.class.available_features[name])
|
111
|
+
argument_error! "Unsupported feature: #{name}"
|
112
|
+
end
|
113
|
+
feature.new(opts_or_feature)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
76
118
|
%w(
|
77
119
|
proxy params form json body follow response
|
78
120
|
socket_class nodelay ssl_socket_class ssl_context ssl
|
@@ -127,6 +169,10 @@ module HTTP
|
|
127
169
|
dupped
|
128
170
|
end
|
129
171
|
|
172
|
+
def feature(name)
|
173
|
+
features[name]
|
174
|
+
end
|
175
|
+
|
130
176
|
protected
|
131
177
|
|
132
178
|
def []=(option, val)
|
data/lib/http/request.rb
CHANGED
@@ -37,7 +37,10 @@ module HTTP
|
|
37
37
|
# RFC 3744: WebDAV Access Control Protocol
|
38
38
|
:acl,
|
39
39
|
|
40
|
-
#
|
40
|
+
# RFC 6352: vCard Extensions to WebDAV -- CardDAV
|
41
|
+
:report,
|
42
|
+
|
43
|
+
# RFC 5789: PATCH Method for HTTP
|
41
44
|
:patch,
|
42
45
|
|
43
46
|
# draft-reschke-webdav-search: WebDAV Search
|
data/lib/http/response.rb
CHANGED
@@ -5,6 +5,7 @@ require "http/headers"
|
|
5
5
|
require "http/content_type"
|
6
6
|
require "http/mime_type"
|
7
7
|
require "http/response/status"
|
8
|
+
require "http/response/inflater"
|
8
9
|
require "http/uri"
|
9
10
|
require "http/cookie_jar"
|
10
11
|
require "time"
|
@@ -48,7 +49,9 @@ module HTTP
|
|
48
49
|
connection = opts.fetch(:connection)
|
49
50
|
encoding = opts[:encoding] || charset || Encoding::BINARY
|
50
51
|
|
51
|
-
|
52
|
+
stream = body_stream_for(connection, opts)
|
53
|
+
|
54
|
+
@body = Response::Body.new(connection, stream, encoding)
|
52
55
|
else
|
53
56
|
@body = opts.fetch(:body)
|
54
57
|
end
|
@@ -143,5 +146,15 @@ module HTTP
|
|
143
146
|
def inspect
|
144
147
|
"#<#{self.class}/#{@version} #{code} #{reason} #{headers.to_h.inspect}>"
|
145
148
|
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def body_stream_for(connection, opts)
|
153
|
+
if opts[:auto_inflate]
|
154
|
+
opts[:auto_inflate].stream_for(connection, self)
|
155
|
+
else
|
156
|
+
connection
|
157
|
+
end
|
158
|
+
end
|
146
159
|
end
|
147
160
|
end
|
data/lib/http/response/body.rb
CHANGED
@@ -15,17 +15,18 @@ module HTTP
|
|
15
15
|
# @return [HTTP::Connection]
|
16
16
|
attr_reader :connection
|
17
17
|
|
18
|
-
def initialize(connection, encoding = Encoding::BINARY)
|
18
|
+
def initialize(connection, stream, encoding = Encoding::BINARY)
|
19
19
|
@connection = connection
|
20
20
|
@streaming = nil
|
21
21
|
@contents = nil
|
22
|
+
@stream = stream
|
22
23
|
@encoding = encoding
|
23
24
|
end
|
24
25
|
|
25
26
|
# (see HTTP::Client#readpartial)
|
26
27
|
def readpartial(*args)
|
27
28
|
stream!
|
28
|
-
@
|
29
|
+
@stream.readpartial(*args)
|
29
30
|
end
|
30
31
|
|
31
32
|
# Iterate over the body, allowing it to be enumerable
|
@@ -52,7 +53,7 @@ module HTTP
|
|
52
53
|
@streaming = false
|
53
54
|
@contents = String.new("").force_encoding(encoding)
|
54
55
|
|
55
|
-
while (chunk = @
|
56
|
+
while (chunk = @stream.readpartial)
|
56
57
|
@contents << chunk.force_encoding(encoding)
|
57
58
|
end
|
58
59
|
rescue
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zlib"
|
4
|
+
|
5
|
+
module HTTP
|
6
|
+
class Response
|
7
|
+
class Inflater
|
8
|
+
def initialize(connection)
|
9
|
+
@connection = connection
|
10
|
+
end
|
11
|
+
|
12
|
+
def readpartial(*args)
|
13
|
+
chunk = @connection.readpartial(*args)
|
14
|
+
if chunk
|
15
|
+
chunk = zstream.inflate(chunk)
|
16
|
+
elsif !zstream.closed?
|
17
|
+
zstream.finish
|
18
|
+
zstream.close
|
19
|
+
end
|
20
|
+
chunk
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def zstream
|
26
|
+
@zstream ||= Zlib::Inflate.new(32 + Zlib::MAX_WBITS)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/http/version.rb
CHANGED
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
RSpec.describe HTTP::Features::AutoDeflate do
|
3
|
+
subject { HTTP::Features::AutoDeflate.new }
|
4
|
+
|
5
|
+
it "raises error for wrong type" do
|
6
|
+
expect { HTTP::Features::AutoDeflate.new(:method => :wrong) }.
|
7
|
+
to raise_error(HTTP::Error) { |error|
|
8
|
+
expect(error.message).to eq("Only gzip and deflate methods are supported")
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
it "accepts gzip method" do
|
13
|
+
expect(HTTP::Features::AutoDeflate.new(:method => :gzip).method).to eq "gzip"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "accepts deflate method" do
|
17
|
+
expect(HTTP::Features::AutoDeflate.new(:method => :deflate).method).to eq "deflate"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "accepts string as method" do
|
21
|
+
expect(HTTP::Features::AutoDeflate.new(:method => "gzip").method).to eq "gzip"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "uses gzip by default" do
|
25
|
+
expect(subject.method).to eq("gzip")
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#deflate" do
|
29
|
+
let(:headers) { HTTP::Headers.coerce("Content-Length" => "10") }
|
30
|
+
|
31
|
+
context "when body is nil" do
|
32
|
+
let(:body) { nil }
|
33
|
+
|
34
|
+
it "returns nil" do
|
35
|
+
expect(subject.deflate(headers, body)).to be_nil
|
36
|
+
end
|
37
|
+
|
38
|
+
it "does not remove Content-Length header" do
|
39
|
+
subject.deflate(headers, body)
|
40
|
+
expect(headers["Content-Length"]).to eq "10"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "does not set Content-Encoding header" do
|
44
|
+
subject.deflate(headers, body)
|
45
|
+
expect(headers.include?("Content-Encoding")).to eq false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when body is not a string" do
|
50
|
+
let(:body) { {} }
|
51
|
+
|
52
|
+
it "returns given body" do
|
53
|
+
expect(subject.deflate(headers, body).object_id).to eq(body.object_id)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "does not remove Content-Length header" do
|
57
|
+
subject.deflate(headers, body)
|
58
|
+
expect(headers["Content-Length"]).to eq "10"
|
59
|
+
end
|
60
|
+
|
61
|
+
it "does not set Content-Encoding header" do
|
62
|
+
subject.deflate(headers, body)
|
63
|
+
expect(headers.include?("Content-Encoding")).to eq false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when body is a string" do
|
68
|
+
let(:body) { "Hello HTTP!" }
|
69
|
+
|
70
|
+
it "encodes body" do
|
71
|
+
encoded = subject.deflate(headers, body)
|
72
|
+
decoded = Zlib::GzipReader.new(StringIO.new(encoded)).read
|
73
|
+
|
74
|
+
expect(decoded).to eq(body)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "removes Content-Length header" do
|
78
|
+
subject.deflate(headers, body)
|
79
|
+
expect(headers.include?("Content-Length")).to eq false
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sets Content-Encoding header" do
|
83
|
+
subject.deflate(headers, body)
|
84
|
+
expect(headers["Content-Encoding"]).to eq "gzip"
|
85
|
+
end
|
86
|
+
|
87
|
+
context "as deflate method" do
|
88
|
+
subject { HTTP::Features::AutoDeflate.new(:method => :deflate) }
|
89
|
+
|
90
|
+
it "encodes body" do
|
91
|
+
encoded = subject.deflate(headers, body)
|
92
|
+
decoded = Zlib::Inflate.inflate(encoded)
|
93
|
+
|
94
|
+
expect(decoded).to eq(body)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "removes Content-Length header" do
|
98
|
+
subject.deflate(headers, body)
|
99
|
+
expect(headers.include?("Content-Length")).to eq false
|
100
|
+
end
|
101
|
+
|
102
|
+
it "sets Content-Encoding header" do
|
103
|
+
subject.deflate(headers, body)
|
104
|
+
expect(headers["Content-Encoding"]).to eq "deflate"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
RSpec.describe HTTP::Features::AutoInflate do
|
3
|
+
subject { HTTP::Features::AutoInflate.new }
|
4
|
+
let(:connection) { double }
|
5
|
+
let(:headers) { {} }
|
6
|
+
let(:response) do
|
7
|
+
HTTP::Response.new(
|
8
|
+
:version => "1.1",
|
9
|
+
:status => 200,
|
10
|
+
:headers => headers,
|
11
|
+
:connection => connection
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "stream_for" do
|
16
|
+
context "when there is no Content-Encoding header" do
|
17
|
+
it "returns connection" do
|
18
|
+
stream = subject.stream_for(connection, response)
|
19
|
+
expect(stream).to eq(connection)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "for identity Content-Encoding header" do
|
24
|
+
let(:headers) { {:content_encoding => "not-supported"} }
|
25
|
+
|
26
|
+
it "returns connection" do
|
27
|
+
stream = subject.stream_for(connection, response)
|
28
|
+
expect(stream).to eq(connection)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "for unknown Content-Encoding header" do
|
33
|
+
let(:headers) { {:content_encoding => "not-supported"} }
|
34
|
+
|
35
|
+
it "returns connection" do
|
36
|
+
stream = subject.stream_for(connection, response)
|
37
|
+
expect(stream).to eq(connection)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "for deflate Content-Encoding header" do
|
42
|
+
let(:headers) { {:content_encoding => "deflate"} }
|
43
|
+
|
44
|
+
it "returns HTTP::Response::Inflater instance - connection wrapper" do
|
45
|
+
stream = subject.stream_for(connection, response)
|
46
|
+
expect(stream).to be_instance_of HTTP::Response::Inflater
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "for gzip Content-Encoding header" do
|
51
|
+
let(:headers) { {:content_encoding => "gzip"} }
|
52
|
+
|
53
|
+
it "returns HTTP::Response::Inflater instance - connection wrapper" do
|
54
|
+
stream = subject.stream_for(connection, response)
|
55
|
+
expect(stream).to be_instance_of HTTP::Response::Inflater
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "for x-gzip Content-Encoding header" do
|
60
|
+
let(:headers) { {:content_encoding => "x-gzip"} }
|
61
|
+
|
62
|
+
it "returns HTTP::Response::Inflater instance - connection wrapper" do
|
63
|
+
stream = subject.stream_for(connection, response)
|
64
|
+
expect(stream).to be_instance_of HTTP::Response::Inflater
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
RSpec.describe HTTP::Options, "features" do
|
3
|
+
let(:opts) { HTTP::Options.new }
|
4
|
+
|
5
|
+
it "defaults to be empty" do
|
6
|
+
expect(opts.features).to be_empty
|
7
|
+
end
|
8
|
+
|
9
|
+
it "accepts plain symbols in array" do
|
10
|
+
opts2 = opts.with_features([:auto_inflate])
|
11
|
+
expect(opts.features).to be_empty
|
12
|
+
expect(opts2.features.keys).to eq([:auto_inflate])
|
13
|
+
expect(opts2.features[:auto_inflate]).
|
14
|
+
to be_instance_of(HTTP::Features::AutoInflate)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "accepts feature name with its options in array" do
|
18
|
+
opts2 = opts.with_features([{:auto_deflate => {:method => :deflate}}])
|
19
|
+
expect(opts.features).to be_empty
|
20
|
+
expect(opts2.features.keys).to eq([:auto_deflate])
|
21
|
+
expect(opts2.features[:auto_deflate]).
|
22
|
+
to be_instance_of(HTTP::Features::AutoDeflate)
|
23
|
+
expect(opts2.features[:auto_deflate].method).to eq("deflate")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "raises error for not supported features" do
|
27
|
+
expect { opts.with_features([:wrong_feature]) }.
|
28
|
+
to raise_error(HTTP::Error) { |error|
|
29
|
+
expect(error.message).to eq("Unsupported feature: wrong_feature")
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
@@ -24,7 +24,8 @@ RSpec.describe HTTP::Options, "merge" do
|
|
24
24
|
:body => "body-foo",
|
25
25
|
:json => {:foo => "foo"},
|
26
26
|
:headers => {:accept => "json", :foo => "foo"},
|
27
|
-
:proxy => {}
|
27
|
+
:proxy => {},
|
28
|
+
:features => {}
|
28
29
|
)
|
29
30
|
|
30
31
|
bar = HTTP::Options.new(
|
@@ -60,7 +61,8 @@ RSpec.describe HTTP::Options, "merge" do
|
|
60
61
|
:ssl_socket_class => described_class.default_ssl_socket_class,
|
61
62
|
:ssl_context => nil,
|
62
63
|
:cookies => {},
|
63
|
-
:encoding => nil
|
64
|
+
:encoding => nil,
|
65
|
+
:features => {}
|
64
66
|
)
|
65
67
|
end
|
66
68
|
end
|
@@ -5,7 +5,7 @@ RSpec.describe HTTP::Response::Body do
|
|
5
5
|
|
6
6
|
before { allow(connection).to receive(:readpartial) { chunks.shift } }
|
7
7
|
|
8
|
-
subject(:body) { described_class.new(connection, Encoding::UTF_8) }
|
8
|
+
subject(:body) { described_class.new(connection, connection, Encoding::UTF_8) }
|
9
9
|
|
10
10
|
it "streams bodies from responses" do
|
11
11
|
expect(subject.to_s).to eq("Hello, World!")
|
@@ -38,4 +38,32 @@ RSpec.describe HTTP::Response::Body do
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
41
|
+
|
42
|
+
context "when body is gzipped" do
|
43
|
+
let(:chunks) do
|
44
|
+
body = Zlib::Deflate.deflate("Hi, HTTP here ☺")
|
45
|
+
len = body.length
|
46
|
+
[String.new(body[0, len / 2]), String.new(body[(len / 2)..-1])]
|
47
|
+
end
|
48
|
+
subject(:body) do
|
49
|
+
inflater = HTTP::Response::Inflater.new(connection)
|
50
|
+
described_class.new(connection, inflater, Encoding::UTF_8)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "decodes body" do
|
54
|
+
expect(subject.to_s).to eq("Hi, HTTP here ☺")
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#readpartial" do
|
58
|
+
it "streams decoded body" do
|
59
|
+
[
|
60
|
+
"Hi, HTTP ",
|
61
|
+
String.new("here ☺").force_encoding("ASCII-8BIT"),
|
62
|
+
nil
|
63
|
+
].each do |part|
|
64
|
+
expect(subject.readpartial).to eq(part)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
41
69
|
end
|
data/spec/lib/http_spec.rb
CHANGED
@@ -410,4 +410,47 @@ RSpec.describe HTTP do
|
|
410
410
|
expect(socket_spy_class.setsockopt_calls).to eq([[Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1]])
|
411
411
|
end
|
412
412
|
end
|
413
|
+
|
414
|
+
describe ".use" do
|
415
|
+
it "turns on given feature" do
|
416
|
+
client = HTTP.use :auto_deflate
|
417
|
+
expect(client.default_options.features.keys).to eq [:auto_deflate]
|
418
|
+
end
|
419
|
+
|
420
|
+
context "with :auto_deflate" do
|
421
|
+
it "sends gzipped body" do
|
422
|
+
client = HTTP.use :auto_deflate
|
423
|
+
body = "Hello!"
|
424
|
+
response = client.post("#{dummy.endpoint}/echo-body", :body => body)
|
425
|
+
encoded = response.to_s
|
426
|
+
|
427
|
+
expect(Zlib::GzipReader.new(StringIO.new(encoded)).read).to eq body
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
context "with :auto_inflate" do
|
432
|
+
it "returns raw body when Content-Encoding type is missing" do
|
433
|
+
client = HTTP.use :auto_inflate
|
434
|
+
body = "Hello!"
|
435
|
+
response = client.post("#{dummy.endpoint}/encoded-body", :body => body)
|
436
|
+
expect(response.to_s).to eq("#{body}-raw")
|
437
|
+
end
|
438
|
+
|
439
|
+
it "returns decoded body" do
|
440
|
+
client = HTTP.use(:auto_inflate).headers("Accept-Encoding" => "gzip")
|
441
|
+
body = "Hello!"
|
442
|
+
response = client.post("#{dummy.endpoint}/encoded-body", :body => body)
|
443
|
+
|
444
|
+
expect(response.to_s).to eq("#{body}-gzipped")
|
445
|
+
end
|
446
|
+
|
447
|
+
it "returns deflated body" do
|
448
|
+
client = HTTP.use(:auto_inflate).headers("Accept-Encoding" => "deflate")
|
449
|
+
body = "Hello!"
|
450
|
+
response = client.post("#{dummy.endpoint}/encoded-body", :body => body)
|
451
|
+
|
452
|
+
expect(response.to_s).to eq("#{body}-deflated")
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
413
456
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# coding: utf-8
|
3
2
|
|
4
3
|
require "simplecov"
|
5
4
|
require "coveralls"
|
@@ -20,11 +19,6 @@ require "http"
|
|
20
19
|
require "rspec/its"
|
21
20
|
require "support/capture_warning"
|
22
21
|
|
23
|
-
# Are we in a flaky environment?
|
24
|
-
def flaky_env?
|
25
|
-
defined?(JRUBY_VERSION) && ENV["CI"]
|
26
|
-
end
|
27
|
-
|
28
22
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
29
23
|
RSpec.configure do |config|
|
30
24
|
config.expect_with :rspec do |expectations|
|
@@ -50,6 +44,7 @@ RSpec.configure do |config|
|
|
50
44
|
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
51
45
|
# get run.
|
52
46
|
config.filter_run :focus
|
47
|
+
config.filter_run_excluding :flaky if defined?(JRUBY_VERSION) && ENV["CI"]
|
53
48
|
config.run_all_when_everything_filtered = true
|
54
49
|
|
55
50
|
# Limits the available syntax to the non-monkey patched syntax that is recommended.
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# encoding: UTF-8
|
3
3
|
|
4
4
|
class DummyServer < WEBrick::HTTPServer
|
5
|
+
# rubocop:disable Metrics/ClassLength
|
5
6
|
class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
6
7
|
def self.sockets
|
7
8
|
@sockets ||= []
|
@@ -146,5 +147,26 @@ class DummyServer < WEBrick::HTTPServer
|
|
146
147
|
res.status = 200
|
147
148
|
res.body = req.body
|
148
149
|
end
|
150
|
+
|
151
|
+
post "/encoded-body" do |req, res|
|
152
|
+
res.status = 200
|
153
|
+
|
154
|
+
res.body = case req["Accept-Encoding"]
|
155
|
+
when "gzip" then
|
156
|
+
res["Content-Encoding"] = "gzip"
|
157
|
+
StringIO.open do |out|
|
158
|
+
Zlib::GzipWriter.wrap(out) do |gz|
|
159
|
+
gz.write "#{req.body}-gzipped"
|
160
|
+
gz.finish
|
161
|
+
out.tap(&:rewind).read
|
162
|
+
end
|
163
|
+
end
|
164
|
+
when "deflate" then
|
165
|
+
res["Content-Encoding"] = "deflate"
|
166
|
+
Zlib::Deflate.deflate("#{req.body}-deflated")
|
167
|
+
else
|
168
|
+
"#{req.body}-raw"
|
169
|
+
end
|
170
|
+
end
|
149
171
|
end
|
150
172
|
end
|
@@ -50,8 +50,7 @@ RSpec.shared_context "HTTP handling" do
|
|
50
50
|
context "of 0" do
|
51
51
|
let(:read_timeout) { 0 }
|
52
52
|
|
53
|
-
it "times out" do
|
54
|
-
skip "flaky environment" if flaky_env?
|
53
|
+
it "times out", :flaky do
|
55
54
|
expect { response }.to raise_error(HTTP::TimeoutError, /Read/i)
|
56
55
|
end
|
57
56
|
end
|
@@ -59,10 +58,7 @@ RSpec.shared_context "HTTP handling" do
|
|
59
58
|
context "of 2.5" do
|
60
59
|
let(:read_timeout) { 2.5 }
|
61
60
|
|
62
|
-
it "does not time out" do
|
63
|
-
# TODO: investigate sporadic JRuby timeouts on CI
|
64
|
-
skip "flaky environment" if flaky_env?
|
65
|
-
|
61
|
+
it "does not time out", :flaky do
|
66
62
|
expect { client.get("#{server.endpoint}/sleep").body.to_s }.to_not raise_error
|
67
63
|
end
|
68
64
|
end
|
@@ -96,10 +92,7 @@ RSpec.shared_context "HTTP handling" do
|
|
96
92
|
|
97
93
|
let(:read_timeout) { 2.5 }
|
98
94
|
|
99
|
-
it "does not timeout" do
|
100
|
-
# TODO: investigate sporadic JRuby timeouts on CI
|
101
|
-
skip "flaky environment" if flaky_env?
|
102
|
-
|
95
|
+
it "does not timeout", :flaky do
|
103
96
|
client.get("#{server.endpoint}/sleep").body.to_s
|
104
97
|
client.get("#{server.endpoint}/sleep").body.to_s
|
105
98
|
end
|
@@ -130,9 +123,7 @@ RSpec.shared_context "HTTP handling" do
|
|
130
123
|
end
|
131
124
|
|
132
125
|
context "on a mixed state" do
|
133
|
-
it "re-opens the connection" do
|
134
|
-
skip "flaky environment" if flaky_env?
|
135
|
-
|
126
|
+
it "re-opens the connection", :flaky do
|
136
127
|
first_socket_id = client.get("#{server.endpoint}/socket/1").body.to_s
|
137
128
|
|
138
129
|
client.instance_variable_set(:@state, :dirty)
|
@@ -163,9 +154,7 @@ RSpec.shared_context "HTTP handling" do
|
|
163
154
|
end
|
164
155
|
|
165
156
|
context "with a socket issue" do
|
166
|
-
it "transparently reopens" do
|
167
|
-
skip "flaky environment" if flaky_env?
|
168
|
-
|
157
|
+
it "transparently reopens", :flaky do
|
169
158
|
first_socket_id = client.get("#{server.endpoint}/socket").body.to_s
|
170
159
|
expect(first_socket_id).to_not eq("")
|
171
160
|
# Kill off the sockets we used
|
@@ -195,9 +184,7 @@ RSpec.shared_context "HTTP handling" do
|
|
195
184
|
context "when disabled" do
|
196
185
|
let(:options) { {} }
|
197
186
|
|
198
|
-
it "opens new sockets" do
|
199
|
-
skip "flaky environment" if flaky_env?
|
200
|
-
|
187
|
+
it "opens new sockets", :flaky do
|
201
188
|
expect(sockets_used).to_not include("")
|
202
189
|
expect(sockets_used.uniq.length).to eq(2)
|
203
190
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Arcieri
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2017-02-03 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: http_parser.rb
|
@@ -95,6 +95,7 @@ files:
|
|
95
95
|
- ".gitignore"
|
96
96
|
- ".rspec"
|
97
97
|
- ".rubocop.yml"
|
98
|
+
- ".ruby-version"
|
98
99
|
- ".travis.yml"
|
99
100
|
- ".yardopts"
|
100
101
|
- CHANGES.md
|
@@ -111,6 +112,9 @@ files:
|
|
111
112
|
- lib/http/connection.rb
|
112
113
|
- lib/http/content_type.rb
|
113
114
|
- lib/http/errors.rb
|
115
|
+
- lib/http/feature.rb
|
116
|
+
- lib/http/features/auto_deflate.rb
|
117
|
+
- lib/http/features/auto_inflate.rb
|
114
118
|
- lib/http/headers.rb
|
115
119
|
- lib/http/headers/known.rb
|
116
120
|
- lib/http/headers/mixin.rb
|
@@ -123,6 +127,7 @@ files:
|
|
123
127
|
- lib/http/request/writer.rb
|
124
128
|
- lib/http/response.rb
|
125
129
|
- lib/http/response/body.rb
|
130
|
+
- lib/http/response/inflater.rb
|
126
131
|
- lib/http/response/parser.rb
|
127
132
|
- lib/http/response/status.rb
|
128
133
|
- lib/http/response/status/reasons.rb
|
@@ -134,9 +139,12 @@ files:
|
|
134
139
|
- logo.png
|
135
140
|
- spec/lib/http/client_spec.rb
|
136
141
|
- spec/lib/http/content_type_spec.rb
|
142
|
+
- spec/lib/http/features/auto_deflate_spec.rb
|
143
|
+
- spec/lib/http/features/auto_inflate_spec.rb
|
137
144
|
- spec/lib/http/headers/mixin_spec.rb
|
138
145
|
- spec/lib/http/headers_spec.rb
|
139
146
|
- spec/lib/http/options/body_spec.rb
|
147
|
+
- spec/lib/http/options/features_spec.rb
|
140
148
|
- spec/lib/http/options/form_spec.rb
|
141
149
|
- spec/lib/http/options/headers_spec.rb
|
142
150
|
- spec/lib/http/options/json_spec.rb
|
@@ -183,16 +191,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
191
|
version: '0'
|
184
192
|
requirements: []
|
185
193
|
rubyforge_project:
|
186
|
-
rubygems_version: 2.
|
194
|
+
rubygems_version: 2.6.8
|
187
195
|
signing_key:
|
188
196
|
specification_version: 4
|
189
197
|
summary: HTTP should be easy
|
190
198
|
test_files:
|
191
199
|
- spec/lib/http/client_spec.rb
|
192
200
|
- spec/lib/http/content_type_spec.rb
|
201
|
+
- spec/lib/http/features/auto_deflate_spec.rb
|
202
|
+
- spec/lib/http/features/auto_inflate_spec.rb
|
193
203
|
- spec/lib/http/headers/mixin_spec.rb
|
194
204
|
- spec/lib/http/headers_spec.rb
|
195
205
|
- spec/lib/http/options/body_spec.rb
|
206
|
+
- spec/lib/http/options/features_spec.rb
|
196
207
|
- spec/lib/http/options/form_spec.rb
|
197
208
|
- spec/lib/http/options/headers_spec.rb
|
198
209
|
- spec/lib/http/options/json_spec.rb
|