http 3.3.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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