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)

@returns [Boolean] True if the redirect was handled, false if it was not.
@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)

maximum_hops is the max number of redirects. Set to 0 to allow 1 request with no redirects.
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