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