http 2.2.2 → 3.0.0.pre
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/.rubocop.yml +46 -13
- data/.travis.yml +17 -12
- data/CHANGES.md +25 -1
- data/Gemfile +11 -4
- data/Guardfile +2 -0
- data/README.md +4 -5
- data/Rakefile +14 -13
- data/http.gemspec +3 -1
- data/lib/http.rb +1 -0
- data/lib/http/chainable.rb +15 -14
- data/lib/http/client.rb +27 -24
- data/lib/http/connection.rb +6 -4
- data/lib/http/content_type.rb +1 -0
- data/lib/http/errors.rb +3 -2
- data/lib/http/feature.rb +2 -1
- data/lib/http/features/auto_deflate.rb +77 -20
- data/lib/http/features/auto_inflate.rb +2 -1
- data/lib/http/headers.rb +3 -2
- data/lib/http/headers/known.rb +23 -22
- data/lib/http/headers/mixin.rb +1 -0
- data/lib/http/mime_type.rb +1 -0
- data/lib/http/mime_type/adapter.rb +2 -1
- data/lib/http/mime_type/json.rb +2 -1
- data/lib/http/options.rb +15 -12
- data/lib/http/redirector.rb +4 -3
- data/lib/http/request.rb +25 -10
- data/lib/http/request/body.rb +67 -0
- data/lib/http/request/writer.rb +32 -37
- data/lib/http/response.rb +17 -2
- data/lib/http/response/body.rb +16 -12
- data/lib/http/response/parser.rb +1 -0
- data/lib/http/response/status.rb +1 -0
- data/lib/http/response/status/reasons.rb +1 -0
- data/lib/http/timeout/global.rb +1 -0
- data/lib/http/timeout/null.rb +2 -1
- data/lib/http/timeout/per_operation.rb +19 -6
- data/lib/http/uri.rb +8 -2
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +104 -4
- data/spec/lib/http/content_type_spec.rb +1 -0
- data/spec/lib/http/features/auto_deflate_spec.rb +32 -64
- data/spec/lib/http/features/auto_inflate_spec.rb +1 -0
- data/spec/lib/http/headers/mixin_spec.rb +1 -0
- data/spec/lib/http/headers_spec.rb +36 -35
- data/spec/lib/http/options/body_spec.rb +1 -0
- data/spec/lib/http/options/features_spec.rb +1 -0
- data/spec/lib/http/options/form_spec.rb +1 -0
- data/spec/lib/http/options/headers_spec.rb +2 -1
- data/spec/lib/http/options/json_spec.rb +1 -0
- data/spec/lib/http/options/new_spec.rb +2 -1
- data/spec/lib/http/options/proxy_spec.rb +1 -0
- data/spec/lib/http/options_spec.rb +1 -0
- data/spec/lib/http/redirector_spec.rb +1 -0
- data/spec/lib/http/request/body_spec.rb +138 -0
- data/spec/lib/http/request/writer_spec.rb +44 -74
- data/spec/lib/http/request_spec.rb +14 -0
- data/spec/lib/http/response/body_spec.rb +20 -4
- data/spec/lib/http/response/status_spec.rb +27 -26
- data/spec/lib/http/response_spec.rb +10 -0
- data/spec/lib/http/uri_spec.rb +11 -0
- data/spec/lib/http_spec.rb +18 -6
- data/spec/regression_specs.rb +1 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/black_hole.rb +9 -2
- data/spec/support/capture_warning.rb +1 -0
- data/spec/support/dummy_server.rb +2 -1
- data/spec/support/dummy_server/servlet.rb +1 -1
- data/spec/support/fakeio.rb +21 -0
- data/spec/support/http_handling_shared.rb +1 -0
- data/spec/support/proxy_server.rb +1 -0
- data/spec/support/servers/config.rb +1 -0
- data/spec/support/servers/runner.rb +1 -0
- data/spec/support/ssl_helper.rb +3 -2
- metadata +20 -9
data/lib/http/client.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "forwardable"
|
3
4
|
|
4
5
|
require "http/form_data"
|
@@ -23,7 +24,7 @@ module HTTP
|
|
23
24
|
end
|
24
25
|
|
25
26
|
# Make an HTTP request
|
26
|
-
def request(verb, uri, opts = {})
|
27
|
+
def request(verb, uri, opts = {}) # rubocop:disable Style/OptionHash
|
27
28
|
opts = @default_options.merge(opts)
|
28
29
|
uri = make_request_uri(uri, opts)
|
29
30
|
headers = make_request_headers(opts)
|
@@ -31,11 +32,12 @@ module HTTP
|
|
31
32
|
proxy = opts.proxy
|
32
33
|
|
33
34
|
req = HTTP::Request.new(
|
34
|
-
:verb
|
35
|
-
:uri
|
36
|
-
:headers
|
37
|
-
:proxy
|
38
|
-
:body
|
35
|
+
:verb => verb,
|
36
|
+
:uri => uri,
|
37
|
+
:headers => headers,
|
38
|
+
:proxy => proxy,
|
39
|
+
:body => body,
|
40
|
+
:auto_deflate => opts.feature(:auto_deflate)
|
39
41
|
)
|
40
42
|
|
41
43
|
res = perform(req, opts)
|
@@ -146,29 +148,30 @@ module HTTP
|
|
146
148
|
headers[Headers::COOKIE] = cookies
|
147
149
|
end
|
148
150
|
|
151
|
+
if (auto_deflate = opts.feature(:auto_deflate))
|
152
|
+
# We need to delete Content-Length header. It will be set automatically
|
153
|
+
# by HTTP::Request::Writer
|
154
|
+
headers.delete(Headers::CONTENT_LENGTH)
|
155
|
+
|
156
|
+
headers[Headers::CONTENT_ENCODING] = auto_deflate.method
|
157
|
+
end
|
158
|
+
|
149
159
|
headers
|
150
160
|
end
|
151
161
|
|
152
162
|
# Create the request body object to send
|
153
163
|
def make_request_body(opts, headers)
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
164
|
+
case
|
165
|
+
when opts.body
|
166
|
+
opts.body
|
167
|
+
when opts.form
|
168
|
+
form = HTTP::FormData.create opts.form
|
169
|
+
headers[Headers::CONTENT_TYPE] ||= form.content_type
|
170
|
+
form
|
171
|
+
when opts.json
|
172
|
+
body = MimeType[:json].encode opts.json
|
173
|
+
headers[Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name}"
|
174
|
+
body
|
172
175
|
end
|
173
176
|
end
|
174
177
|
end
|
data/lib/http/connection.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "forwardable"
|
3
4
|
|
4
5
|
require "http/headers"
|
@@ -10,17 +11,17 @@ module HTTP
|
|
10
11
|
extend Forwardable
|
11
12
|
|
12
13
|
# Allowed values for CONNECTION header
|
13
|
-
KEEP_ALIVE = "Keep-Alive"
|
14
|
-
CLOSE = "close"
|
14
|
+
KEEP_ALIVE = "Keep-Alive"
|
15
|
+
CLOSE = "close"
|
15
16
|
|
16
17
|
# Attempt to read this much data
|
17
18
|
BUFFER_SIZE = 16_384
|
18
19
|
|
19
20
|
# HTTP/1.0
|
20
|
-
HTTP_1_0 = "1.0"
|
21
|
+
HTTP_1_0 = "1.0"
|
21
22
|
|
22
23
|
# HTTP/1.1
|
23
|
-
HTTP_1_1 = "1.1"
|
24
|
+
HTTP_1_1 = "1.1"
|
24
25
|
|
25
26
|
# Returned after HTTP CONNECT (via proxy)
|
26
27
|
attr_reader :proxy_response_headers
|
@@ -211,6 +212,7 @@ module HTTP
|
|
211
212
|
|
212
213
|
value = @socket.readpartial(size)
|
213
214
|
if value == :eof
|
215
|
+
@parser << ""
|
214
216
|
:eof
|
215
217
|
elsif value
|
216
218
|
@parser << value
|
data/lib/http/content_type.rb
CHANGED
data/lib/http/errors.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module HTTP
|
3
4
|
# Generic error
|
4
5
|
class Error < StandardError; end
|
@@ -18,6 +19,6 @@ module HTTP
|
|
18
19
|
# Generic Timeout error
|
19
20
|
class TimeoutError < Error; end
|
20
21
|
|
21
|
-
# Header
|
22
|
-
class
|
22
|
+
# Header value is of unexpected format (similar to Net::HTTPHeaderSyntaxError)
|
23
|
+
class HeaderError < Error; end
|
23
24
|
end
|
data/lib/http/feature.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "zlib"
|
4
|
+
require "tempfile"
|
4
5
|
|
5
6
|
module HTTP
|
6
7
|
module Features
|
@@ -12,32 +13,88 @@ module HTTP
|
|
12
13
|
|
13
14
|
@method = @opts.key?(:method) ? @opts[:method].to_s : "gzip"
|
14
15
|
|
15
|
-
raise Error, "Only gzip and deflate methods are supported" unless %w
|
16
|
+
raise Error, "Only gzip and deflate methods are supported" unless %w[gzip deflate].include?(@method)
|
16
17
|
end
|
17
18
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
19
|
+
def deflated_body(body)
|
20
|
+
case method
|
21
|
+
when "gzip"
|
22
|
+
GzippedBody.new(body)
|
23
|
+
when "deflate"
|
24
|
+
DeflatedBody.new(body)
|
25
|
+
else
|
26
|
+
raise ArgumentError, "Unsupported deflate method: #{method}"
|
27
|
+
end
|
28
|
+
end
|
21
29
|
|
22
|
-
|
23
|
-
|
24
|
-
|
30
|
+
class CompressedBody
|
31
|
+
def initialize(body)
|
32
|
+
@body = body
|
33
|
+
@compressed = nil
|
34
|
+
end
|
25
35
|
|
26
|
-
|
36
|
+
def size
|
37
|
+
compress_all! unless @compressed
|
38
|
+
@compressed.size
|
39
|
+
end
|
27
40
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
41
|
+
def each(&block)
|
42
|
+
return to_enum __method__ unless block
|
43
|
+
|
44
|
+
if @compressed
|
45
|
+
compressed_each(&block)
|
46
|
+
else
|
47
|
+
compress(&block)
|
36
48
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
49
|
+
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def compressed_each
|
56
|
+
while (data = @compressed.read(Connection::BUFFER_SIZE))
|
57
|
+
yield data
|
58
|
+
end
|
59
|
+
ensure
|
60
|
+
@compressed.close!
|
61
|
+
end
|
62
|
+
|
63
|
+
def compress_all!
|
64
|
+
@compressed = Tempfile.new("http-compressed_body", :binmode => true)
|
65
|
+
compress { |data| @compressed.write(data) }
|
66
|
+
@compressed.rewind
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class GzippedBody < CompressedBody
|
71
|
+
def compress(&block)
|
72
|
+
gzip = Zlib::GzipWriter.new(BlockIO.new(block))
|
73
|
+
@body.each { |chunk| gzip.write(chunk) }
|
74
|
+
ensure
|
75
|
+
gzip.finish
|
76
|
+
end
|
77
|
+
|
78
|
+
class BlockIO
|
79
|
+
def initialize(block)
|
80
|
+
@block = block
|
81
|
+
end
|
82
|
+
|
83
|
+
def write(data)
|
84
|
+
@block.call(data)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class DeflatedBody < CompressedBody
|
90
|
+
def compress
|
91
|
+
deflater = Zlib::Deflate.new
|
92
|
+
|
93
|
+
@body.each { |chunk| yield deflater.deflate(chunk) }
|
94
|
+
|
95
|
+
yield deflater.finish
|
96
|
+
ensure
|
97
|
+
deflater.close
|
41
98
|
end
|
42
99
|
end
|
43
100
|
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module HTTP
|
3
4
|
module Features
|
4
5
|
class AutoInflate < Feature
|
5
6
|
def stream_for(connection, response)
|
6
|
-
if %w
|
7
|
+
if %w[deflate gzip x-gzip].include?(response.headers[:content_encoding])
|
7
8
|
Response::Inflater.new(connection)
|
8
9
|
else
|
9
10
|
connection
|
data/lib/http/headers.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "forwardable"
|
3
4
|
|
4
5
|
require "http/errors"
|
@@ -196,7 +197,7 @@ module HTTP
|
|
196
197
|
# Transforms `name` to canonical HTTP header capitalization
|
197
198
|
#
|
198
199
|
# @param [String] name
|
199
|
-
# @raise [
|
200
|
+
# @raise [HeaderError] if normalized name does not
|
200
201
|
# match {HEADER_NAME_RE}
|
201
202
|
# @return [String] canonical HTTP header name
|
202
203
|
def normalize_header(name)
|
@@ -206,7 +207,7 @@ module HTTP
|
|
206
207
|
|
207
208
|
return normalized if normalized =~ COMPLIANT_NAME_RE
|
208
209
|
|
209
|
-
raise
|
210
|
+
raise HeaderError, "Invalid HTTP header field name: #{name.inspect}"
|
210
211
|
end
|
211
212
|
end
|
212
213
|
end
|
data/lib/http/headers/known.rb
CHANGED
@@ -1,83 +1,84 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module HTTP
|
3
4
|
class Headers
|
4
5
|
# Content-Types that are acceptable for the response.
|
5
|
-
ACCEPT = "Accept"
|
6
|
+
ACCEPT = "Accept"
|
6
7
|
|
7
8
|
# The age the object has been in a proxy cache in seconds.
|
8
|
-
AGE = "Age"
|
9
|
+
AGE = "Age"
|
9
10
|
|
10
11
|
# Authentication credentials for HTTP authentication.
|
11
|
-
AUTHORIZATION = "Authorization"
|
12
|
+
AUTHORIZATION = "Authorization"
|
12
13
|
|
13
14
|
# Used to specify directives that must be obeyed by all caching mechanisms
|
14
15
|
# along the request-response chain.
|
15
|
-
CACHE_CONTROL = "Cache-Control"
|
16
|
+
CACHE_CONTROL = "Cache-Control"
|
16
17
|
|
17
18
|
# An HTTP cookie previously sent by the server with Set-Cookie.
|
18
|
-
COOKIE = "Cookie"
|
19
|
+
COOKIE = "Cookie"
|
19
20
|
|
20
21
|
# Control options for the current connection and list
|
21
22
|
# of hop-by-hop request fields.
|
22
|
-
CONNECTION = "Connection"
|
23
|
+
CONNECTION = "Connection"
|
23
24
|
|
24
25
|
# The length of the request body in octets (8-bit bytes).
|
25
|
-
CONTENT_LENGTH = "Content-Length"
|
26
|
+
CONTENT_LENGTH = "Content-Length"
|
26
27
|
|
27
28
|
# The MIME type of the body of the request
|
28
29
|
# (used with POST and PUT requests).
|
29
|
-
CONTENT_TYPE = "Content-Type"
|
30
|
+
CONTENT_TYPE = "Content-Type"
|
30
31
|
|
31
32
|
# The date and time that the message was sent (in "HTTP-date" format as
|
32
33
|
# defined by RFC 7231 Date/Time Formats).
|
33
|
-
DATE = "Date"
|
34
|
+
DATE = "Date"
|
34
35
|
|
35
36
|
# An identifier for a specific version of a resource,
|
36
37
|
# often a message digest.
|
37
|
-
ETAG = "ETag"
|
38
|
+
ETAG = "ETag"
|
38
39
|
|
39
40
|
# Gives the date/time after which the response is considered stale (in
|
40
41
|
# "HTTP-date" format as defined by RFC 7231).
|
41
|
-
EXPIRES = "Expires"
|
42
|
+
EXPIRES = "Expires"
|
42
43
|
|
43
44
|
# The domain name of the server (for virtual hosting), and the TCP port
|
44
45
|
# number on which the server is listening. The port number may be omitted
|
45
46
|
# if the port is the standard port for the service requested.
|
46
|
-
HOST = "Host"
|
47
|
+
HOST = "Host"
|
47
48
|
|
48
49
|
# Allows a 304 Not Modified to be returned if content is unchanged.
|
49
|
-
IF_MODIFIED_SINCE = "If-Modified-Since"
|
50
|
+
IF_MODIFIED_SINCE = "If-Modified-Since"
|
50
51
|
|
51
52
|
# Allows a 304 Not Modified to be returned if content is unchanged.
|
52
|
-
IF_NONE_MATCH = "If-None-Match"
|
53
|
+
IF_NONE_MATCH = "If-None-Match"
|
53
54
|
|
54
55
|
# The last modified date for the requested object (in "HTTP-date" format as
|
55
56
|
# defined by RFC 7231).
|
56
|
-
LAST_MODIFIED = "Last-Modified"
|
57
|
+
LAST_MODIFIED = "Last-Modified"
|
57
58
|
|
58
59
|
# Used in redirection, or when a new resource has been created.
|
59
|
-
LOCATION = "Location"
|
60
|
+
LOCATION = "Location"
|
60
61
|
|
61
62
|
# Authorization credentials for connecting to a proxy.
|
62
|
-
PROXY_AUTHORIZATION = "Proxy-Authorization"
|
63
|
+
PROXY_AUTHORIZATION = "Proxy-Authorization"
|
63
64
|
|
64
65
|
# An HTTP cookie.
|
65
|
-
SET_COOKIE = "Set-Cookie"
|
66
|
+
SET_COOKIE = "Set-Cookie"
|
66
67
|
|
67
68
|
# The form of encoding used to safely transfer the entity to the user.
|
68
69
|
# Currently defined methods are: chunked, compress, deflate, gzip, identity.
|
69
|
-
TRANSFER_ENCODING = "Transfer-Encoding"
|
70
|
+
TRANSFER_ENCODING = "Transfer-Encoding"
|
70
71
|
|
71
72
|
# Indicates what additional content codings have been applied to the
|
72
73
|
# entity-body.
|
73
|
-
CONTENT_ENCODING = "Content-Encoding"
|
74
|
+
CONTENT_ENCODING = "Content-Encoding"
|
74
75
|
|
75
76
|
# The user agent string of the user agent.
|
76
|
-
USER_AGENT = "User-Agent"
|
77
|
+
USER_AGENT = "User-Agent"
|
77
78
|
|
78
79
|
# Tells downstream proxies how to match future request headers to decide
|
79
80
|
# whether the cached response can be used rather than requesting a fresh
|
80
81
|
# one from the origin server.
|
81
|
-
VARY = "Vary"
|
82
|
+
VARY = "Vary"
|
82
83
|
end
|
83
84
|
end
|
data/lib/http/headers/mixin.rb
CHANGED
data/lib/http/mime_type.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "forwardable"
|
3
4
|
require "singleton"
|
4
5
|
|
@@ -13,7 +14,7 @@ module HTTP
|
|
13
14
|
def_delegators :instance, :encode, :decode
|
14
15
|
end
|
15
16
|
|
16
|
-
%w
|
17
|
+
%w[encode decode].each do |operation|
|
17
18
|
class_eval <<-RUBY, __FILE__, __LINE__
|
18
19
|
def #{operation}(*)
|
19
20
|
fail Error, "\#{self.class} does not supports ##{operation}"
|
data/lib/http/mime_type/json.rb
CHANGED