Skip to content

Commit 2b56c80

Browse files
committed
Extract redirect location handling and clarify behaviour.
1 parent ae73872 commit 2b56c80

File tree

2 files changed

+55
-14
lines changed

2 files changed

+55
-14
lines changed

lib/async/http/relative_location.rb

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ module HTTP
1616
class TooManyRedirects < StandardError
1717
end
1818

19-
# A client wrapper which transparently handles both relative and absolute redirects to a given maximum number of hops.
19+
# A client wrapper which transparently handles redirects to a given maximum number of hops.
20+
#
21+
# The default implementation will only follow relative locations (i.e. those without a scheme) and will switch to GET if the original request was not a GET.
2022
#
2123
# The best reference for these semantics is defined by the [Fetch specification](https://fetch.spec.whatwg.org/#http-redirect-fetch).
2224
#
@@ -58,14 +60,38 @@ def redirect_with_get?(request, response)
5860
end
5961
end
6062

63+
# Handle a redirect to a relative location.
64+
#
65+
# @parameter request [Protocol::HTTP::Request] The original request, which you can modify if you want to handle the redirect.
66+
# @parameter location [String] The relative location to redirect to.
67+
# @returns [Boolean] True if the redirect was handled, false if it was not.
68+
def handle_redirect(request, location)
69+
uri = URI.parse(location)
70+
71+
if uri.absolute?
72+
return false
73+
end
74+
75+
# Update the path of the request:
76+
request.path = Reference[request.path] + location
77+
78+
# Follow the redirect:
79+
return true
80+
end
81+
6182
def call(request)
6283
# We don't want to follow redirects for HEAD requests:
6384
return super if request.head?
6485

6586
if body = request.body
66-
# We need to cache the body as it might be submitted multiple times if we get a response status of 307 or 308:
67-
body = ::Protocol::HTTP::Body::Rewindable.new(body)
68-
request.body = body
87+
if body.respond_to?(:rewind)
88+
# The request body was already rewindable, so use it as is:
89+
body = request.body
90+
else
91+
# The request body was not rewindable, and we might need to resubmit it if we get a response status of 307 or 308, so make it rewindable:
92+
body = ::Protocol::HTTP::Body::Rewindable.new(body)
93+
request.body = body
94+
end
6995
end
7096

7197
hops = 0
@@ -83,23 +109,22 @@ def call(request)
83109

84110
response.finish
85111

86-
uri = URI.parse(location)
87-
88-
if uri.absolute?
112+
unless handle_redirect(request, location)
89113
return response
90-
else
91-
request.path = Reference[request.path] + location
92114
end
93115

116+
# Ensure the request (body) is finished and set to nil before we manipulate the request:
117+
request.finish
118+
94119
if request.method == GET or response.preserve_method?
95120
# We (might) need to rewind the body so that it can be submitted again:
96121
body&.rewind
122+
request.body = body
97123
else
98124
# We are changing the method to GET:
99125
request.method = GET
100126

101-
# Clear the request body:
102-
request.finish
127+
# We will no longer be submitting the body:
103128
body = nil
104129

105130
# Remove any headers which are not allowed in a GET request:

test/async/http/relative_location.rb

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
end
3131

3232
it 'should redirect POST to GET' do
33-
response = relative_location.post('/')
33+
body = Protocol::HTTP::Body::Buffered.wrap(["Hello, World!"])
34+
expect(body).to receive(:finish)
35+
36+
response = relative_location.post('/', {}, body)
3437

3538
expect(response).to be(:success?)
3639
expect(response.read).to be == "GET"
@@ -44,9 +47,22 @@
4447
end
4548

4649
it 'should fail with maximum redirects' do
47-
expect{
50+
expect do
4851
response = relative_location.get('/home')
49-
}.to raise_exception(Async::HTTP::TooManyRedirects, message: be =~ /maximum/)
52+
end.to raise_exception(Async::HTTP::TooManyRedirects, message: be =~ /maximum/)
53+
end
54+
end
55+
56+
with "handle_redirect returning false" do
57+
before do
58+
expect(relative_location).to receive(:handle_redirect).and_return(false)
59+
end
60+
61+
it "should not follow the redirect" do
62+
response = relative_location.get('/')
63+
response.finish
64+
65+
expect(response).to be(:redirection?)
5066
end
5167
end
5268
end

0 commit comments

Comments
 (0)