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 +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -0
- data/.travis.yml +6 -6
- data/CHANGES.md +41 -0
- data/README.md +7 -4
- data/Rakefile +1 -1
- data/lib/http.rb +1 -0
- data/lib/http/chainable.rb +14 -19
- data/lib/http/client.rb +11 -4
- data/lib/http/connection.rb +4 -8
- data/lib/http/feature.rb +13 -0
- data/lib/http/features/auto_deflate.rb +20 -5
- data/lib/http/features/auto_inflate.rb +16 -6
- data/lib/http/features/instrumentation.rb +56 -0
- data/lib/http/features/logging.rb +55 -0
- data/lib/http/options.rb +26 -20
- data/lib/http/request.rb +7 -14
- data/lib/http/request/body.rb +5 -0
- data/lib/http/request/writer.rb +21 -7
- data/lib/http/response.rb +7 -15
- data/lib/http/timeout/global.rb +12 -14
- data/lib/http/timeout/per_operation.rb +5 -7
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/features/auto_inflate_spec.rb +17 -21
- data/spec/lib/http/features/instrumentation_spec.rb +56 -0
- data/spec/lib/http/features/logging_spec.rb +74 -0
- data/spec/lib/http/request/body_spec.rb +29 -0
- data/spec/lib/http/request/writer_spec.rb +20 -0
- data/spec/lib/http_spec.rb +25 -65
- data/spec/support/http_handling_shared.rb +60 -64
- metadata +8 -3
- data/.ruby-version +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18081275ee2fbb499b450316f301fc7cac4a35f3e5092fe453fd1fb089a2696b
|
4
|
+
data.tar.gz: 0a3c8b1dd2fa6bc5e68ed518552b4e4e3be8a517fe55fd6cb2b32650612d42ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35f037dc6661cc33147b40647c8faf78a1b881611672c806e529c75f28ab86fc4e9a37e592036a7f22bb5423203d0b6d62dfcc2335016424f8e4fdceccc7b65f
|
7
|
+
data.tar.gz: c7f299760ef14e5cf40f906520fabfaf2a845449686883f0a6512160f99fc0ff3ee73c8d3bab1b4d6337548e7bff158c23bc41bde843ba8f0dbc50649b131993
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
@@ -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.
|
21
|
-
- 2.
|
22
|
-
- 2.
|
23
|
-
- 2.
|
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.
|
29
|
+
- rvm: 2.5
|
30
30
|
env: SUITE="rubocop"
|
31
|
-
- rvm: 2.
|
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
|
[](https://coveralls.io/r/httprb/http)
|
7
7
|
[](https://github.com/httprb/http/blob/master/LICENSE.txt)
|
8
8
|
|
9
|
-
|
9
|
+
[Documentation]
|
10
10
|
|
11
|
-
|
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]
|
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
|
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
|
|
data/lib/http.rb
CHANGED
data/lib/http/chainable.rb
CHANGED
@@ -86,29 +86,24 @@ module HTTP
|
|
86
86
|
end
|
87
87
|
|
88
88
|
# @overload timeout(options = {})
|
89
|
-
#
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
data/lib/http/client.rb
CHANGED
@@ -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
|
|
data/lib/http/connection.rb
CHANGED
@@ -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
|
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
|
-
|
103
|
-
|
104
|
-
|
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
|
data/lib/http/feature.rb
CHANGED
@@ -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
|
-
|
31
|
-
|
32
|
-
|
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
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|