Class: Async::HTTP::Middleware::LocationRedirector

Inherits:
Protocol::HTTP::Middleware
  • Object
show all
Defined in:
lib/async/http/middleware/location_redirector.rb

Overview

A client wrapper which transparently handles redirects to a given maximum number of hops.

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.

The best reference for these semantics is defined by the [Fetch specification](fetch.spec.whatwg.org/#http-redirect-fetch).

| Redirect using GET | Permanent | Temporary | |:—————————————–:|:———:|:———:| | Allowed | 301 | 302 | | Preserve original method | 308 | 307 |

For the specific details of the redirect handling, see:

Defined Under Namespace

Classes: TooManyRedirects

Constant Summary collapse

PROHIBITED_GET_HEADERS =

Header keys which should be deleted when changing a request from a POST to a GET as defined by <fetch.spec.whatwg.org/#request-body-header-name>.

[
	"content-encoding",
	"content-language",
	"content-location",
	"content-type",
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, maximum_hops = 3) ⇒ LocationRedirector

maximum_hops is the max number of redirects. Set to 0 to allow 1 request with no redirects.



45
46
47
48
49
# File 'lib/async/http/middleware/location_redirector.rb', line 45

def initialize(app, maximum_hops = 3)
	super(app)
	
	@maximum_hops = maximum_hops
end

Instance Attribute Details

#maximum_hopsObject (readonly)

The maximum number of hops which will limit the number of redirects until an error is thrown.



52
53
54
# File 'lib/async/http/middleware/location_redirector.rb', line 52

def maximum_hops
  @maximum_hops
end

Instance Method Details

#call(request) ⇒ Object

Make a request, transparently following redirects up to #maximum_hops times.

Raises:



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/async/http/middleware/location_redirector.rb', line 88

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

#handle_redirect(request, location) ⇒ Object

Handle a redirect to a relative location.



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/async/http/middleware/location_redirector.rb', line 71

def handle_redirect(request, location)
	uri = URI.parse(location)
	
	if uri.absolute?
		return false
	end
	
	# Update the path of the request:
	request.path = ::Protocol::URL::Reference[request.path] + location
	
	# Follow the redirect:
	return true
end

#redirect_with_get?(request, response) ⇒ Boolean

Determine whether the redirect should switch the request method to GET.

Returns:

  • (Boolean)


58
59
60
61
62
63
64
# File 'lib/async/http/middleware/location_redirector.rb', line 58

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