http 5.0.0.pre2 → 5.0.2
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/.github/workflows/ci.yml +65 -0
- data/.gitignore +6 -10
- data/.rspec +0 -4
- data/.rubocop/layout.yml +8 -0
- data/.rubocop/style.yml +32 -0
- data/.rubocop.yml +7 -124
- data/.rubocop_todo.yml +192 -0
- data/CHANGES.md +114 -1
- data/Gemfile +18 -11
- data/LICENSE.txt +1 -1
- data/README.md +13 -16
- data/Rakefile +2 -10
- data/http.gemspec +3 -3
- data/lib/http/chainable.rb +15 -14
- data/lib/http/client.rb +26 -15
- data/lib/http/connection.rb +7 -3
- data/lib/http/content_type.rb +10 -5
- data/lib/http/feature.rb +1 -1
- data/lib/http/features/auto_inflate.rb +0 -2
- data/lib/http/features/instrumentation.rb +1 -1
- data/lib/http/features/logging.rb +19 -21
- data/lib/http/headers.rb +3 -3
- data/lib/http/mime_type/adapter.rb +2 -0
- data/lib/http/options.rb +2 -2
- data/lib/http/redirector.rb +1 -1
- data/lib/http/request/writer.rb +5 -1
- data/lib/http/request.rb +22 -5
- data/lib/http/response/body.rb +5 -4
- data/lib/http/response/inflater.rb +1 -1
- data/lib/http/response/parser.rb +74 -62
- data/lib/http/response/status.rb +2 -2
- data/lib/http/response.rb +22 -4
- data/lib/http/timeout/global.rb +41 -35
- data/lib/http/timeout/null.rb +2 -1
- data/lib/http/timeout/per_operation.rb +56 -59
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +109 -41
- data/spec/lib/http/features/auto_inflate_spec.rb +0 -1
- data/spec/lib/http/features/instrumentation_spec.rb +21 -16
- data/spec/lib/http/features/logging_spec.rb +2 -5
- data/spec/lib/http/headers_spec.rb +3 -3
- data/spec/lib/http/redirector_spec.rb +44 -0
- data/spec/lib/http/request/writer_spec.rb +12 -1
- data/spec/lib/http/response/body_spec.rb +5 -5
- data/spec/lib/http/response/parser_spec.rb +30 -1
- data/spec/lib/http/response_spec.rb +62 -10
- data/spec/lib/http_spec.rb +20 -2
- data/spec/spec_helper.rb +21 -21
- data/spec/support/black_hole.rb +1 -1
- data/spec/support/dummy_server/servlet.rb +14 -2
- data/spec/support/dummy_server.rb +1 -1
- data/spec/support/fuubar.rb +21 -0
- data/spec/support/simplecov.rb +19 -0
- metadata +23 -17
- data/.coveralls.yml +0 -1
- data/.travis.yml +0 -38
data/lib/http/response/status.rb
CHANGED
@@ -58,7 +58,7 @@ module HTTP
|
|
58
58
|
# SYMBOLS[418] # => :im_a_teapot
|
59
59
|
#
|
60
60
|
# @return [Hash<Fixnum => Symbol>]
|
61
|
-
SYMBOLS =
|
61
|
+
SYMBOLS = REASONS.transform_values { |v| symbolize(v) }.freeze
|
62
62
|
|
63
63
|
# Reversed {SYMBOLS} map.
|
64
64
|
#
|
@@ -69,7 +69,7 @@ module HTTP
|
|
69
69
|
# SYMBOL_CODES[:im_a_teapot] # => 418
|
70
70
|
#
|
71
71
|
# @return [Hash<Symbol => Fixnum>]
|
72
|
-
SYMBOL_CODES =
|
72
|
+
SYMBOL_CODES = SYMBOLS.map { |k, v| [v, k] }.to_h.freeze
|
73
73
|
|
74
74
|
# @return [Fixnum] status code
|
75
75
|
attr_reader :code
|
data/lib/http/response.rb
CHANGED
@@ -40,10 +40,11 @@ module HTTP
|
|
40
40
|
# @option opts [HTTP::Connection] :connection
|
41
41
|
# @option opts [String] :encoding Encoding to use when reading body
|
42
42
|
# @option opts [String] :body
|
43
|
-
# @option opts [HTTP::Request] request
|
43
|
+
# @option opts [HTTP::Request] request The request this is in response to.
|
44
|
+
# @option opts [String] :uri (DEPRECATED) used to populate a missing request
|
44
45
|
def initialize(opts)
|
45
46
|
@version = opts.fetch(:version)
|
46
|
-
@request = opts
|
47
|
+
@request = init_request(opts)
|
47
48
|
@status = HTTP::Response::Status.new(opts.fetch(:status))
|
48
49
|
@headers = HTTP::Headers.coerce(opts[:headers] || {})
|
49
50
|
@proxy_headers = HTTP::Headers.coerce(opts[:proxy_headers] || {})
|
@@ -156,13 +157,30 @@ module HTTP
|
|
156
157
|
# @param type [#to_s] Parse as given MIME type.
|
157
158
|
# @raise (see MimeType.[])
|
158
159
|
# @return [Object]
|
159
|
-
def parse(type)
|
160
|
-
MimeType[type].decode to_s
|
160
|
+
def parse(type = nil)
|
161
|
+
MimeType[type || mime_type].decode to_s
|
161
162
|
end
|
162
163
|
|
163
164
|
# Inspect a response
|
164
165
|
def inspect
|
165
166
|
"#<#{self.class}/#{@version} #{code} #{reason} #{headers.to_h.inspect}>"
|
166
167
|
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
# Initialize an HTTP::Request from options.
|
172
|
+
#
|
173
|
+
# @return [HTTP::Request]
|
174
|
+
def init_request(opts)
|
175
|
+
raise ArgumentError, ":uri is for backwards compatibilty and conflicts with :request" \
|
176
|
+
if opts[:request] && opts[:uri]
|
177
|
+
|
178
|
+
# For backwards compatibilty
|
179
|
+
if opts[:uri]
|
180
|
+
HTTP::Request.new(:uri => opts[:uri], :verb => :get)
|
181
|
+
else
|
182
|
+
opts.fetch(:request)
|
183
|
+
end
|
184
|
+
end
|
167
185
|
end
|
168
186
|
end
|
data/lib/http/timeout/global.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "timeout"
|
4
3
|
require "io/wait"
|
4
|
+
require "resolv"
|
5
|
+
require "timeout"
|
5
6
|
|
6
7
|
require "http/timeout/null"
|
7
8
|
|
@@ -12,6 +13,9 @@ module HTTP
|
|
12
13
|
super
|
13
14
|
|
14
15
|
@timeout = @time_left = options.fetch(:global_timeout)
|
16
|
+
@dns_resolver = options.fetch(:dns_resolver) do
|
17
|
+
::Resolv.method(:getaddresses)
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
21
|
# To future me: Don't remove this again, past you was smarter.
|
@@ -19,14 +23,28 @@ module HTTP
|
|
19
23
|
@time_left = @timeout
|
20
24
|
end
|
21
25
|
|
22
|
-
def connect(socket_class,
|
26
|
+
def connect(socket_class, host_name, *args)
|
27
|
+
connect_operation = lambda do |host_address|
|
28
|
+
::Timeout.timeout(@time_left, TimeoutError) do
|
29
|
+
super(socket_class, host_address, *args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
host_addresses = @dns_resolver.call(host_name)
|
33
|
+
# ensure something to iterates
|
34
|
+
trying_targets = host_addresses.empty? ? [host_name] : host_addresses
|
23
35
|
reset_timer
|
24
|
-
|
25
|
-
|
26
|
-
|
36
|
+
trying_iterator = trying_targets.lazy
|
37
|
+
error = nil
|
38
|
+
begin
|
39
|
+
connect_operation.call(trying_iterator.next).tap do
|
40
|
+
log_time
|
41
|
+
end
|
42
|
+
rescue TimeoutError => e
|
43
|
+
error = e
|
44
|
+
retry
|
45
|
+
rescue ::StopIteration
|
46
|
+
raise error
|
27
47
|
end
|
28
|
-
|
29
|
-
log_time
|
30
48
|
end
|
31
49
|
|
32
50
|
def connect_ssl
|
@@ -59,22 +77,12 @@ module HTTP
|
|
59
77
|
|
60
78
|
private
|
61
79
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
def write_nonblock(data)
|
68
|
-
@socket.write_nonblock(data)
|
69
|
-
end
|
70
|
-
else
|
71
|
-
def read_nonblock(size, buffer = nil)
|
72
|
-
@socket.read_nonblock(size, buffer, :exception => false)
|
73
|
-
end
|
80
|
+
def read_nonblock(size, buffer = nil)
|
81
|
+
@socket.read_nonblock(size, buffer, :exception => false)
|
82
|
+
end
|
74
83
|
|
75
|
-
|
76
|
-
|
77
|
-
end
|
84
|
+
def write_nonblock(data)
|
85
|
+
@socket.write_nonblock(data, :exception => false)
|
78
86
|
end
|
79
87
|
|
80
88
|
# Perform the given I/O operation with the given argument
|
@@ -82,20 +90,18 @@ module HTTP
|
|
82
90
|
reset_timer
|
83
91
|
|
84
92
|
loop do
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
else return result
|
93
|
-
end
|
94
|
-
rescue IO::WaitReadable
|
95
|
-
wait_readable_or_timeout
|
96
|
-
rescue IO::WaitWritable
|
97
|
-
wait_writable_or_timeout
|
93
|
+
result = yield
|
94
|
+
|
95
|
+
case result
|
96
|
+
when :wait_readable then wait_readable_or_timeout
|
97
|
+
when :wait_writable then wait_writable_or_timeout
|
98
|
+
when NilClass then return :eof
|
99
|
+
else return result
|
98
100
|
end
|
101
|
+
rescue IO::WaitReadable
|
102
|
+
wait_readable_or_timeout
|
103
|
+
rescue IO::WaitWritable
|
104
|
+
wait_writable_or_timeout
|
99
105
|
end
|
100
106
|
rescue EOFError
|
101
107
|
:eof
|
data/lib/http/timeout/null.rb
CHANGED
@@ -12,7 +12,7 @@ module HTTP
|
|
12
12
|
|
13
13
|
attr_reader :options, :socket
|
14
14
|
|
15
|
-
def initialize(options = {})
|
15
|
+
def initialize(options = {})
|
16
16
|
@options = options
|
17
17
|
end
|
18
18
|
|
@@ -36,6 +36,7 @@ module HTTP
|
|
36
36
|
connect_ssl
|
37
37
|
|
38
38
|
return unless ssl_context.verify_mode == OpenSSL::SSL::VERIFY_PEER
|
39
|
+
return if ssl_context.respond_to?(:verify_hostname) && !ssl_context.verify_hostname
|
39
40
|
|
40
41
|
@socket.post_connection_check(host)
|
41
42
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "resolv"
|
3
4
|
require "timeout"
|
4
5
|
|
5
6
|
require "http/timeout/null"
|
@@ -17,14 +18,34 @@ module HTTP
|
|
17
18
|
@read_timeout = options.fetch(:read_timeout, READ_TIMEOUT)
|
18
19
|
@write_timeout = options.fetch(:write_timeout, WRITE_TIMEOUT)
|
19
20
|
@connect_timeout = options.fetch(:connect_timeout, CONNECT_TIMEOUT)
|
21
|
+
@dns_resolver = options.fetch(:dns_resolver) do
|
22
|
+
::Resolv.method(:getaddresses)
|
23
|
+
end
|
20
24
|
end
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
# TODO: refactor
|
27
|
+
# rubocop:disable Metrics/MethodLength
|
28
|
+
def connect(socket_class, host_name, *args)
|
29
|
+
connect_operation = lambda do |host_address|
|
30
|
+
::Timeout.timeout(@connect_timeout, TimeoutError) do
|
31
|
+
super(socket_class, host_address, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
host_addresses = @dns_resolver.call(host_name)
|
35
|
+
# ensure something to iterates
|
36
|
+
trying_targets = host_addresses.empty? ? [host_name] : host_addresses
|
37
|
+
trying_iterator = trying_targets.lazy
|
38
|
+
error = nil
|
39
|
+
begin
|
40
|
+
connect_operation.call(trying_iterator.next)
|
41
|
+
rescue TimeoutError => e
|
42
|
+
error = e
|
43
|
+
retry
|
44
|
+
rescue ::StopIteration
|
45
|
+
raise error
|
26
46
|
end
|
27
47
|
end
|
48
|
+
# rubocop:enable Metrics/MethodLength
|
28
49
|
|
29
50
|
def connect_ssl
|
30
51
|
rescue_readable(@connect_timeout) do
|
@@ -34,66 +55,42 @@ module HTTP
|
|
34
55
|
end
|
35
56
|
end
|
36
57
|
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
:eof
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
# Read data from the socket
|
60
|
-
def readpartial(size, buffer = nil)
|
61
|
-
timeout = false
|
62
|
-
loop do
|
63
|
-
result = @socket.read_nonblock(size, buffer, :exception => false)
|
64
|
-
|
65
|
-
return :eof if result.nil?
|
66
|
-
return result if result != :wait_readable
|
67
|
-
|
68
|
-
raise TimeoutError, "Read timed out after #{@read_timeout} seconds" if timeout
|
69
|
-
|
70
|
-
# marking the socket for timeout. Why is this not being raised immediately?
|
71
|
-
# it seems there is some race-condition on the network level between calling
|
72
|
-
# #read_nonblock and #wait_readable, in which #read_nonblock signalizes waiting
|
73
|
-
# for reads, and when waiting for x seconds, it returns nil suddenly without completing
|
74
|
-
# the x seconds. In a normal case this would be a timeout on wait/read, but it can
|
75
|
-
# also mean that the socket has been closed by the server. Therefore we "mark" the
|
76
|
-
# socket for timeout and try to read more bytes. If it returns :eof, it's all good, no
|
77
|
-
# timeout. Else, the first timeout was a proper timeout.
|
78
|
-
# This hack has to be done because io/wait#wait_readable doesn't provide a value for when
|
79
|
-
# the socket is closed by the server, and HTTP::Parser doesn't provide the limit for the chunks.
|
80
|
-
timeout = true unless @socket.to_io.wait_readable(@read_timeout)
|
81
|
-
end
|
58
|
+
# Read data from the socket
|
59
|
+
def readpartial(size, buffer = nil)
|
60
|
+
timeout = false
|
61
|
+
loop do
|
62
|
+
result = @socket.read_nonblock(size, buffer, :exception => false)
|
63
|
+
|
64
|
+
return :eof if result.nil?
|
65
|
+
return result if result != :wait_readable
|
66
|
+
|
67
|
+
raise TimeoutError, "Read timed out after #{@read_timeout} seconds" if timeout
|
68
|
+
|
69
|
+
# marking the socket for timeout. Why is this not being raised immediately?
|
70
|
+
# it seems there is some race-condition on the network level between calling
|
71
|
+
# #read_nonblock and #wait_readable, in which #read_nonblock signalizes waiting
|
72
|
+
# for reads, and when waiting for x seconds, it returns nil suddenly without completing
|
73
|
+
# the x seconds. In a normal case this would be a timeout on wait/read, but it can
|
74
|
+
# also mean that the socket has been closed by the server. Therefore we "mark" the
|
75
|
+
# socket for timeout and try to read more bytes. If it returns :eof, it's all good, no
|
76
|
+
# timeout. Else, the first timeout was a proper timeout.
|
77
|
+
# This hack has to be done because io/wait#wait_readable doesn't provide a value for when
|
78
|
+
# the socket is closed by the server, and HTTP::Parser doesn't provide the limit for the chunks.
|
79
|
+
timeout = true unless @socket.to_io.wait_readable(@read_timeout)
|
82
80
|
end
|
81
|
+
end
|
83
82
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
83
|
+
# Write data to the socket
|
84
|
+
def write(data)
|
85
|
+
timeout = false
|
86
|
+
loop do
|
87
|
+
result = @socket.write_nonblock(data, :exception => false)
|
88
|
+
return result unless result == :wait_writable
|
90
89
|
|
91
|
-
|
90
|
+
raise TimeoutError, "Write timed out after #{@write_timeout} seconds" if timeout
|
92
91
|
|
93
|
-
|
94
|
-
end
|
92
|
+
timeout = true unless @socket.to_io.wait_writable(@write_timeout)
|
95
93
|
end
|
96
|
-
|
97
94
|
end
|
98
95
|
end
|
99
96
|
end
|
data/lib/http/version.rb
CHANGED
@@ -4,50 +4,55 @@
|
|
4
4
|
require "support/http_handling_shared"
|
5
5
|
require "support/dummy_server"
|
6
6
|
require "support/ssl_helper"
|
7
|
+
require "logger"
|
7
8
|
|
8
9
|
RSpec.describe HTTP::Client do
|
9
10
|
run_server(:dummy) { DummyServer.new }
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def stubs
|
18
|
-
@stubs ||= {}
|
19
|
-
end
|
12
|
+
before do
|
13
|
+
stubbed_client = Class.new(HTTP::Client) do
|
14
|
+
def perform(request, options)
|
15
|
+
stubbed = stubs[HTTP::URI::NORMALIZER.call(request.uri).to_s]
|
16
|
+
stubbed ? stubbed.call(request) : super(request, options)
|
17
|
+
end
|
20
18
|
|
21
|
-
|
22
|
-
|
23
|
-
o[HTTP::URI.parse k] = v
|
19
|
+
def stubs
|
20
|
+
@stubs ||= {}
|
24
21
|
end
|
25
22
|
|
26
|
-
|
23
|
+
def stub(stubs)
|
24
|
+
@stubs = stubs.transform_keys do |k|
|
25
|
+
HTTP::URI::NORMALIZER.call(k).to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
self
|
29
|
+
end
|
27
30
|
end
|
28
|
-
end
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
def redirect_response(location, status = 302)
|
33
|
+
lambda do |request|
|
34
|
+
HTTP::Response.new(
|
35
|
+
:status => status,
|
36
|
+
:version => "1.1",
|
37
|
+
:headers => {"Location" => location},
|
38
|
+
:body => "",
|
39
|
+
:request => request
|
40
|
+
)
|
41
|
+
end
|
39
42
|
end
|
40
|
-
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
def simple_response(body, status = 200)
|
45
|
+
lambda do |request|
|
46
|
+
HTTP::Response.new(
|
47
|
+
:status => status,
|
48
|
+
:version => "1.1",
|
49
|
+
:body => body,
|
50
|
+
:request => request
|
51
|
+
)
|
52
|
+
end
|
50
53
|
end
|
54
|
+
|
55
|
+
stub_const("StubbedClient", stubbed_client)
|
51
56
|
end
|
52
57
|
|
53
58
|
describe "following redirects" do
|
@@ -105,13 +110,45 @@ RSpec.describe HTTP::Client do
|
|
105
110
|
end
|
106
111
|
|
107
112
|
it "works like a charm in real world" do
|
108
|
-
|
109
|
-
|
110
|
-
expect(client.get(url).to_s).to include "support for non-ascii URIs"
|
113
|
+
expect(HTTP.follow.get("https://bit.ly/2UaBT4R").parse(:json)).
|
114
|
+
to include("url" => "https://httpbin.org/anything/könig")
|
111
115
|
end
|
112
116
|
end
|
113
117
|
end
|
114
118
|
|
119
|
+
describe "following redirects with logging" do
|
120
|
+
let(:logger) do
|
121
|
+
logger = Logger.new(logdev)
|
122
|
+
logger.formatter = ->(severity, _, _, message) { format("** %s **\n%s\n", severity, message) }
|
123
|
+
logger.level = Logger::INFO
|
124
|
+
logger
|
125
|
+
end
|
126
|
+
|
127
|
+
let(:logdev) { StringIO.new }
|
128
|
+
|
129
|
+
it "logs all requests" do
|
130
|
+
client = StubbedClient.new(:follow => true, :features => { :logging => { :logger => logger } }).stub(
|
131
|
+
"http://example.com/" => redirect_response("/1"),
|
132
|
+
"http://example.com/1" => redirect_response("/2"),
|
133
|
+
"http://example.com/2" => redirect_response("/3"),
|
134
|
+
"http://example.com/3" => simple_response("OK")
|
135
|
+
)
|
136
|
+
|
137
|
+
expect { client.get("http://example.com/") }.not_to raise_error
|
138
|
+
|
139
|
+
expect(logdev.string).to eq <<~OUTPUT
|
140
|
+
** INFO **
|
141
|
+
> GET http://example.com/
|
142
|
+
** INFO **
|
143
|
+
> GET http://example.com/1
|
144
|
+
** INFO **
|
145
|
+
> GET http://example.com/2
|
146
|
+
** INFO **
|
147
|
+
> GET http://example.com/3
|
148
|
+
OUTPUT
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
115
152
|
describe "parsing params" do
|
116
153
|
let(:client) { HTTP::Client.new }
|
117
154
|
before { allow(client).to receive :perform }
|
@@ -197,6 +234,22 @@ RSpec.describe HTTP::Client do
|
|
197
234
|
|
198
235
|
client.get("http://example.com/", :form => {:foo => HTTP::FormData::Part.new("content")})
|
199
236
|
end
|
237
|
+
|
238
|
+
context "when passing an HTTP::FormData object directly" do
|
239
|
+
it "creates url encoded form data object" do
|
240
|
+
client = HTTP::Client.new
|
241
|
+
form_data = HTTP::FormData::Multipart.new({ :foo => "bar" })
|
242
|
+
|
243
|
+
allow(client).to receive(:perform)
|
244
|
+
|
245
|
+
expect(HTTP::Request).to receive(:new) do |opts|
|
246
|
+
expect(opts[:body]).to be form_data
|
247
|
+
expect(opts[:body].to_s).to match(/^Content-Disposition: form-data; name="foo"\r\n\r\nbar\r\n/m)
|
248
|
+
end
|
249
|
+
|
250
|
+
client.get("http://example.com/", :form => form_data)
|
251
|
+
end
|
252
|
+
end
|
200
253
|
end
|
201
254
|
|
202
255
|
describe "passing json" do
|
@@ -220,9 +273,9 @@ RSpec.describe HTTP::Client do
|
|
220
273
|
end
|
221
274
|
|
222
275
|
it "works like a charm in real world" do
|
223
|
-
url
|
224
|
-
|
225
|
-
expect(
|
276
|
+
url = "https://httpbin.org/anything/ö無"
|
277
|
+
|
278
|
+
expect(HTTP.follow.get(url).parse(:json)).to include("url" => url)
|
226
279
|
end
|
227
280
|
end
|
228
281
|
|
@@ -291,6 +344,7 @@ RSpec.describe HTTP::Client do
|
|
291
344
|
end
|
292
345
|
end
|
293
346
|
end
|
347
|
+
|
294
348
|
it "is given a chance to wrap the Request" do
|
295
349
|
feature_instance = feature_class.new
|
296
350
|
|
@@ -299,7 +353,7 @@ RSpec.describe HTTP::Client do
|
|
299
353
|
|
300
354
|
expect(response.code).to eq(200)
|
301
355
|
expect(feature_instance.captured_request.verb).to eq(:get)
|
302
|
-
expect(feature_instance.captured_request.uri.to_s).to eq(dummy.endpoint
|
356
|
+
expect(feature_instance.captured_request.uri.to_s).to eq("#{dummy.endpoint}/")
|
303
357
|
end
|
304
358
|
|
305
359
|
it "is given a chance to wrap the Response" do
|
@@ -325,6 +379,19 @@ RSpec.describe HTTP::Client do
|
|
325
379
|
expect(feature_instance.captured_request.verb).to eq(:post)
|
326
380
|
expect(feature_instance.captured_request.uri.to_s).to eq(sleep_url)
|
327
381
|
end
|
382
|
+
|
383
|
+
it "is given a chance to handle a connection timeout error" do
|
384
|
+
allow(TCPSocket).to receive(:open) { sleep 1 }
|
385
|
+
sleep_url = "#{dummy.endpoint}/sleep"
|
386
|
+
feature_instance = feature_class.new
|
387
|
+
|
388
|
+
expect do
|
389
|
+
client.use(:test_feature => feature_instance).
|
390
|
+
timeout(0.001).
|
391
|
+
request(:post, sleep_url)
|
392
|
+
end.to raise_error(HTTP::TimeoutError)
|
393
|
+
expect(feature_instance.captured_error).to be_a(HTTP::TimeoutError)
|
394
|
+
end
|
328
395
|
end
|
329
396
|
end
|
330
397
|
|
@@ -335,7 +402,8 @@ RSpec.describe HTTP::Client do
|
|
335
402
|
let(:client) { described_class.new(options.merge(extra_options)) }
|
336
403
|
end
|
337
404
|
|
338
|
-
|
405
|
+
# TODO: https://github.com/httprb/http/issues/627
|
406
|
+
xdescribe "working with SSL" do
|
339
407
|
run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
|
340
408
|
|
341
409
|
let(:extra_options) { {} }
|
@@ -476,7 +544,7 @@ RSpec.describe HTTP::Client do
|
|
476
544
|
BODY
|
477
545
|
end
|
478
546
|
|
479
|
-
|
547
|
+
xit "raises HTTP::ConnectionError" do
|
480
548
|
expect { client.get(dummy.endpoint).to_s }.to raise_error(HTTP::ConnectionError)
|
481
549
|
end
|
482
550
|
end
|