Class: Async::HTTP::RelativeLocation

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

Overview

A client wrapper which transparently handles both relative and absolute redirects to a given maximum number of hops.

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:

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) ⇒ RelativeLocation

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



58
59
60
61
62
# File 'lib/async/http/relative_location.rb', line 58

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.



65
66
67
# File 'lib/async/http/relative_location.rb', line 65

def maximum_hops
  @maximum_hops
end

Instance Method Details

#call(request) ⇒ Object

Raises:



75
76
77
78
79
80
81
82
83
84
85
86
87
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
# File 'lib/async/http/relative_location.rb', line 75

def call(request)
	# We don't want to follow redirects for HEAD requests:
	return super if request.head?
	
	if body = request.body
		# We need to cache the body as it might be submitted multiple times if we get a response status of 307 or 308:
		body = ::Protocol::HTTP::Body::Rewindable.new(body)
		request.body = body
	end
	
	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
			
			uri = URI.parse(location)
			
			if uri.absolute?
				return response
			else
				request.path = Reference[request.path] + location
			end
			
			if request.method == GET or response.preserve_method?
				# We (might) need to rewind the body so that it can be submitted again:
				body&.rewind
			else
				# We are changing the method to GET:
				request.method = GET
				
				# Clear the request body:
				request.finish
				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

#redirect_with_get?(request, response) ⇒ Boolean

Returns:

  • (Boolean)


67
68
69
70
71
72
73
# File 'lib/async/http/relative_location.rb', line 67

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