http 0.8.0.pre4 → 0.8.0.pre5
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 +3 -0
- data/CHANGES.md +1 -1
- data/Gemfile +1 -0
- data/README.md +1 -0
- data/Rakefile +42 -0
- data/lib/http/chainable.rb +1 -1
- data/lib/http/client.rb +17 -10
- data/lib/http/connection.rb +81 -52
- data/lib/http/options.rb +11 -1
- data/lib/http/redirector.rb +60 -32
- data/lib/http/response/status/reasons.rb +13 -7
- data/lib/http/timeout/global.rb +14 -29
- data/lib/http/timeout/null.rb +0 -2
- data/lib/http/timeout/per_operation.rb +16 -68
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +24 -28
- data/spec/lib/http/options/merge_spec.rb +2 -0
- data/spec/lib/http/redirector_spec.rb +338 -57
- data/spec/lib/http/response/status_spec.rb +0 -6
- data/spec/lib/http_spec.rb +2 -2
- data/spec/spec_helper.rb +0 -7
- data/spec/support/dummy_server.rb +13 -24
- data/spec/support/http_handling_shared.rb +19 -45
- data/spec/support/servers/config.rb +0 -4
- data/spec/support/ssl_helper.rb +102 -0
- metadata +4 -4
- data/spec/support/create_certs.rb +0 -57
@@ -1,3 +1,5 @@
|
|
1
|
+
# AUTO-GENERATED FILE, DO NOT CHANGE IT MANUALLY
|
2
|
+
|
1
3
|
require "delegate"
|
2
4
|
|
3
5
|
module HTTP
|
@@ -9,7 +11,6 @@ module HTTP
|
|
9
11
|
#
|
10
12
|
# REASONS[400] # => "Bad Request"
|
11
13
|
# REASONS[414] # => "Request-URI Too Long"
|
12
|
-
# REASONS[418] # => "I'm a Teapot"
|
13
14
|
#
|
14
15
|
# @return [Hash<Fixnum => String>]
|
15
16
|
REASONS = {
|
@@ -24,6 +25,7 @@ module HTTP
|
|
24
25
|
205 => "Reset Content",
|
25
26
|
206 => "Partial Content",
|
26
27
|
207 => "Multi-Status",
|
28
|
+
208 => "Already Reported",
|
27
29
|
226 => "IM Used",
|
28
30
|
300 => "Multiple Choices",
|
29
31
|
301 => "Moved Permanently",
|
@@ -31,7 +33,6 @@ module HTTP
|
|
31
33
|
303 => "See Other",
|
32
34
|
304 => "Not Modified",
|
33
35
|
305 => "Use Proxy",
|
34
|
-
306 => "Reserved",
|
35
36
|
307 => "Temporary Redirect",
|
36
37
|
308 => "Permanent Redirect",
|
37
38
|
400 => "Bad Request",
|
@@ -47,16 +48,19 @@ module HTTP
|
|
47
48
|
410 => "Gone",
|
48
49
|
411 => "Length Required",
|
49
50
|
412 => "Precondition Failed",
|
50
|
-
413 => "
|
51
|
-
414 => "
|
51
|
+
413 => "Payload Too Large",
|
52
|
+
414 => "URI Too Long",
|
52
53
|
415 => "Unsupported Media Type",
|
53
|
-
416 => "
|
54
|
+
416 => "Range Not Satisfiable",
|
54
55
|
417 => "Expectation Failed",
|
55
|
-
|
56
|
+
421 => "Misdirected Request",
|
56
57
|
422 => "Unprocessable Entity",
|
57
58
|
423 => "Locked",
|
58
59
|
424 => "Failed Dependency",
|
59
60
|
426 => "Upgrade Required",
|
61
|
+
428 => "Precondition Required",
|
62
|
+
429 => "Too Many Requests",
|
63
|
+
431 => "Request Header Fields Too Large",
|
60
64
|
500 => "Internal Server Error",
|
61
65
|
501 => "Not Implemented",
|
62
66
|
502 => "Bad Gateway",
|
@@ -65,7 +69,9 @@ module HTTP
|
|
65
69
|
505 => "HTTP Version Not Supported",
|
66
70
|
506 => "Variant Also Negotiates",
|
67
71
|
507 => "Insufficient Storage",
|
68
|
-
|
72
|
+
508 => "Loop Detected",
|
73
|
+
510 => "Not Extended",
|
74
|
+
511 => "Network Authentication Required"
|
69
75
|
}.each { |_, v| v.freeze }.freeze
|
70
76
|
end
|
71
77
|
end
|
data/lib/http/timeout/global.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# rubocop:disable Lint/HandleExceptions
|
2
1
|
module HTTP
|
3
2
|
module Timeout
|
4
3
|
class Global < PerOperation
|
@@ -11,24 +10,28 @@ module HTTP
|
|
11
10
|
@total_timeout = time_left
|
12
11
|
end
|
13
12
|
|
14
|
-
|
15
|
-
def connect_with_timeout(*args)
|
13
|
+
def connect(socket_class, host, port)
|
16
14
|
reset_timer
|
15
|
+
::Timeout.timeout(time_left, TimeoutError) do
|
16
|
+
@socket = socket_class.open(host, port)
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
19
|
+
log_time
|
20
|
+
end
|
21
|
+
|
22
|
+
def connect_ssl
|
23
|
+
reset_timer
|
20
24
|
|
25
|
+
begin
|
26
|
+
socket.connect_nonblock
|
21
27
|
rescue IO::WaitReadable
|
22
28
|
IO.select([socket], nil, nil, time_left)
|
23
29
|
log_time
|
24
30
|
retry
|
25
|
-
|
26
|
-
rescue Errno::EINPROGRESS
|
31
|
+
rescue IO::WaitWritable
|
27
32
|
IO.select(nil, [socket], nil, time_left)
|
28
33
|
log_time
|
29
34
|
retry
|
30
|
-
|
31
|
-
rescue Errno::EISCONN
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
@@ -58,26 +61,9 @@ module HTTP
|
|
58
61
|
end
|
59
62
|
end
|
60
63
|
|
61
|
-
|
64
|
+
alias_method :<<, :write
|
62
65
|
|
63
|
-
|
64
|
-
def resolve_address(host)
|
65
|
-
addr = HostResolver.getaddress(host)
|
66
|
-
return addr if addr
|
67
|
-
|
68
|
-
reset_timer
|
69
|
-
|
70
|
-
addr = Resolv::DNS.open(:timeout => time_left) do |dns|
|
71
|
-
dns.getaddress
|
72
|
-
end
|
73
|
-
|
74
|
-
log_time
|
75
|
-
|
76
|
-
addr
|
77
|
-
|
78
|
-
rescue Resolv::ResolvTimeout
|
79
|
-
raise TimeoutError, "DNS timed out after #{total_timeout} seconds"
|
80
|
-
end
|
66
|
+
private
|
81
67
|
|
82
68
|
# Due to the run/retry nature of nonblocking I/O, it's easier to keep track of time
|
83
69
|
# via method calls instead of a block to monitor.
|
@@ -96,4 +82,3 @@ module HTTP
|
|
96
82
|
end
|
97
83
|
end
|
98
84
|
end
|
99
|
-
# rubocop:enable Lint/HandleExceptions
|
data/lib/http/timeout/null.rb
CHANGED
@@ -25,8 +25,6 @@ module HTTP
|
|
25
25
|
|
26
26
|
# Configures the SSL connection and starts the connection
|
27
27
|
def start_tls(host, ssl_socket_class, ssl_context)
|
28
|
-
# TODO: abstract away SSLContexts so we can use other TLS libraries
|
29
|
-
ssl_context ||= OpenSSL::SSL::SSLContext.new
|
30
28
|
@socket = ssl_socket_class.new(socket, ssl_context)
|
31
29
|
socket.sync_close = true
|
32
30
|
|
@@ -1,6 +1,3 @@
|
|
1
|
-
# rubocop:disable Lint/HandleExceptions
|
2
|
-
require "resolv"
|
3
|
-
|
4
1
|
module HTTP
|
5
2
|
module Timeout
|
6
3
|
class PerOperation < Null
|
@@ -20,40 +17,28 @@ module HTTP
|
|
20
17
|
@connect_timeout = options.fetch(:connect_timeout, CONNECT_TIMEOUT)
|
21
18
|
end
|
22
19
|
|
23
|
-
def connect(
|
24
|
-
|
25
|
-
|
26
|
-
addr = Resolv::IPv4.create(host)
|
27
|
-
rescue ArgumentError
|
28
|
-
end
|
29
|
-
|
30
|
-
# Guess it's not IPv4! Is it IPv6?
|
31
|
-
begin
|
32
|
-
addr ||= Resolv::IPv6.create(host)
|
33
|
-
rescue ArgumentError
|
20
|
+
def connect(socket_class, host, port)
|
21
|
+
::Timeout.timeout(connect_timeout, TimeoutError) do
|
22
|
+
@socket = socket_class.open(host, port)
|
34
23
|
end
|
24
|
+
end
|
35
25
|
|
36
|
-
|
37
|
-
|
38
|
-
|
26
|
+
def connect_ssl
|
27
|
+
socket.connect_nonblock
|
28
|
+
rescue IO::WaitReadable
|
29
|
+
if IO.select([socket], nil, nil, connect_timeout)
|
30
|
+
retry
|
31
|
+
else
|
32
|
+
raise TimeoutError, "Connection timed out after #{connect_timeout} seconds"
|
39
33
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
family = Socket::AF_INET6
|
46
|
-
else fail ArgumentError, "unsupported address class: #{addr.class}"
|
34
|
+
rescue IO::WaitWritable
|
35
|
+
if IO.select(nil, [socket], nil, connect_timeout)
|
36
|
+
retry
|
37
|
+
else
|
38
|
+
raise TimeoutError, "Connection timed out after #{connect_timeout} seconds"
|
47
39
|
end
|
48
|
-
|
49
|
-
@socket = Socket.new(family, Socket::SOCK_STREAM, 0)
|
50
|
-
|
51
|
-
connect_with_timeout(Socket.sockaddr_in(port, addr.to_s))
|
52
40
|
end
|
53
41
|
|
54
|
-
# No changes need to be made for the SSL connection
|
55
|
-
alias_method :connect_with_timeout, :connect_ssl
|
56
|
-
|
57
42
|
# Read data from the socket
|
58
43
|
def readpartial(size)
|
59
44
|
socket.read_nonblock(size)
|
@@ -75,43 +60,6 @@ module HTTP
|
|
75
60
|
raise TimeoutError, "Read timed out after #{write_timeout} seconds"
|
76
61
|
end
|
77
62
|
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
# Actually do the connect after we're setup
|
82
|
-
def connect_with_timeout(*args)
|
83
|
-
socket.connect_nonblock(*args)
|
84
|
-
|
85
|
-
rescue IO::WaitReadable
|
86
|
-
if IO.select([socket], nil, nil, connect_timeout)
|
87
|
-
retry
|
88
|
-
else
|
89
|
-
raise TimeoutError, "Connection timed out after #{connect_timeout} seconds"
|
90
|
-
end
|
91
|
-
|
92
|
-
rescue Errno::EINPROGRESS
|
93
|
-
if IO.select(nil, [socket], nil, connect_timeout)
|
94
|
-
retry
|
95
|
-
else
|
96
|
-
raise TimeoutError, "Connection timed out after #{connect_timeout} seconds"
|
97
|
-
end
|
98
|
-
|
99
|
-
rescue Errno::EISCONN
|
100
|
-
end
|
101
|
-
|
102
|
-
# Create a DNS resolver
|
103
|
-
def resolve_address(host)
|
104
|
-
addr = HostResolver.getaddress(host)
|
105
|
-
return addr if addr
|
106
|
-
|
107
|
-
Resolv::DNS.open(:timeout => connect_timeout) do |dns|
|
108
|
-
dns.getaddress
|
109
|
-
end
|
110
|
-
|
111
|
-
rescue Resolv::ResolvTimeout
|
112
|
-
raise TimeoutError, "DNS timed out after #{connect_timeout} seconds"
|
113
|
-
end
|
114
63
|
end
|
115
64
|
end
|
116
65
|
end
|
117
|
-
# rubocop:enable Lint/HandleExceptions
|
data/lib/http/version.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require "support/http_handling_shared"
|
2
2
|
require "support/dummy_server"
|
3
|
+
require "support/ssl_helper"
|
4
|
+
|
3
5
|
require "http/cache"
|
4
6
|
|
5
7
|
RSpec.describe HTTP::Client do
|
6
8
|
run_server(:dummy) { DummyServer.new }
|
7
|
-
run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
|
8
9
|
|
9
10
|
StubbedClient = Class.new(HTTP::Client) do
|
10
11
|
def make_request(request, options)
|
@@ -59,7 +60,7 @@ RSpec.describe HTTP::Client do
|
|
59
60
|
end
|
60
61
|
|
61
62
|
it "fails if max amount of hops reached" do
|
62
|
-
client = StubbedClient.new(:follow => 5).stub(
|
63
|
+
client = StubbedClient.new(:follow => {:max_hops => 5}).stub(
|
63
64
|
"http://example.com/" => redirect_response("/1"),
|
64
65
|
"http://example.com/1" => redirect_response("/2"),
|
65
66
|
"http://example.com/2" => redirect_response("/3"),
|
@@ -169,44 +170,39 @@ RSpec.describe HTTP::Client do
|
|
169
170
|
|
170
171
|
include_context "HTTP handling" do
|
171
172
|
let(:options) { {} }
|
172
|
-
let(:server)
|
173
|
-
let(:client)
|
173
|
+
let(:server) { dummy }
|
174
|
+
let(:client) { described_class.new(options) }
|
174
175
|
end
|
175
176
|
|
176
|
-
describe "SSL" do
|
177
|
+
describe "working with SSL" do
|
178
|
+
run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
|
179
|
+
|
177
180
|
let(:client) do
|
178
|
-
described_class.new
|
179
|
-
options.merge(
|
180
|
-
:ssl_context => OpenSSL::SSL::SSLContext.new.tap do |context|
|
181
|
-
context.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
|
182
|
-
|
183
|
-
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
184
|
-
context.ca_file = File.join(certs_dir, "ca.crt")
|
185
|
-
context.cert = OpenSSL::X509::Certificate.new(
|
186
|
-
File.read(File.join(certs_dir, "client.crt"))
|
187
|
-
)
|
188
|
-
context.key = OpenSSL::PKey::RSA.new(
|
189
|
-
File.read(File.join(certs_dir, "client.key"))
|
190
|
-
)
|
191
|
-
context
|
192
|
-
end
|
193
|
-
)
|
194
|
-
)
|
181
|
+
described_class.new options.merge :ssl_context => SSLHelper.client_context
|
195
182
|
end
|
196
183
|
|
197
|
-
include_context "HTTP handling"
|
184
|
+
include_context "HTTP handling" do
|
198
185
|
let(:server) { dummy_ssl }
|
199
186
|
end
|
200
187
|
|
201
|
-
it "works
|
188
|
+
it "just works" do
|
202
189
|
response = client.get(dummy_ssl.endpoint)
|
203
190
|
expect(response.body.to_s).to eq("<!doctype html>")
|
204
191
|
end
|
205
192
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
193
|
+
it "fails with OpenSSL::SSL::SSLError if host mismatch" do
|
194
|
+
expect { client.get(dummy_ssl.endpoint.gsub("127.0.0.1", "localhost")) }
|
195
|
+
.to raise_error(OpenSSL::SSL::SSLError, /does not match/)
|
196
|
+
end
|
197
|
+
|
198
|
+
context "with SSL options instead of a context" do
|
199
|
+
let(:client) do
|
200
|
+
described_class.new options.merge :ssl => SSLHelper.client_params
|
201
|
+
end
|
202
|
+
|
203
|
+
it "just works" do
|
204
|
+
response = client.get(dummy_ssl.endpoint)
|
205
|
+
expect(response.body.to_s).to eq("<!doctype html>")
|
210
206
|
end
|
211
207
|
end
|
212
208
|
end
|
@@ -35,6 +35,7 @@ RSpec.describe HTTP::Options, "merge" do
|
|
35
35
|
:keep_alive_timeout => 10,
|
36
36
|
:headers => {:accept => "xml", :bar => "bar"},
|
37
37
|
:timeout_options => {:foo => :bar},
|
38
|
+
:ssl => {:foo => "bar"},
|
38
39
|
:proxy => {:proxy_address => "127.0.0.1", :proxy_port => 8080})
|
39
40
|
|
40
41
|
expect(foo.merge(bar).to_hash).to eq(
|
@@ -47,6 +48,7 @@ RSpec.describe HTTP::Options, "merge" do
|
|
47
48
|
:json => {:bar => "bar"},
|
48
49
|
:persistent => "https://www.googe.com",
|
49
50
|
:keep_alive_timeout => 10,
|
51
|
+
:ssl => {:foo => "bar"},
|
50
52
|
:headers => {"Foo" => "foo", "Accept" => "xml", "Bar" => "bar"},
|
51
53
|
:proxy => {:proxy_address => "127.0.0.1", :proxy_port => 8080},
|
52
54
|
:follow => nil,
|
@@ -3,95 +3,376 @@ RSpec.describe HTTP::Redirector do
|
|
3
3
|
HTTP::Response.new(status, "1.1", headers, body)
|
4
4
|
end
|
5
5
|
|
6
|
-
def redirect_response(
|
6
|
+
def redirect_response(status, location)
|
7
7
|
simple_response status, "", "Location" => location
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
describe "#strict" do
|
11
|
+
subject { redirector.strict }
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
context "by default" do
|
14
|
+
let(:redirector) { described_class.new }
|
15
|
+
it { is_expected.to be true }
|
16
|
+
end
|
17
|
+
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
describe "#max_hops" do
|
20
|
+
subject { redirector.max_hops }
|
21
|
+
|
22
|
+
context "by default" do
|
23
|
+
let(:redirector) { described_class.new }
|
24
|
+
it { is_expected.to eq 5 }
|
22
25
|
end
|
23
26
|
end
|
24
27
|
|
25
|
-
|
26
|
-
let(:
|
27
|
-
let(:
|
28
|
+
describe "#perform" do
|
29
|
+
let(:options) { {} }
|
30
|
+
let(:redirector) { described_class.new options }
|
31
|
+
|
32
|
+
it "fails with TooManyRedirectsError if max hops reached" do
|
33
|
+
req = HTTP::Request.new :head, "http://example.com"
|
34
|
+
res = proc { |prev_req| redirect_response(301, "#{prev_req.uri}/1") }
|
35
|
+
|
36
|
+
expect { redirector.perform(req, res.call(req), &res) }
|
37
|
+
.to raise_error HTTP::Redirector::TooManyRedirectsError
|
38
|
+
end
|
39
|
+
|
40
|
+
it "fails with EndlessRedirectError if endless loop detected" do
|
41
|
+
req = HTTP::Request.new :head, "http://example.com"
|
42
|
+
res = redirect_response(301, req.uri)
|
43
|
+
|
44
|
+
expect { redirector.perform(req, res) { res } }
|
45
|
+
.to raise_error HTTP::Redirector::EndlessRedirectError
|
46
|
+
end
|
47
|
+
|
48
|
+
it "fails with StateError if there were no Location header" do
|
49
|
+
req = HTTP::Request.new :head, "http://example.com"
|
50
|
+
res = simple_response(301)
|
51
|
+
|
52
|
+
expect { |b| redirector.perform(req, res, &b) }
|
53
|
+
.to raise_error HTTP::StateError
|
54
|
+
end
|
55
|
+
|
56
|
+
it "returns first non-redirect response" do
|
57
|
+
req = HTTP::Request.new :head, "http://example.com"
|
58
|
+
hops = [
|
59
|
+
redirect_response(301, "http://example.com/1"),
|
60
|
+
redirect_response(301, "http://example.com/2"),
|
61
|
+
redirect_response(301, "http://example.com/3"),
|
62
|
+
simple_response(200, "foo"),
|
63
|
+
redirect_response(301, "http://example.com/4"),
|
64
|
+
simple_response(200, "bar")
|
65
|
+
]
|
28
66
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
67
|
+
res = redirector.perform(req, hops.shift) { hops.shift }
|
68
|
+
expect(res.to_s).to eq "foo"
|
69
|
+
end
|
70
|
+
|
71
|
+
context "following 300 redirect" do
|
72
|
+
context "with strict mode" do
|
73
|
+
let(:options) { {:strict => true} }
|
74
|
+
|
75
|
+
it "it follows with original verb if it's safe" do
|
76
|
+
req = HTTP::Request.new :head, "http://example.com"
|
77
|
+
res = redirect_response 300, "http://example.com/1"
|
78
|
+
|
79
|
+
redirector.perform(req, res) do |prev_req, _|
|
80
|
+
expect(prev_req.verb).to be :head
|
81
|
+
simple_response 200
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "raises StateError if original request was PUT" do
|
86
|
+
req = HTTP::Request.new :put, "http://example.com"
|
87
|
+
res = redirect_response 300, "http://example.com/1"
|
88
|
+
|
89
|
+
expect { redirector.perform(req, res) { simple_response 200 } }
|
90
|
+
.to raise_error HTTP::StateError
|
91
|
+
end
|
92
|
+
|
93
|
+
it "raises StateError if original request was POST" do
|
94
|
+
req = HTTP::Request.new :post, "http://example.com"
|
95
|
+
res = redirect_response 300, "http://example.com/1"
|
96
|
+
|
97
|
+
expect { redirector.perform(req, res) { simple_response 200 } }
|
98
|
+
.to raise_error HTTP::StateError
|
99
|
+
end
|
100
|
+
|
101
|
+
it "raises StateError if original request was DELETE" do
|
102
|
+
req = HTTP::Request.new :delete, "http://example.com"
|
103
|
+
res = redirect_response 300, "http://example.com/1"
|
104
|
+
|
105
|
+
expect { redirector.perform(req, res) { simple_response 200 } }
|
106
|
+
.to raise_error HTTP::StateError
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "with non-strict mode" do
|
111
|
+
let(:options) { {:strict => false} }
|
112
|
+
|
113
|
+
it "it follows with original verb if it's safe" do
|
114
|
+
req = HTTP::Request.new :head, "http://example.com"
|
115
|
+
res = redirect_response 300, "http://example.com/1"
|
116
|
+
|
117
|
+
redirector.perform(req, res) do |prev_req, _|
|
118
|
+
expect(prev_req.verb).to be :head
|
119
|
+
simple_response 200
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it "it follows with GET if original request was PUT" do
|
124
|
+
req = HTTP::Request.new :put, "http://example.com"
|
125
|
+
res = redirect_response 300, "http://example.com/1"
|
126
|
+
|
127
|
+
redirector.perform(req, res) do |prev_req, _|
|
128
|
+
expect(prev_req.verb).to be :get
|
129
|
+
simple_response 200
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it "it follows with GET if original request was POST" do
|
134
|
+
req = HTTP::Request.new :post, "http://example.com"
|
135
|
+
res = redirect_response 300, "http://example.com/1"
|
136
|
+
|
137
|
+
redirector.perform(req, res) do |prev_req, _|
|
138
|
+
expect(prev_req.verb).to be :get
|
139
|
+
simple_response 200
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it "it follows with GET if original request was DELETE" do
|
144
|
+
req = HTTP::Request.new :delete, "http://example.com"
|
145
|
+
res = redirect_response 300, "http://example.com/1"
|
146
|
+
|
147
|
+
redirector.perform(req, res) do |prev_req, _|
|
148
|
+
expect(prev_req.verb).to be :get
|
149
|
+
simple_response 200
|
150
|
+
end
|
151
|
+
end
|
33
152
|
end
|
34
153
|
end
|
35
|
-
end
|
36
154
|
|
37
|
-
|
38
|
-
|
39
|
-
|
155
|
+
context "following 301 redirect" do
|
156
|
+
context "with strict mode" do
|
157
|
+
let(:options) { {:strict => true} }
|
158
|
+
|
159
|
+
it "it follows with original verb if it's safe" do
|
160
|
+
req = HTTP::Request.new :head, "http://example.com"
|
161
|
+
res = redirect_response 301, "http://example.com/1"
|
162
|
+
|
163
|
+
redirector.perform(req, res) do |prev_req, _|
|
164
|
+
expect(prev_req.verb).to be :head
|
165
|
+
simple_response 200
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
it "raises StateError if original request was PUT" do
|
170
|
+
req = HTTP::Request.new :put, "http://example.com"
|
171
|
+
res = redirect_response 301, "http://example.com/1"
|
172
|
+
|
173
|
+
expect { redirector.perform(req, res) { simple_response 200 } }
|
174
|
+
.to raise_error HTTP::StateError
|
175
|
+
end
|
176
|
+
|
177
|
+
it "raises StateError if original request was POST" do
|
178
|
+
req = HTTP::Request.new :post, "http://example.com"
|
179
|
+
res = redirect_response 301, "http://example.com/1"
|
180
|
+
|
181
|
+
expect { redirector.perform(req, res) { simple_response 200 } }
|
182
|
+
.to raise_error HTTP::StateError
|
183
|
+
end
|
184
|
+
|
185
|
+
it "raises StateError if original request was DELETE" do
|
186
|
+
req = HTTP::Request.new :delete, "http://example.com"
|
187
|
+
res = redirect_response 301, "http://example.com/1"
|
188
|
+
|
189
|
+
expect { redirector.perform(req, res) { simple_response 200 } }
|
190
|
+
.to raise_error HTTP::StateError
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context "with non-strict mode" do
|
195
|
+
let(:options) { {:strict => false} }
|
196
|
+
|
197
|
+
it "it follows with original verb if it's safe" do
|
198
|
+
req = HTTP::Request.new :head, "http://example.com"
|
199
|
+
res = redirect_response 301, "http://example.com/1"
|
200
|
+
|
201
|
+
redirector.perform(req, res) do |prev_req, _|
|
202
|
+
expect(prev_req.verb).to be :head
|
203
|
+
simple_response 200
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
it "it follows with GET if original request was PUT" do
|
208
|
+
req = HTTP::Request.new :put, "http://example.com"
|
209
|
+
res = redirect_response 301, "http://example.com/1"
|
210
|
+
|
211
|
+
redirector.perform(req, res) do |prev_req, _|
|
212
|
+
expect(prev_req.verb).to be :get
|
213
|
+
simple_response 200
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
it "it follows with GET if original request was POST" do
|
218
|
+
req = HTTP::Request.new :post, "http://example.com"
|
219
|
+
res = redirect_response 301, "http://example.com/1"
|
220
|
+
|
221
|
+
redirector.perform(req, res) do |prev_req, _|
|
222
|
+
expect(prev_req.verb).to be :get
|
223
|
+
simple_response 200
|
224
|
+
end
|
225
|
+
end
|
40
226
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
227
|
+
it "it follows with GET if original request was DELETE" do
|
228
|
+
req = HTTP::Request.new :delete, "http://example.com"
|
229
|
+
res = redirect_response 301, "http://example.com/1"
|
230
|
+
|
231
|
+
redirector.perform(req, res) do |prev_req, _|
|
232
|
+
expect(prev_req.verb).to be :get
|
233
|
+
simple_response 200
|
234
|
+
end
|
235
|
+
end
|
45
236
|
end
|
46
237
|
end
|
47
|
-
end
|
48
238
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
let(:orig_response) { redirect_response "http://example.com/", 303 }
|
239
|
+
context "following 302 redirect" do
|
240
|
+
context "with strict mode" do
|
241
|
+
let(:options) { {:strict => true} }
|
53
242
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
243
|
+
it "it follows with original verb if it's safe" do
|
244
|
+
req = HTTP::Request.new :head, "http://example.com"
|
245
|
+
res = redirect_response 302, "http://example.com/1"
|
246
|
+
|
247
|
+
redirector.perform(req, res) do |prev_req, _|
|
248
|
+
expect(prev_req.verb).to be :head
|
249
|
+
simple_response 200
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
it "raises StateError if original request was PUT" do
|
254
|
+
req = HTTP::Request.new :put, "http://example.com"
|
255
|
+
res = redirect_response 302, "http://example.com/1"
|
256
|
+
|
257
|
+
expect { redirector.perform(req, res) { simple_response 200 } }
|
258
|
+
.to raise_error HTTP::StateError
|
259
|
+
end
|
260
|
+
|
261
|
+
it "raises StateError if original request was POST" do
|
262
|
+
req = HTTP::Request.new :post, "http://example.com"
|
263
|
+
res = redirect_response 302, "http://example.com/1"
|
264
|
+
|
265
|
+
expect { redirector.perform(req, res) { simple_response 200 } }
|
266
|
+
.to raise_error HTTP::StateError
|
267
|
+
end
|
268
|
+
|
269
|
+
it "raises StateError if original request was DELETE" do
|
270
|
+
req = HTTP::Request.new :delete, "http://example.com"
|
271
|
+
res = redirect_response 302, "http://example.com/1"
|
272
|
+
|
273
|
+
expect { redirector.perform(req, res) { simple_response 200 } }
|
274
|
+
.to raise_error HTTP::StateError
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
context "with non-strict mode" do
|
279
|
+
let(:options) { {:strict => false} }
|
280
|
+
|
281
|
+
it "it follows with original verb if it's safe" do
|
282
|
+
req = HTTP::Request.new :head, "http://example.com"
|
283
|
+
res = redirect_response 302, "http://example.com/1"
|
284
|
+
|
285
|
+
redirector.perform(req, res) do |prev_req, _|
|
286
|
+
expect(prev_req.verb).to be :head
|
287
|
+
simple_response 200
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
it "it follows with GET if original request was PUT" do
|
292
|
+
req = HTTP::Request.new :put, "http://example.com"
|
293
|
+
res = redirect_response 302, "http://example.com/1"
|
294
|
+
|
295
|
+
redirector.perform(req, res) do |prev_req, _|
|
296
|
+
expect(prev_req.verb).to be :get
|
297
|
+
simple_response 200
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
it "it follows with GET if original request was POST" do
|
302
|
+
req = HTTP::Request.new :post, "http://example.com"
|
303
|
+
res = redirect_response 302, "http://example.com/1"
|
304
|
+
|
305
|
+
redirector.perform(req, res) do |prev_req, _|
|
306
|
+
expect(prev_req.verb).to be :get
|
307
|
+
simple_response 200
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
it "it follows with GET if original request was DELETE" do
|
312
|
+
req = HTTP::Request.new :delete, "http://example.com"
|
313
|
+
res = redirect_response 302, "http://example.com/1"
|
314
|
+
|
315
|
+
redirector.perform(req, res) do |prev_req, _|
|
316
|
+
expect(prev_req.verb).to be :get
|
317
|
+
simple_response 200
|
318
|
+
end
|
58
319
|
end
|
59
320
|
end
|
60
321
|
end
|
61
322
|
|
62
|
-
context "
|
63
|
-
|
64
|
-
|
323
|
+
context "following 303 redirect" do
|
324
|
+
it "follows with HEAD if original request was HEAD" do
|
325
|
+
req = HTTP::Request.new :head, "http://example.com"
|
326
|
+
res = redirect_response 303, "http://example.com/1"
|
65
327
|
|
66
|
-
|
67
|
-
|
68
|
-
|
328
|
+
redirector.perform(req, res) do |prev_req, _|
|
329
|
+
expect(prev_req.verb).to be :head
|
330
|
+
simple_response 200
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
it "follows with GET if original request was GET" do
|
335
|
+
req = HTTP::Request.new :get, "http://example.com"
|
336
|
+
res = redirect_response 303, "http://example.com/1"
|
337
|
+
|
338
|
+
redirector.perform(req, res) do |prev_req, _|
|
339
|
+
expect(prev_req.verb).to be :get
|
340
|
+
simple_response 200
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
it "follows with GET if original request was neither GET nor HEAD" do
|
345
|
+
req = HTTP::Request.new :post, "http://example.com"
|
346
|
+
res = redirect_response 303, "http://example.com/1"
|
347
|
+
|
348
|
+
redirector.perform(req, res) do |prev_req, _|
|
349
|
+
expect(prev_req.verb).to be :get
|
69
350
|
simple_response 200
|
70
351
|
end
|
71
352
|
end
|
72
353
|
end
|
73
|
-
end
|
74
354
|
|
75
|
-
|
76
|
-
|
77
|
-
|
355
|
+
context "following 307 redirect" do
|
356
|
+
it "follows with original request's verb" do
|
357
|
+
req = HTTP::Request.new :post, "http://example.com"
|
358
|
+
res = redirect_response 307, "http://example.com/1"
|
78
359
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
360
|
+
redirector.perform(req, res) do |prev_req, _|
|
361
|
+
expect(prev_req.verb).to be :post
|
362
|
+
simple_response 200
|
363
|
+
end
|
83
364
|
end
|
84
365
|
end
|
85
|
-
end
|
86
366
|
|
87
|
-
|
88
|
-
|
89
|
-
|
367
|
+
context "following 308 redirect" do
|
368
|
+
it "follows with original request's verb" do
|
369
|
+
req = HTTP::Request.new :post, "http://example.com"
|
370
|
+
res = redirect_response 308, "http://example.com/1"
|
90
371
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
372
|
+
redirector.perform(req, res) do |prev_req, _|
|
373
|
+
expect(prev_req.verb).to be :post
|
374
|
+
simple_response 200
|
375
|
+
end
|
95
376
|
end
|
96
377
|
end
|
97
378
|
end
|