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.



44
45
46
47
48
# File 'lib/async/http/relative_location.rb', line 44

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.



51
52
53
# File 'lib/async/http/relative_location.rb', line 51

def maximum_hops
  @maximum_hops
end

Instance Method Details

#call(request) ⇒ Object

Raises:



61
62
63
64
65
66
67
68
69
70
71
72
73
74
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
# File 'lib/async/http/relative_location.rb', line 61

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)


53
54
55
56
57
58
59
# File 'lib/async/http/relative_location.rb', line 53

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