http 3.3.0 → 4.0.0

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
  SHA256:
3
- metadata.gz: 853087de359024f397422b0563f0232edfa247c2f201a060c10b7b8ea9aff00b
4
- data.tar.gz: b07ad1d88da70f9d76c5e272923dab3ce763077a8f8f7cb9ee6c37602acb6bb5
3
+ metadata.gz: 18081275ee2fbb499b450316f301fc7cac4a35f3e5092fe453fd1fb089a2696b
4
+ data.tar.gz: 0a3c8b1dd2fa6bc5e68ed518552b4e4e3be8a517fe55fd6cb2b32650612d42ca
5
5
  SHA512:
6
- metadata.gz: 654f47604b4e77c1138f9b82b5b902bf5d47f3bdc73979594633fc1d15c991eab5d933f001f1e6671bd432c498127f9b3ddb89cc2dc93fd3b792461961f1f1a0
7
- data.tar.gz: 4f8e97ea9830b1f36d5a0d636807e3f1296176640c2876d0f66c59d0643954e663d5c35ab14a538220b5f669195109a367b70d6ad281898ecb3830ce573cd485
6
+ metadata.gz: 35f037dc6661cc33147b40647c8faf78a1b881611672c806e529c75f28ab86fc4e9a37e592036a7f22bb5423203d0b6d62dfcc2335016424f8e4fdceccc7b65f
7
+ data.tar.gz: c7f299760ef14e5cf40f906520fabfaf2a845449686883f0a6512160f99fc0ff3ee73c8d3bab1b4d6337548e7bff158c23bc41bde843ba8f0dbc50649b131993
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  .bundle
3
3
  .config
4
4
  .rvmrc
5
+ .ruby-version
5
6
  .yardoc
6
7
  Gemfile.lock
7
8
  InstalledFiles
@@ -1,4 +1,5 @@
1
1
  AllCops:
2
+ TargetRubyVersion: 2.3
2
3
  DisplayCopNames: true
3
4
 
4
5
  ## Layout ######################################################################
@@ -17,18 +17,18 @@ env: JRUBY_OPTS="$JRUBY_OPTS --debug"
17
17
 
18
18
  rvm:
19
19
  # Include JRuby first because it takes the longest
20
- - jruby-9.1.13.0
21
- - 2.2
22
- - 2.3.4
23
- - 2.4.1
20
+ - jruby-9.1.16.0
21
+ - 2.3
22
+ - 2.4
23
+ - 2.5
24
24
 
25
25
  matrix:
26
26
  fast_finish: true
27
27
  include:
28
28
  # Only run RuboCop and Yardstick metrics on the latest Ruby
29
- - rvm: 2.4.1
29
+ - rvm: 2.5
30
30
  env: SUITE="rubocop"
31
- - rvm: 2.4.1
31
+ - rvm: 2.5
32
32
  env: SUITE="yardstick"
33
33
 
34
34
  branches:
