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 +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
|
[![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
|
-
|
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
|