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



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