Class: HTTP::Redirector

Inherits:
Object
  • Object
show all
Defined in:
lib/http/redirector.rb,
sig/http.rbs

Overview

Follows HTTP redirects according to configured policy

Defined Under Namespace

Classes: EndlessRedirectError, TooManyRedirectsError

Constant Summary collapse

REDIRECT_CODES =

HTTP status codes which indicate redirects

Returns:

  • (Set[Integer])
[300, 301, 302, 303, 307, 308].to_set.freeze
STRICT_SENSITIVE_CODES =

Codes which which should raise StateError in strict mode if original request was any of UNSAFE_VERBS

Returns:

  • (Set[Integer])
[300, 301, 302].to_set.freeze
UNSAFE_VERBS =

Insecure http verbs, which should trigger StateError in strict mode upon STRICT_SENSITIVE_CODES

Returns:

  • (Set[Symbol])
%i[put delete post].to_set.freeze
SEE_OTHER_ALLOWED_VERBS =

Verbs which will remain unchanged upon See Other response.

Returns:

  • (Set[Symbol])
%i[get head].to_set.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(strict: true, max_hops: 5, on_redirect: nil) ⇒ HTTP::Redirector

Initializes a new Redirector

Examples:

HTTP::Redirector.new(strict: true, max_hops: 5)

Parameters:

  • strict (Boolean) (defaults to: true)

    (true) redirector hops policy

  • max_hops (#to_i) (defaults to: 5)

    (5) maximum allowed amount of hops

  • on_redirect (#call, nil) (defaults to: nil)

    optional redirect callback

  • strict: (Boolean) (defaults to: true)
  • max_hops: (Integer) (defaults to: 5)
  • on_redirect: (^(Response, Request) -> void, nil) (defaults to: nil)


56
57
58
59
60
# File 'lib/http/redirector.rb', line 56

def initialize(strict: true, max_hops: 5, on_redirect: nil)
  @strict      = strict
  @max_hops    = Integer(max_hops)
  @on_redirect = on_redirect
end

Instance Attribute Details

#max_hopsFixnum (readonly)

Returns maximum allowed hops

Examples:

redirector.max_hops # => 5

Returns:

  • (Fixnum)


44
45
46
# File 'lib/http/redirector.rb', line 44

def max_hops
  @max_hops
end

#strictBoolean (readonly)

Returns redirector policy

Examples:

redirector.strict # => true

Returns:

  • (Boolean)


35
36
37
# File 'lib/http/redirector.rb', line 35

def strict
  @strict
end

Instance Method Details

#endless_loop?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Check if we got into an endless loop

Returns:

  • (Boolean)


121
122
123
# File 'lib/http/redirector.rb', line 121

def endless_loop?
  @visited.count(@visited.last) > 1
end

#follow_redirects {|arg0| ... } ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Perform a single redirect step

Yields:

Yield Parameters:

Yield Returns:

Raises:



87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/http/redirector.rb', line 87

def follow_redirects
  @visited << visit_key

  raise TooManyRedirectsError if too_many_hops?
  raise EndlessRedirectError  if endless_loop?

  @response.flush

  @request = redirect_to(redirect_uri)
  @on_redirect&.call @response, @request
  @response = yield @request
end

#perform(request, response) {|arg0| ... } ⇒ HTTP::Response

Follows redirects until non-redirect response found

Examples:

redirector.perform(request, response) { |req| client.perform(req) }

Parameters:

Yields:

Yield Parameters:

Yield Returns:

Returns:



71
72
73
74
75
76
77
78
79
# File 'lib/http/redirector.rb', line 71

def perform(request, response, &)
  @request  = request
  @response = response
  @visited  = []

  follow_redirects(&) while REDIRECT_CODES.include?(@response.code)

  @response
end

#redirect_to(uri) ⇒ Request

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Redirect policy for follow

Parameters:

  • uri (String, nil)

Returns:

Raises:



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/http/redirector.rb', line 140

def redirect_to(uri)
  raise StateError, "no Location header in redirect" unless uri

  verb = @request.verb
  code = @response.code

  if UNSAFE_VERBS.include?(verb) && STRICT_SENSITIVE_CODES.include?(code)
    raise StateError, "can't follow #{@response.status} redirect" if @strict

    verb = :get
  end

  verb = :get if !SEE_OTHER_ALLOWED_VERBS.include?(verb) && code.eql?(303)

  @request.redirect(uri, verb)
end

#redirect_uriString?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Extracts the redirect URI from the Location header

Returns:

  • (String, nil)

    URI string or nil if no Location header



104
105
106
107
# File 'lib/http/redirector.rb', line 104

def redirect_uri
  location = @response.headers.get(Headers::LOCATION)
  location.join unless location.empty?
end

#too_many_hops?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Check if we reached max amount of redirect hops

Returns:

  • (Boolean)


113
114
115
# File 'lib/http/redirector.rb', line 113

def too_many_hops?
  @max_hops.positive? && @visited.length > @max_hops
end

#visit_keyString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Build a visit key for the current request

Includes verb, URI, and Cookie header so that requests to the same URL with different cookies are not falsely detected as an endless loop.

Returns:

  • (String)


132
133
134
# File 'lib/http/redirector.rb', line 132

def visit_key
  "#{@request.verb} #{@request.uri} #{@request.headers[Headers::COOKIE]}"
end