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