data/CHANGES.md CHANGED
@@ -1,3 +1,44 @@
1
+ ## 4.0.0 (2018-10-15)
2
+
3
+ * [#468](https://github.com/httprb/http/pull/468)
4
+ Rewind `HTTP::Request::Body#source` once `#each` is complete.
5
+ ([@ixti])
6
+
7
+ * [#467](https://github.com/httprb/http/pull/467)
8
+ Drop Ruby 2.2 support.
9
+ ([@ixti])
10
+
11
+ * [#436](https://github.com/httprb/http/pull/436)
12
+ Raise ConnectionError when writing to socket fails.
13
+ ([@janko-m])
14
+
15
+ * [#438](https://github.com/httprb/http/pull/438)
16
+ Expose `HTTP::Request::Body#source`.
17
+ ([@janko-m])
18
+
19
+ * [#446](https://github.com/httprb/http/pull/446)
20
+ Simplify setting a timeout.
21
+ ([@mikegee])
22
+
23
+ * [#451](https://github.com/httprb/http/pull/451)
24
+ Reduce memory usage when reading response body.
25
+ ([@janko-m])
26
+
27
+ * [#458](https://github.com/httprb/http/pull/458)
28
+ Extract HTTP::Client#build_request method.
29
+ ([@tycoon])
30
+
31
+ * [#462](https://github.com/httprb/http/pull/462)
32
+ Fix HTTP::Request#headline to allow two leading slashes in path.
33
+ ([@scarfacedeb])
34
+
35
+ * [#454](https://github.com/httprb/http/pull/454)
36
+ [#464](https://github.com/httprb/http/pull/464)
37
+ [#384](https://github.com/httprb/http/issues/384)
38
+ Fix #readpartial not respecting max length argument.
39
+ ([@janko-m], [@marshall-lee])
40
+
41
+
1
42
  ## 3.3.0 (2018-04-25)
2
43
 
3
44
  This version backports some of the fixes and improvements made to development
data/README.md CHANGED
@@ -6,10 +6,11 @@
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 3.x **stable** branch. For the 4.x **development** branch, please see:_
9
+ [Documentation]
10
10
 
11
- https://github.com/httprb/http/
11
+ _NOTE: This is the 4.x **development** branch. For the 3.x **stable** branch, please see:_
12
12
 
13
+ https://github.com/httprb/http/tree/3-x-stable
13
14
 
14
15
  ## About
15
16
 
@@ -108,7 +109,7 @@ require "http"
108
109
 
109
110
  ## Documentation
110
111
 
111
- [Please see the http.rb wiki](https://github.com/httprb/http/wiki)
112
+ [Please see the http.rb wiki][documentation]
112
113
  for more detailed documentation and usage notes.
113
114
 
114
115
  The following API documentation is also available:
@@ -116,6 +117,8 @@ The following API documentation is also available:
116
117
  * [YARD API documentation](http://www.rubydoc.info/gems/http/frames)
117
118
  * [Chainable module (all chainable methods)](http://www.rubydoc.info/gems/http/HTTP/Chainable)
118
119
 
120
+ [documentation]: https://github.com/httprb/http/wiki
121
+
119
122
  ### Basic Usage
120
123
 
121
124
  Here's some simple examples to get you started:
@@ -161,9 +164,9 @@ and call `#readpartial` on it repeatedly until it returns `nil`:
161
164
  This library aims to support and is [tested against][travis] the following Ruby
162
165
  versions:
163
166
 
164
- * Ruby 2.2.x
165
167
  * Ruby 2.3.x
166
168
  * Ruby 2.4.x
169
+ * Ruby 2.5.x
167
170
  * JRuby 9.1.x.x
168
171
 
169
172
  If something doesn't work on one of these versions, it's a bug.
data/Rakefile CHANGED
@@ -25,7 +25,7 @@ task :generate_status_codes do
25
25
 
26
26
  url = "http://www.iana.org/assignments/http-status-codes/http-status-codes.xml"
27
27
  xml = Nokogiri::XML HTTP.get url
28
- arr = xml.xpath("//xmlns:record").reduce [] do |a, e|
28
+ arr = xml.xpath("//xmlns:record").reduce([]) do |a, e|
29
29
  code = e.xpath("xmlns:value").text.to_s
30
30
  desc = e.xpath("xmlns:description").text.to_s
31
31
 
@@ -10,6 +10,7 @@ require "http/chainable"
10
10
  require "http/client"
11
11
  require "http/connection"
12
12
  require "http/options"
13
+ require "http/feature"
13
14
  require "http/request"
14
15
  require "http/request/writer"
15
16
  require "http/response"
@@ -86,29 +86,24 @@ module HTTP
86
86
  end
87
87
 
88
88
  # @overload timeout(options = {})
89
- # Syntax sugar for `timeout(:per_operation, options)`
90
- # @overload timeout(klass, options = {})
91
- # Adds a timeout to the request.
92
- # @param [#to_sym] klass
93
- # either :null, :global, or :per_operation
89
+ # Adds per operation timeouts to the request
94
90
  # @param [Hash] options
95
91
  # @option options [Float] :read Read timeout
96
92
  # @option options [Float] :write Write timeout
97
93
  # @option options [Float] :connect Connect timeout
98
- def timeout(klass, options = {}) # rubocop:disable Style/OptionHash
99
- if klass.is_a? Hash
100
- options = klass
101
- klass = :per_operation
102
- end
103
-
104
- klass = case klass.to_sym
105
- when :null then HTTP::Timeout::Null
106
- when :global then HTTP::Timeout::Global
107
- when :per_operation then HTTP::Timeout::PerOperation
108
- else raise ArgumentError, "Unsupported Timeout class: #{klass}"
109
- end
110
-
111
- %i[read write connect].each do |k|
94
+ # @overload timeout(global_timeout)
95
+ # Adds a global timeout to the full request
96
+ # @param [Numeric] global_timeout
97
+ def timeout(options)
98
+ klass, options = case options
99
+ when Numeric then [HTTP::Timeout::Global, {:global => options}]
100
+ when Hash then [HTTP::Timeout::PerOperation, options]
101
+ when :null then [HTTP::Timeout::Null, {}]
102
+ else raise ArgumentError, "Use `.timeout(global_timeout_in_seconds)` or `.timeout(connect: x, write: y, read: z)`."
103
+
104
+ end
105
+
106
+ %i[global read write connect].each do |k|
112
107
  next unless options.key? k
113
108
  options["#{k}_timeout".to_sym] = options.delete k
114
109
  end
@@ -4,6 +4,7 @@ require "forwardable"
4
4
 
5
5
  require "http/form_data"
6
6
  require "http/options"
7
+ require "http/feature"
7
8
  require "http/headers"
8
9
  require "http/connection"
9
10
  require "http/redirector"
@@ -43,14 +44,17 @@ module HTTP
43
44
  body = make_request_body(opts, headers)
44
45
  proxy = opts.proxy
45
46
 
46
- HTTP::Request.new(
47
+ req = HTTP::Request.new(
47
48
  :verb => verb,
48
49
  :uri => uri,
49
50
  :headers => headers,
50
51
  :proxy => proxy,
51
- :body => body,
52
- :auto_deflate => opts.feature(:auto_deflate)
52
+ :body => body
53
53
  )
54
+
55
+ opts.features.inject(req) do |request, (_name, feature)|
56
+ feature.wrap_request(request)
57
+ end
54
58
  end
55
59
 
56
60
  # @!method persistent?
@@ -78,10 +82,13 @@ module HTTP
78
82
  :proxy_headers => @connection.proxy_response_headers,
79
83
  :connection => @connection,
80
84
  :encoding => options.encoding,
81
- :auto_inflate => options.feature(:auto_inflate),
82
85
  :uri => req.uri
83
86
  )
84
87
 
88
+ res = options.features.inject(res) do |response, (_name, feature)|
89
+ feature.wrap_response(response)
90
+ end
91
+
85
92
  @connection.finish_response if req.verb == :head
86
93
  @state = :clean
87
94
 
@@ -7,7 +7,7 @@ require "http/response/parser"
7
7
 
8
8
  module HTTP
9
9
  # A connection to the HTTP server
10
- class Connection # rubocop: disable Metrics/ClassLength
10
+ class Connection
11
11
  extend Forwardable
12
12
 
13
13
  # Allowed values for CONNECTION header
@@ -99,13 +99,9 @@ module HTTP
99
99
  # Reads data from socket up until headers are loaded
100
100
  # @return [void]
101
101
  def read_headers!
102
- loop do
103
- if read_more(BUFFER_SIZE) == :eof
104
- raise ConnectionError, "couldn't read response headers" unless @parser.headers?
105
- break
106
- elsif @parser.headers?
107
- break
108
- end
102
+ until @parser.headers?
103
+ result = read_more(BUFFER_SIZE)
104
+ raise ConnectionError, "couldn't read response headers" if result == :eof
109
105
  end
110
106
 
111
107
  set_keep_alive
@@ -5,5 +5,18 @@ module HTTP
5
5
  def initialize(opts = {}) # rubocop:disable Style/OptionHash
6
6
  @opts = opts
7
7
  end
8
+
9
+ def wrap_request(request)
10
+ request
11
+ end
12
+
13
+ def wrap_response(response)
14
+ response
15
+ end
8
16
  end
9
17
  end
18
+
19
+ require "http/features/auto_inflate"
20
+ require "http/features/auto_deflate"
21
+ require "http/features/logging"
22
+ require "http/features/instrumentation"
@@ -3,6 +3,8 @@
3
3
  require "zlib"
4
4
  require "tempfile"
5
5
 
6
+ require "http/request/body"
7
+
6
8
  module HTTP
7
9
  module Features
8
10
  class AutoDeflate < Feature
@@ -16,20 +18,33 @@ module HTTP
16
18
  raise Error, "Only gzip and deflate methods are supported" unless %w[gzip deflate].include?(@method)
17
19
  end
18
20
 
21
+ def wrap_request(request)
22
+ return request unless method
23
+
24
+ Request.new(
25
+ :version => request.version,
26
+ :verb => request.verb,
27
+ :uri => request.uri,
28
+ :headers => request.headers,
29
+ :proxy => request.proxy,
30
+ :body => deflated_body(request.body)
31
+ )
32
+ end
33
+
19
34
  def deflated_body(body)
20
35
  case method
21
36
  when "gzip"
22
37
  GzippedBody.new(body)
23
38
  when "deflate"
24
39
  DeflatedBody.new(body)
25
- else
26
- raise ArgumentError, "Unsupported deflate method: #{method}"
27
40
  end
28
41
  end
29
42
 
30
- class CompressedBody
31
- def initialize(body)
32
- @body = body
43
+ HTTP::Options.register_feature(:auto_deflate, self)
44
+
45
+ class CompressedBody < HTTP::Request::Body
46
+ def initialize(uncompressed_body)
47
+ @body = uncompressed_body
33
48
  @compressed = nil
34
49
  end
35
50
 
@@ -3,13 +3,23 @@
3
3
  module HTTP
4
4
  module Features
5
5
  class AutoInflate < Feature
6
- def stream_for(connection, response)
7
- if %w[deflate gzip x-gzip].include?(response.headers[:content_encoding])
8
- Response::Inflater.new(connection)
9
- else
10
- connection
11
- end
6
+ def wrap_response(response)
7
+ return response unless %w[deflate gzip x-gzip].include?(response.headers[:content_encoding])
8
+ Response.new(
9
+ :status => response.status,
10
+ :version => response.version,
11
+ :headers => response.headers,
12
+ :proxy_headers => response.proxy_headers,
13
+ :connection => response.connection,
14
+ :body => stream_for(response.connection)
15
+ )
12
16
  end
17
+
18
+ def stream_for(connection)
19
+ Response::Body.new(Response::Inflater.new(connection))
20
+ end
21
+
22
+ HTTP::Options.register_feature(:auto_inflate, self)
13
23
  end
14
24
  end
15
25
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP
4
+ module Features
5
+ # Instrument requests and responses. Expects an
6
+ # ActiveSupport::Notifications-compatible instrumenter. Defaults to use a
7
+ # namespace of 'http' which may be overridden with a `:namespace` param.
8
+ # Emits a single event like `"request.{namespace}"`, eg `"request.http"`.
9
+ # Be sure to specify the instrumenter when enabling the feature:
10
+ #
11
+ # HTTP
12
+ # .use(instrumentation: {instrumenter: ActiveSupport::Notifications})
13
+ # .get("https://example.com/")
14
+ #
15
+ class Instrumentation
16
+ attr_reader :instrumenter, :name
17
+
18
+ def initialize(instrumenter: NullInstrumenter.new, namespace: "http")
19
+ @instrumenter = instrumenter
20
+ @name = "request.#{namespace}"
21
+ end
22
+
23
+ def wrap_request(request)
24
+ instrumenter.instrument("start_#{name}", :request => request)
25
+ instrumenter.start(name, :request => request)
26
+ request
27
+ end
28
+
29
+ def wrap_response(response)
30
+ instrumenter.finish(name, :response => response)
31
+ response
32
+ end
33
+
34
+ HTTP::Options.register_feature(:instrumentation, self)
35
+
36
+ class NullInstrumenter
37
+ def instrument(name, payload = {})
38
+ start(name, payload)
39
+ begin
40
+ yield payload if block_given?
41
+ ensure
42
+ finish name, payload
43
+ end
44
+ end
45
+
46
+ def start(_name, _payload)
47
+ true
48
+ end
49
+
50
+ def finish(_name, _payload)
51
+ true
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP
4
+ module Features
5
+ # Log requests and responses. Request verb and uri, and Response status are
6
+ # logged at `info`, and the headers and bodies of both are logged at
7
+ # `debug`. Be sure to specify the logger when enabling the feature:
8
+ #
9
+ # HTTP.use(logging: {logger: Logger.new(STDOUT)}).get("https://example.com/")
10
+ #
11
+ class Logging < Feature
12
+ attr_reader :logger
13
+
14
+ def initialize(logger: NullLogger.new)
15
+ @logger = logger
16
+ end
17
+
18
+ def wrap_request(request)
19
+ logger.info { "> #{request.verb.to_s.upcase} #{request.uri}" }
20
+ logger.debug do
21
+ headers = request.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
22
+ body = request.body.source
23
+
24
+ headers + "\n\n" + body.to_s
25
+ end
26
+ request
27
+ end
28
+
29
+ def wrap_response(response)
30
+ logger.info { "< #{response.status}" }
31
+ logger.debug do
32
+ headers = response.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
33
+ body = response.body.to_s
34
+
35
+ headers + "\n\n" + body
36
+ end
37
+ response
38
+ end
39
+
40
+ class NullLogger
41
+ %w[fatal error warn info debug].each do |level|
42
+ define_method(level.to_sym) do |*_args|
43
+ nil
44
+ end
45
+
46
+ define_method(:"#{level}?") do
47
+ true
48
+ end
49
+ end
50
+ end
51
+
52
+ HTTP::Options.register_feature(:logging, self)
53
+ end
54
+ end
55
+ end