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.
@@ -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 => "Request Entity Too Large",
51
- 414 => "Request-URI Too Long",
51
+ 413 => "Payload Too Large",
52
+ 414 => "URI Too Long",
52
53
  415 => "Unsupported Media Type",
53
- 416 => "Requested Range Not Satisfiable",
54
+ 416 => "Range Not Satisfiable",
54
55
  417 => "Expectation Failed",
55
- 418 => "I'm a Teapot",
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
- 510 => "Not Extended"
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
@@ -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
- # Abstracted out from the normal connect for SSL connections
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
- begin
19
- socket.connect_nonblock(*args)
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
- private
64
+ alias_method :<<, :write
62
65
 
63
- # Create a DNS resolver
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
@@ -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(_, host, port)
24
- # https://github.com/celluloid/celluloid-io/blob/master/lib/celluloid/io/tcp_socket.rb
25
- begin
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
- unless addr
37
- addr = resolve_address(host)
38
- fail Resolv::ResolvError, "DNS result has no information for #{host}" unless addr
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
- case addr
42
- when Resolv::IPv4
43
- family = Socket::AF_INET
44
- when Resolv::IPv6
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,3 +1,3 @@
1
1
  module HTTP
2
- VERSION = "0.8.0.pre4"
2
+ VERSION = "0.8.0.pre5"
3
3
  end
@@ -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) { dummy }
173
- let(:client) { described_class.new(options) }
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", true do
184
+ include_context "HTTP handling" do
198
185
  let(:server) { dummy_ssl }
199
186
  end
200
187
 
201
- it "works via SSL" do
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
- context "with a mismatch host" do
207
- it "errors" do
208
- expect { client.get(dummy_ssl.endpoint.gsub("127.0.0.1", "localhost")) }
209
- .to raise_error(OpenSSL::SSL::SSLError, /does not match/)
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(location, status)
6
+ def redirect_response(status, location)
7
7
  simple_response status, "", "Location" => location
8
8
  end
9
9
 
10
- let(:max_hops) { 5 }
11
- subject(:redirector) { described_class.new max_hops }
10
+ describe "#strict" do
11
+ subject { redirector.strict }
12
12
 
13
- context "following 300 redirect" do
14
- let(:orig_request) { HTTP::Request.new :post, "http://www.example.com/" }
15
- let(:orig_response) { redirect_response "http://example.com/", 300 }
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
- it "follows without changing verb" do
18
- redirector.perform(orig_request, orig_response) do |request|
19
- expect(request.verb).to be orig_request.verb
20
- simple_response 200
21
- end
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
- context "following 301 redirect" do
26
- let(:orig_request) { HTTP::Request.new :post, "http://www.example.com/" }
27
- let(:orig_response) { redirect_response "http://example.com/", 301 }
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
- it "follows without changing verb" do
30
- redirector.perform(orig_request, orig_response) do |request|
31
- expect(request.verb).to be orig_request.verb
32
- simple_response 200
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
- context "following 302 redirect" do
38
- let(:orig_request) { HTTP::Request.new :post, "http://www.example.com/" }
39
- let(:orig_response) { redirect_response "http://example.com/", 302 }
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
- it "follows without changing verb" do
42
- redirector.perform(orig_request, orig_response) do |request|
43
- expect(request.verb).to be orig_request.verb
44
- simple_response 200
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
- context "following 303 redirect" do
50
- context "upon POST request" do
51
- let(:orig_request) { HTTP::Request.new :post, "http://www.example.com/" }
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
- it "follows without changing verb" do
55
- redirector.perform(orig_request, orig_response) do |request|
56
- expect(request.verb).to be :get
57
- simple_response 200
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 "upon HEAD request" do
63
- let(:orig_request) { HTTP::Request.new :head, "http://www.example.com/" }
64
- let(:orig_response) { redirect_response "http://example.com/", 303 }
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
- it "follows without changing verb" do
67
- redirector.perform(orig_request, orig_response) do |request|
68
- expect(request.verb).to be :get
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
- context "following 307 redirect" do
76
- let(:orig_request) { HTTP::Request.new :post, "http://www.example.com/" }
77
- let(:orig_response) { redirect_response "http://example.com/", 307 }
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
- it "follows without changing verb" do
80
- redirector.perform(orig_request, orig_response) do |request|
81
- expect(request.verb).to be orig_request.verb
82
- simple_response 200
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
- context "following 308 redirect" do
88
- let(:orig_request) { HTTP::Request.new :post, "http://www.example.com/" }
89
- let(:orig_response) { redirect_response "http://example.com/", 308 }
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
- it "follows without changing verb" do
92
- redirector.perform(orig_request, orig_response) do |request|
93
- expect(request.verb).to be orig_request.verb
94
- simple_response 200
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