class Async::HTTP::Middleware::LocationRedirector
- <datatracker.ietf.org/doc/html/rfc7231#section-6-4-7> 307 Temporary Redirect.
- <datatracker.ietf.org/doc/html/rfc7538 308 Permanent Redirect.
- <datatracker.ietf.org/doc/html/rfc7231#section-6-4-3> 302 Found.
- <datatracker.ietf.org/doc/html/rfc7231#section-6-4-2> 301 Moved Permanently.
For the specific details of the redirect handling, see:
| Preserve original method | 308 | 307 |
| Allowed | 301 | 302 |
|:—————————————–:|:———:|:———:|
| Redirect using GET | Permanent | Temporary |
The best reference for these semantics is defined by the [Fetch specification](fetch.spec.whatwg.org/#http-redirect-fetch).
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.
A client wrapper which transparently handles redirects to a given maximum number of hops.
def call(request)
def call(request) # We don't want to follow redirects for HEAD requests: return super if request.head? body = ::Protocol::HTTP::Body::Rewindable.wrap(request) hops = 0 while hops <= @maximum_hops response = super(request) if response.redirection? hops += 1 # Get the redirect location: unless location = response.headers["location"] return response end response.finish unless handle_redirect(request, location) return response end # Ensure the request (body) is finished and set to nil before we manipulate the request: request.finish if request.method == GET or response.preserve_method? # We (might) need to rewind the body so that it can be submitted again: body&.rewind request.body = body else # We are changing the method to GET: request.method = GET # We will no longer be submitting the body: body = nil # Remove any headers which are not allowed in a GET request: PROHIBITED_GET_HEADERS.each do |header| request.headers.delete(header) end end else return response end end raise TooManyRedirects, "Redirected #{hops} times, exceeded maximum!" end
def handle_redirect(request, location)
@parameter location [String] The relative location to redirect to.
@parameter request [Protocol::HTTP::Request] The original request, which you can modify if you want to handle the redirect.
Handle a redirect to a relative location.
def handle_redirect(request, location) uri = URI.parse(location) if uri.absolute? return false end # Update the path of the request: request.path = Reference[request.path] + location # Follow the redirect: return true end
def initialize(app, maximum_hops = 3)
def initialize(app, maximum_hops = 3) super(app) @maximum_hops = maximum_hops end
def redirect_with_get?(request, response)
def redirect_with_get?(request, response) # We only want to switch to GET if the request method is something other than get, e.g. POST. if request.method != GET # According to the RFC, we should only switch to GET if the response is a 301 or 302: return response.status == 301 || response.status == 302 end end