http 5.0.0.pre → 5.0.0.pre2
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 +17 -1
- data/.travis.yml +6 -4
- data/CHANGES.md +83 -0
- data/Gemfile +2 -1
- data/README.md +7 -6
- data/http.gemspec +11 -4
- data/lib/http/chainable.rb +8 -3
- data/lib/http/client.rb +32 -34
- data/lib/http/connection.rb +5 -5
- data/lib/http/content_type.rb +2 -2
- data/lib/http/feature.rb +3 -0
- data/lib/http/features/auto_deflate.rb +13 -7
- data/lib/http/features/auto_inflate.rb +6 -5
- data/lib/http/features/normalize_uri.rb +17 -0
- data/lib/http/headers.rb +48 -11
- data/lib/http/headers/known.rb +3 -0
- data/lib/http/mime_type/adapter.rb +1 -1
- data/lib/http/mime_type/json.rb +1 -0
- data/lib/http/options.rb +4 -7
- data/lib/http/redirector.rb +3 -1
- data/lib/http/request.rb +32 -29
- data/lib/http/request/body.rb +26 -1
- data/lib/http/request/writer.rb +3 -2
- data/lib/http/response.rb +17 -15
- data/lib/http/response/body.rb +1 -0
- data/lib/http/response/parser.rb +20 -6
- data/lib/http/response/status.rb +2 -1
- data/lib/http/timeout/global.rb +1 -3
- data/lib/http/timeout/per_operation.rb +1 -0
- data/lib/http/uri.rb +13 -0
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +96 -14
- data/spec/lib/http/connection_spec.rb +8 -5
- data/spec/lib/http/features/auto_inflate_spec.rb +4 -2
- data/spec/lib/http/features/instrumentation_spec.rb +7 -6
- data/spec/lib/http/features/logging_spec.rb +6 -5
- data/spec/lib/http/headers_spec.rb +52 -17
- data/spec/lib/http/options/headers_spec.rb +1 -1
- data/spec/lib/http/options/merge_spec.rb +16 -16
- data/spec/lib/http/redirector_spec.rb +15 -1
- data/spec/lib/http/request/body_spec.rb +22 -0
- data/spec/lib/http/request/writer_spec.rb +13 -1
- data/spec/lib/http/request_spec.rb +5 -5
- data/spec/lib/http/response/parser_spec.rb +45 -0
- data/spec/lib/http/response/status_spec.rb +3 -3
- data/spec/lib/http/response_spec.rb +11 -22
- data/spec/lib/http_spec.rb +30 -1
- data/spec/support/black_hole.rb +1 -1
- data/spec/support/dummy_server.rb +6 -6
- data/spec/support/dummy_server/servlet.rb +8 -4
- data/spec/support/http_handling_shared.rb +4 -4
- data/spec/support/ssl_helper.rb +4 -4
- metadata +23 -16
data/lib/http/request/writer.rb
CHANGED
@@ -108,12 +108,13 @@ module HTTP
|
|
108
108
|
until data.empty?
|
109
109
|
length = @socket.write(data)
|
110
110
|
break unless data.bytesize > length
|
111
|
+
|
111
112
|
data = data.byteslice(length..-1)
|
112
113
|
end
|
113
114
|
rescue Errno::EPIPE
|
114
115
|
raise
|
115
|
-
rescue IOError, SocketError, SystemCallError =>
|
116
|
-
raise ConnectionError, "error writing to socket: #{
|
116
|
+
rescue IOError, SocketError, SystemCallError => e
|
117
|
+
raise ConnectionError, "error writing to socket: #{e}", e.backtrace
|
117
118
|
end
|
118
119
|
end
|
119
120
|
end
|
data/lib/http/response.rb
CHANGED
@@ -7,7 +7,6 @@ require "http/content_type"
|
|
7
7
|
require "http/mime_type"
|
8
8
|
require "http/response/status"
|
9
9
|
require "http/response/inflater"
|
10
|
-
require "http/uri"
|
11
10
|
require "http/cookie_jar"
|
12
11
|
require "time"
|
13
12
|
|
@@ -26,8 +25,8 @@ module HTTP
|
|
26
25
|
# @return [Body]
|
27
26
|
attr_reader :body
|
28
27
|
|
29
|
-
# @return [
|
30
|
-
attr_reader :
|
28
|
+
# @return [Request]
|
29
|
+
attr_reader :request
|
31
30
|
|
32
31
|
# @return [Hash]
|
33
32
|
attr_reader :proxy_headers
|
@@ -41,10 +40,10 @@ module HTTP
|
|
41
40
|
# @option opts [HTTP::Connection] :connection
|
42
41
|
# @option opts [String] :encoding Encoding to use when reading body
|
43
42
|
# @option opts [String] :body
|
44
|
-
# @option opts [
|
43
|
+
# @option opts [HTTP::Request] request
|
45
44
|
def initialize(opts)
|
46
45
|
@version = opts.fetch(:version)
|
47
|
-
@
|
46
|
+
@request = opts.fetch(:request)
|
48
47
|
@status = HTTP::Response::Status.new(opts.fetch(:status))
|
49
48
|
@headers = HTTP::Headers.coerce(opts[:headers] || {})
|
50
49
|
@proxy_headers = HTTP::Headers.coerce(opts[:proxy_headers] || {})
|
@@ -61,24 +60,28 @@ module HTTP
|
|
61
60
|
|
62
61
|
# @!method reason
|
63
62
|
# @return (see HTTP::Response::Status#reason)
|
64
|
-
def_delegator
|
63
|
+
def_delegator :@status, :reason
|
65
64
|
|
66
65
|
# @!method code
|
67
66
|
# @return (see HTTP::Response::Status#code)
|
68
|
-
def_delegator
|
67
|
+
def_delegator :@status, :code
|
69
68
|
|
70
69
|
# @!method to_s
|
71
70
|
# (see HTTP::Response::Body#to_s)
|
72
|
-
def_delegator
|
71
|
+
def_delegator :@body, :to_s
|
73
72
|
alias to_str to_s
|
74
73
|
|
75
74
|
# @!method readpartial
|
76
75
|
# (see HTTP::Response::Body#readpartial)
|
77
|
-
def_delegator
|
76
|
+
def_delegator :@body, :readpartial
|
78
77
|
|
79
78
|
# @!method connection
|
80
79
|
# (see HTTP::Response::Body#connection)
|
81
|
-
def_delegator
|
80
|
+
def_delegator :@body, :connection
|
81
|
+
|
82
|
+
# @!method uri
|
83
|
+
# @return (see HTTP::Request#uri)
|
84
|
+
def_delegator :@request, :uri
|
82
85
|
|
83
86
|
# Returns an Array ala Rack: `[status, headers, body]`
|
84
87
|
#
|
@@ -150,12 +153,11 @@ module HTTP
|
|
150
153
|
|
151
154
|
# Parse response body with corresponding MIME type adapter.
|
152
155
|
#
|
153
|
-
# @param [#to_s]
|
154
|
-
#
|
155
|
-
# @raise [HTTP::Error] if adapter not found
|
156
|
+
# @param type [#to_s] Parse as given MIME type.
|
157
|
+
# @raise (see MimeType.[])
|
156
158
|
# @return [Object]
|
157
|
-
def parse(
|
158
|
-
MimeType[
|
159
|
+
def parse(type)
|
160
|
+
MimeType[type].decode to_s
|
159
161
|
end
|
160
162
|
|
161
163
|
# Inspect a response
|
data/lib/http/response/body.rb
CHANGED
data/lib/http/response/parser.rb
CHANGED
@@ -49,14 +49,17 @@ module HTTP
|
|
49
49
|
#
|
50
50
|
|
51
51
|
def on_header_field(_response, field)
|
52
|
-
|
52
|
+
append_header if @reading_header_value
|
53
|
+
@field << field
|
53
54
|
end
|
54
55
|
|
55
56
|
def on_header_value(_response, value)
|
56
|
-
@
|
57
|
+
@reading_header_value = true
|
58
|
+
@field_value << value
|
57
59
|
end
|
58
60
|
|
59
61
|
def on_headers_complete(_reposse)
|
62
|
+
append_header if @reading_header_value
|
60
63
|
@finished[:headers] = true
|
61
64
|
end
|
62
65
|
|
@@ -89,15 +92,26 @@ module HTTP
|
|
89
92
|
def reset
|
90
93
|
@state.reset!
|
91
94
|
|
92
|
-
@finished
|
93
|
-
@headers
|
94
|
-
@
|
95
|
-
@
|
95
|
+
@finished = Hash.new(false)
|
96
|
+
@headers = HTTP::Headers.new
|
97
|
+
@reading_header_value = false
|
98
|
+
@field = +""
|
99
|
+
@field_value = +""
|
100
|
+
@chunk = nil
|
96
101
|
end
|
97
102
|
|
98
103
|
def finished?
|
99
104
|
@finished[:message]
|
100
105
|
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def append_header
|
110
|
+
@headers.add(@field, @field_value)
|
111
|
+
@reading_header_value = false
|
112
|
+
@field_value = +""
|
113
|
+
@field = +""
|
114
|
+
end
|
101
115
|
end
|
102
116
|
end
|
103
117
|
end
|
data/lib/http/response/status.rb
CHANGED
@@ -132,7 +132,7 @@ module HTTP
|
|
132
132
|
end
|
133
133
|
|
134
134
|
SYMBOLS.each do |code, symbol|
|
135
|
-
class_eval <<-RUBY, __FILE__, __LINE__
|
135
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
136
136
|
def #{symbol}? # def bad_request?
|
137
137
|
#{code} == code # 400 == code
|
138
138
|
end # end
|
@@ -141,6 +141,7 @@ module HTTP
|
|
141
141
|
|
142
142
|
def __setobj__(obj)
|
143
143
|
raise TypeError, "Expected #{obj.inspect} to respond to #to_i" unless obj.respond_to? :to_i
|
144
|
+
|
144
145
|
@code = obj.to_i
|
145
146
|
end
|
146
147
|
|
data/lib/http/timeout/global.rb
CHANGED
@@ -121,9 +121,7 @@ module HTTP
|
|
121
121
|
|
122
122
|
def log_time
|
123
123
|
@time_left -= (Time.now - @started)
|
124
|
-
if @time_left <= 0
|
125
|
-
raise TimeoutError, "Timed out after using the allocated #{@timeout} seconds"
|
126
|
-
end
|
124
|
+
raise TimeoutError, "Timed out after using the allocated #{@timeout} seconds" if @time_left <= 0
|
127
125
|
|
128
126
|
reset_timer
|
129
127
|
end
|
@@ -66,6 +66,7 @@ module HTTP
|
|
66
66
|
return result if result != :wait_readable
|
67
67
|
|
68
68
|
raise TimeoutError, "Read timed out after #{@read_timeout} seconds" if timeout
|
69
|
+
|
69
70
|
# marking the socket for timeout. Why is this not being raised immediately?
|
70
71
|
# it seems there is some race-condition on the network level between calling
|
71
72
|
# #read_nonblock and #wait_readable, in which #read_nonblock signalizes waiting
|
data/lib/http/uri.rb
CHANGED
@@ -26,6 +26,19 @@ module HTTP
|
|
26
26
|
# @private
|
27
27
|
HTTPS_SCHEME = "https"
|
28
28
|
|
29
|
+
# @private
|
30
|
+
NORMALIZER = lambda do |uri|
|
31
|
+
uri = HTTP::URI.parse uri
|
32
|
+
|
33
|
+
HTTP::URI.new(
|
34
|
+
:scheme => uri.normalized_scheme,
|
35
|
+
:authority => uri.normalized_authority,
|
36
|
+
:path => uri.normalized_path,
|
37
|
+
:query => uri.query,
|
38
|
+
:fragment => uri.normalized_fragment
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
29
42
|
# Parse the given URI string, returning an HTTP::URI object
|
30
43
|
#
|
31
44
|
# @param [HTTP::URI, String, #to_str] uri to parse
|
data/lib/http/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
1
|
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "support/http_handling_shared"
|
5
5
|
require "support/dummy_server"
|
@@ -10,7 +10,8 @@ RSpec.describe HTTP::Client do
|
|
10
10
|
|
11
11
|
StubbedClient = Class.new(HTTP::Client) do
|
12
12
|
def perform(request, options)
|
13
|
-
stubs
|
13
|
+
stubbed = stubs[request.uri]
|
14
|
+
stubbed ? stubbed.call(request) : super(request, options)
|
14
15
|
end
|
15
16
|
|
16
17
|
def stubs
|
@@ -27,20 +28,26 @@ RSpec.describe HTTP::Client do
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def redirect_response(location, status = 302)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
lambda do |request|
|
32
|
+
HTTP::Response.new(
|
33
|
+
:status => status,
|
34
|
+
:version => "1.1",
|
35
|
+
:headers => {"Location" => location},
|
36
|
+
:body => "",
|
37
|
+
:request => request
|
38
|
+
)
|
39
|
+
end
|
36
40
|
end
|
37
41
|
|
38
42
|
def simple_response(body, status = 200)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
lambda do |request|
|
44
|
+
HTTP::Response.new(
|
45
|
+
:status => status,
|
46
|
+
:version => "1.1",
|
47
|
+
:body => body,
|
48
|
+
:request => request
|
49
|
+
)
|
50
|
+
end
|
44
51
|
end
|
45
52
|
|
46
53
|
describe "following redirects" do
|
@@ -234,7 +241,7 @@ RSpec.describe HTTP::Client do
|
|
234
241
|
|
235
242
|
context "when :auto_deflate was specified" do
|
236
243
|
let(:headers) { {"Content-Length" => "12"} }
|
237
|
-
let(:client) { described_class.new :headers => headers, :features => {:auto_deflate => {}} }
|
244
|
+
let(:client) { described_class.new :headers => headers, :features => {:auto_deflate => {}}, :body => "foo" }
|
238
245
|
|
239
246
|
it "deletes Content-Length header" do
|
240
247
|
expect(client).to receive(:perform) do |req, _|
|
@@ -251,6 +258,73 @@ RSpec.describe HTTP::Client do
|
|
251
258
|
|
252
259
|
client.request(:get, "http://example.com/")
|
253
260
|
end
|
261
|
+
|
262
|
+
context "and there is no body" do
|
263
|
+
let(:client) { described_class.new :headers => headers, :features => {:auto_deflate => {}} }
|
264
|
+
|
265
|
+
it "doesn't set Content-Encoding header" do
|
266
|
+
expect(client).to receive(:perform) do |req, _|
|
267
|
+
expect(req.headers).not_to include "Content-Encoding"
|
268
|
+
end
|
269
|
+
|
270
|
+
client.request(:get, "http://example.com/")
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
context "Feature" do
|
276
|
+
let(:feature_class) do
|
277
|
+
Class.new(HTTP::Feature) do
|
278
|
+
attr_reader :captured_request, :captured_response, :captured_error
|
279
|
+
|
280
|
+
def wrap_request(request)
|
281
|
+
@captured_request = request
|
282
|
+
end
|
283
|
+
|
284
|
+
def wrap_response(response)
|
285
|
+
@captured_response = response
|
286
|
+
end
|
287
|
+
|
288
|
+
def on_error(request, error)
|
289
|
+
@captured_request = request
|
290
|
+
@captured_error = error
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
it "is given a chance to wrap the Request" do
|
295
|
+
feature_instance = feature_class.new
|
296
|
+
|
297
|
+
response = client.use(:test_feature => feature_instance).
|
298
|
+
request(:get, dummy.endpoint)
|
299
|
+
|
300
|
+
expect(response.code).to eq(200)
|
301
|
+
expect(feature_instance.captured_request.verb).to eq(:get)
|
302
|
+
expect(feature_instance.captured_request.uri.to_s).to eq(dummy.endpoint + "/")
|
303
|
+
end
|
304
|
+
|
305
|
+
it "is given a chance to wrap the Response" do
|
306
|
+
feature_instance = feature_class.new
|
307
|
+
|
308
|
+
response = client.use(:test_feature => feature_instance).
|
309
|
+
request(:get, dummy.endpoint)
|
310
|
+
|
311
|
+
expect(feature_instance.captured_response).to eq(response)
|
312
|
+
end
|
313
|
+
|
314
|
+
it "is given a chance to handle an error" do
|
315
|
+
sleep_url = "#{dummy.endpoint}/sleep"
|
316
|
+
feature_instance = feature_class.new
|
317
|
+
|
318
|
+
expect do
|
319
|
+
client.use(:test_feature => feature_instance).
|
320
|
+
timeout(0.2).
|
321
|
+
request(:post, sleep_url)
|
322
|
+
end.to raise_error(HTTP::TimeoutError)
|
323
|
+
|
324
|
+
expect(feature_instance.captured_error).to be_a(HTTP::TimeoutError)
|
325
|
+
expect(feature_instance.captured_request.verb).to eq(:post)
|
326
|
+
expect(feature_instance.captured_request.uri.to_s).to eq(sleep_url)
|
327
|
+
end
|
254
328
|
end
|
255
329
|
end
|
256
330
|
|
@@ -304,6 +378,14 @@ RSpec.describe HTTP::Client do
|
|
304
378
|
client.get(dummy.endpoint).to_s
|
305
379
|
end
|
306
380
|
|
381
|
+
it "provides access to the Request from the Response" do
|
382
|
+
unique_value = "20190424"
|
383
|
+
response = client.headers("X-Value" => unique_value).get(dummy.endpoint)
|
384
|
+
|
385
|
+
expect(response.request).to be_a(HTTP::Request)
|
386
|
+
expect(response.request.headers["X-Value"]).to eq(unique_value)
|
387
|
+
end
|
388
|
+
|
307
389
|
context "with HEAD request" do
|
308
390
|
it "does not iterates through body" do
|
309
391
|
expect_any_instance_of(HTTP::Connection).to_not receive(:readpartial)
|
@@ -3,9 +3,9 @@
|
|
3
3
|
RSpec.describe HTTP::Connection do
|
4
4
|
let(:req) do
|
5
5
|
HTTP::Request.new(
|
6
|
-
:verb
|
7
|
-
:uri
|
8
|
-
:headers
|
6
|
+
:verb => :get,
|
7
|
+
:uri => "http://example.com/",
|
8
|
+
:headers => {}
|
9
9
|
)
|
10
10
|
end
|
11
11
|
let(:socket) { double(:connect => nil) }
|
@@ -20,14 +20,17 @@ RSpec.describe HTTP::Connection do
|
|
20
20
|
<<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
|
21
21
|
| HTTP/1.1 200 OK
|
22
22
|
| Content-Type: text
|
23
|
+
| foo_bar: 123
|
23
24
|
|
|
24
25
|
RESPONSE
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
|
-
it "
|
29
|
+
it "populates headers collection, preserving casing" do
|
29
30
|
connection.read_headers!
|
30
|
-
expect(connection.headers).to eq("Content-Type" => "text")
|
31
|
+
expect(connection.headers).to eq("Content-Type" => "text", "foo_bar" => "123")
|
32
|
+
expect(connection.headers["Foo-Bar"]).to eq("123")
|
33
|
+
expect(connection.headers["foo_bar"]).to eq("123")
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
@@ -11,7 +11,8 @@ RSpec.describe HTTP::Features::AutoInflate do
|
|
11
11
|
:version => "1.1",
|
12
12
|
:status => 200,
|
13
13
|
:headers => headers,
|
14
|
-
:connection => connection
|
14
|
+
:connection => connection,
|
15
|
+
:request => HTTP::Request.new(:verb => :get, :uri => "http://example.com")
|
15
16
|
)
|
16
17
|
end
|
17
18
|
|
@@ -73,7 +74,8 @@ RSpec.describe HTTP::Features::AutoInflate do
|
|
73
74
|
:status => 200,
|
74
75
|
:headers => {:content_encoding => "gzip"},
|
75
76
|
:connection => connection,
|
76
|
-
:uri => "https://example.com"
|
77
|
+
:uri => "https://example.com",
|
78
|
+
:request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
|
77
79
|
)
|
78
80
|
end
|
79
81
|
|
@@ -7,10 +7,10 @@ RSpec.describe HTTP::Features::Instrumentation do
|
|
7
7
|
describe "logging the request" do
|
8
8
|
let(:request) do
|
9
9
|
HTTP::Request.new(
|
10
|
-
:verb
|
11
|
-
:uri
|
10
|
+
:verb => :post,
|
11
|
+
:uri => "https://example.com/",
|
12
12
|
:headers => {:accept => "application/json"},
|
13
|
-
:body
|
13
|
+
:body => '{"hello": "world!"}'
|
14
14
|
)
|
15
15
|
end
|
16
16
|
|
@@ -25,10 +25,11 @@ RSpec.describe HTTP::Features::Instrumentation do
|
|
25
25
|
let(:response) do
|
26
26
|
HTTP::Response.new(
|
27
27
|
:version => "1.1",
|
28
|
-
:uri
|
29
|
-
:status
|
28
|
+
:uri => "https://example.com",
|
29
|
+
:status => 200,
|
30
30
|
:headers => {:content_type => "application/json"},
|
31
|
-
:body
|
31
|
+
:body => '{"success": true}',
|
32
|
+
:request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
|
32
33
|
)
|
33
34
|
end
|
34
35
|
|