http 2.1.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9ce75d295d182290642aa569b754914f89ed797f
4
- data.tar.gz: 353078321cc4c0e9134a5b10d655186474288770
3
+ metadata.gz: 66bda3b695b673507461ef5f59bc3d14a145fc78
4
+ data.tar.gz: 69c82f1484defda8becec34ef76caf0a59e2d378
5
5
  SHA512:
6
- metadata.gz: d4717d7e9b0a3d841fa31e533e69e35728943218c68455550ccacb845712c1a24e657385c7c4d228c6d3af5a5c68105a674647bef64fba1af33439041153db6a
7
- data.tar.gz: 6dd93161b0a552b4f7484ee2e935e54f57de0f0f6f3f1415250f35073ea90be854baa00322cc59a078a2f3f0a84732289de5ad44e63f782874df44a347770bb8
6
+ metadata.gz: c1149d111a7b16ea0d02c30a5a5126c393ea86cda749341a2056c17cf5c5004ee1bb3faf416c9bc483d28771c7c0ad9af349a866bd209a394460b579561142be
7
+ data.tar.gz: 7e3a46cf89a76a1ff557f40740f3aa851a8dd3e9bc44f64146f62db7db4839f21fb5efce9aaf88826945c85d97820bfa7574bc8aea21a0e6551d7195120d5e51
data/.rubocop.yml CHANGED
@@ -20,7 +20,7 @@ Metrics/LineLength:
20
20
 
21
21
  Metrics/MethodLength:
22
22
  CountComments: false
23
- Max: 22 # TODO: Lower to 15
23
+ Max: 25 # TODO: Lower to 15
24
24
 
25
25
  Metrics/ModuleLength:
