Skip to content

Commit 359f8a7

Browse files
committed
Extract redirect location handling and clarify behaviour.
1 parent 498b912 commit 359f8a7

File tree

2 files changed

+38
-13
lines changed

2 files changed

+38
-13
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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@
4444
end
4545

4646
it 'should fail with maximum redirects' do
47-
expect{
47+
expect do
4848
response = relative_location.get('/home')
49-
}.to raise_exception(Async::HTTP::TooManyRedirects, message: be =~ /maximum/)
49+
end.to raise_exception(Async::HTTP::TooManyRedirects, message: be =~ /maximum/)
5050
end
5151
end
5252
end

0 commit comments

Comments
 (0)