26
26
  CountComments: false
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
- bundler_args: --without development doc
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.0
15
- - 2.3.1
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,29 @@
1
+ ## 2.2.2 (2017-04-27)
2
+
3
+ * [#404](https://github.com/httprb/http/issues/404),
4
+ [#405](https://github.com/httprb/http/pull/405)
5
+ Make keepalive timeout configurable.
6
+ ([@nestegg])
7
+
8
+
9
+ ## 2.2.1 (2017-02-06)
10
+
11
+ * [#395](https://github.com/httprb/http/issues/395)
12
+ Fix regression of API, that broke webmock integration.
13
+ ([@ixti])
14
+
15
+
16
+ ## 2.2.0 (2017-02-03)
17
+
18
+ * [#375](https://github.com/httprb/http/pull/375)
19
+ Add support for automatic Gzip/Inflate
20
+ ([@Bonias])
21
+
22
+ * [#390](https://github.com/httprb/http/pull/390)
23
+ Add REPORT to the list of valid HTTP verbs
24
+ ([@ixti])
25
+
26
+
1
27
  ## 2.1.0 (2016-11-08)
2
28
 
3
29
  * [#370](https://github.com/httprb/http/issues/370)
@@ -566,3 +592,5 @@ end
566
592
  [@jhbabon]: https://github.com/jhbabon
567
593
  [@britishtea]: https://github.com/britishtea
568
594
  [@janko-m]: https://github.com/janko-m
595
+ [@Bonias]: https://github.com/Bonias
596
+ [@nestegg]: https://github.com/nestegg
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", "~> 4", :require => false # Used by certificate_authority
26
+ gem "activemodel", :require => false # Used by certificate_authority
27
27
  end
28
28
 
29
29
  group :doc do
data/README.md CHANGED
@@ -6,6 +6,10 @@
6
6
  [![Coverage Status](https://coveralls.io/repos/httprb/http/badge.svg?branch=master)](https://coveralls.io/r/httprb/http)
7
7
  [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/httprb/http/blob/master/LICENSE.txt)
8
8
 
9
+ _NOTE: This is the 2.x **stable** branch. For the 3.x **development** branch, please see:_
10
+
11
+ https://github.com/httprb/http/
12
+
9
13
  ## About
10
14
 
11
15
  HTTP (The Gem! a.k.a. http.rb) is an easy-to-use client library for making requests
@@ -160,7 +164,7 @@ versions:
160
164
  * Ruby 2.1.x
161
165
  * Ruby 2.2.x
162
166
  * Ruby 2.3.x
163
- * JRuby 9.1.0.0
167
+ * JRuby 9.1.x.x
164
168
 
165
169
  If something doesn't work on one of these versions, it's a bug.
166
170
 
@@ -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
@@ -107,12 +109,13 @@ module HTTP
107
109
  )
108
110
  end
109
111
 
110
- # @overload persistent(host)
112
+ # @overload persistent(host, timeout: 5)
111
113
  # Flags as persistent
112
- # @param [String] host
113
- # @raise [Request::Error] if Host is invalid
114
+ # @param [String] host
115
+ # @option [Integer] timeout Keep alive timeout
116
+ # @raise [Request::Error] if Host is invalid
114
117
  # @return [HTTP::Client] Persistent client
115
- # @overload persistent(host, &block)
118
+ # @overload persistent(host, timeout: 5, &block)
116
119
  # Executes given block with persistent client and automatically closes
117
120
  # connection at the end of execution.
118
121
  #
@@ -136,8 +139,9 @@ module HTTP
136
139
  #
137
140
  # @yieldparam [HTTP::Client] client Persistent client
138
141
  # @return [Object] result of last expression in the block
139
- def persistent(host)
140
- p_client = branch default_options.with_persistent host
142
+ def persistent(host, timeout: 5)
143
+ options = {:keep_alive_timeout => timeout}
144
+ p_client = branch default_options.merge(options).with_persistent host
141
145
  return p_client unless block_given?
142
146
  yield p_client
143
147
  ensure
@@ -228,6 +232,14 @@ module HTTP
228
232
  branch default_options.with_nodelay(true)
229
233
  end
230
234
 
235
+ # Turn on given features. Available features are:
236
+ # * auto_inflate
237
+ # * auto_deflate
238
+ # @param features
239
+ def use(*features)
240
+ branch default_options.with_features(features)
241
+ end
242
+
231
243
  private
232
244
 
233
245
  # :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
- case
154
- when opts.body
155
- opts.body
156
- when opts.form
157
- form = HTTP::FormData.create opts.form
158
- headers[Headers::CONTENT_TYPE] ||= form.content_type
159
- headers[Headers::CONTENT_LENGTH] ||= form.content_length
160
- form.to_s
161
- when opts.json
162
- body = MimeType[:json].encode opts.json
163
- headers[Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name}"
164
- body
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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ module HTTP
3
+ class Feature
4
+ def initialize(opts = {})
5
+ @opts = opts
6
+ end
7
+ end
8
+ end
@@ -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
@@ -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
- # draft-dusseault-http-patch: PATCH Method for HTTP
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
@@ -15,8 +15,9 @@ module HTTP
15
15
  # @return [HTTP::Connection]
16
16
  attr_reader :connection
17
17
 
18
- def initialize(connection, encoding = Encoding::BINARY)
19
- @connection = connection
18
+ def initialize(stream, encoding = Encoding::BINARY)
19
+ @stream = stream
20
+ @connection = stream.is_a?(Inflater) ? stream.connection : stream
20
21
  @streaming = nil
21
22
  @contents = nil
22
23
  @encoding = encoding
@@ -25,7 +26,7 @@ module HTTP
25
26
  # (see HTTP::Client#readpartial)
26
27
  def readpartial(*args)
27
28
  stream!
28
- @connection.readpartial(*args)
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 = @connection.readpartial)
56
+ while (chunk = @stream.readpartial)
56
57
  @contents << chunk.force_encoding(encoding)
57
58
  end
58
59
  rescue
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+
5
+ module HTTP
6
+ class Response
7
+ class Inflater
8
+ attr_reader :connection
9
+
10
+ def initialize(connection)
11
+ @connection = connection
12
+ end
13
+
14
+ def readpartial(*args)
15
+ chunk = @connection.readpartial(*args)
16
+ if chunk
17
+ chunk = zstream.inflate(chunk)
18
+ elsif !zstream.closed?
19
+ zstream.finish
20
+ zstream.close
21
+ end
22
+ chunk
23
+ end
24
+
25
+ private
26
+
27
+ def zstream
28
+ @zstream ||= Zlib::Inflate.new(32 + Zlib::MAX_WBITS)
29
+ end
30
+ end
31
+ end
32
+ end
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"
@@ -47,8 +48,9 @@ module HTTP
47
48
  if opts.include?(:connection)
48
49
  connection = opts.fetch(:connection)
49
50
  encoding = opts[:encoding] || charset || Encoding::BINARY
51
+ stream = body_stream_for(connection, opts)
50
52
 
51
- @body = Response::Body.new(connection, encoding)
53
+ @body = Response::Body.new(stream, encoding)
52
54
  else
53
55
  @body = opts.fetch(:body)
54
56
  end
@@ -143,5 +145,15 @@ module HTTP
143
145
  def inspect
144
146
  "#<#{self.class}/#{@version} #{code} #{reason} #{headers.to_h.inspect}>"
145
147
  end
148
+
149
+ private
150
+
151
+ def body_stream_for(connection, opts)
152
+ if opts[:auto_inflate]
153
+ opts[:auto_inflate].stream_for(connection, self)
154
+ else
155
+ connection
156
+ end
157
+ end
146
158
  end
147
159
  end
data/lib/http/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP
4
- VERSION = "2.1.0".freeze
4
+ VERSION = "2.2.2".freeze
5
5
  end
@@ -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
@@ -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(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
@@ -288,6 +288,14 @@ RSpec.describe HTTP do
288
288
  end
289
289
  end
290
290
  end
291
+
292
+ context "with timeout specified" do
293
+ subject(:client) { HTTP.persistent host, :timeout => 100 }
294
+ it "sets keep_alive_timeout" do
295
+ options = client.default_options
296
+ expect(options.keep_alive_timeout).to eq(100)
297
+ end
298
+ end
291
299
  end
292
300
 
293
301
  describe ".timeout" do
@@ -410,4 +418,47 @@ RSpec.describe HTTP do
410
418
  expect(socket_spy_class.setsockopt_calls).to eq([[Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1]])
411
419
  end
412
420
  end
421
+
422
+ describe ".use" do
423
+ it "turns on given feature" do
424
+ client = HTTP.use :auto_deflate
425
+ expect(client.default_options.features.keys).to eq [:auto_deflate]
426
+ end
427
+
428
+ context "with :auto_deflate" do
429
+ it "sends gzipped body" do
430
+ client = HTTP.use :auto_deflate
431
+ body = "Hello!"
432
+ response = client.post("#{dummy.endpoint}/echo-body", :body => body)
433
+ encoded = response.to_s
434
+
435
+ expect(Zlib::GzipReader.new(StringIO.new(encoded)).read).to eq body
436
+ end
437
+ end
438
+
439
+ context "with :auto_inflate" do
440
+ it "returns raw body when Content-Encoding type is missing" do
441
+ client = HTTP.use :auto_inflate
442
+ body = "Hello!"
443
+ response = client.post("#{dummy.endpoint}/encoded-body", :body => body)
444
+ expect(response.to_s).to eq("#{body}-raw")
445
+ end
446
+
447
+ it "returns decoded body" do
448
+ client = HTTP.use(:auto_inflate).headers("Accept-Encoding" => "gzip")
449
+ body = "Hello!"
450
+ response = client.post("#{dummy.endpoint}/encoded-body", :body => body)
451
+
452
+ expect(response.to_s).to eq("#{body}-gzipped")
453
+ end
454
+
455
+ it "returns deflated body" do
456
+ client = HTTP.use(:auto_inflate).headers("Accept-Encoding" => "deflate")
457
+ body = "Hello!"
458
+ response = client.post("#{dummy.endpoint}/encoded-body", :body => body)
459
+
460
+ expect(response.to_s).to eq("#{body}-deflated")
461
+ end
462
+ end
463
+ end
413
464
  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.1.0
4
+ version: 2.2.2
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: 2016-11-09 00:00:00.000000000 Z
14
+ date: 2017-04-27 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.5.1
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