Class: Rack::Rewrite

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/rewrite.rb,
lib/rack/rewrite/rule.rb

Overview

A rack middleware for defining and applying rewrite rules. In many cases you
can get away with rack-rewrite instead of writing Apache mod_rewrite rules.

Defined Under Namespace

Classes: Rule, RuleSet

Instance Method Summary collapse

Constructor Details

#initialize(app, given_options = {}) ⇒ Rewrite

Returns a new instance of Rewrite.



9
10
11
12
13
14
15
16
17
18
19
# File 'lib/rack/rewrite.rb', line 9

def initialize(app, given_options = {}, &)
  options = {
    klass: RuleSet,
    options: {}
  }.merge(given_options)
  @app = app
  @rule_set = options[:klass].new(options[:options])
  @logger = options[:logger] || ::Logger.new($stdout)
  @redis = options[:redis]
  @rule_set.instance_eval(&) if block_given?
end

Instance Method Details

#call(env) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/rack/rewrite.rb', line 21

def call(env)
  if (matched_rule = find_first_matching_rule(env))
    rack_response = matched_rule.apply!(env)
    rule_key = matched_rule.from.inspect
    rule_details = { rule_type: matched_rule.rule_type, from: rule_key, to: matched_rule.to }
    significant_options = matched_rule.options.dup.delete(:'Cache-Control')
    rule_details[:options] = significant_options if significant_options.present?
    unless redis.nil?
      redis_match = JSON.parse(redis.get(rule_key) || {})
      # Log referring agent for bots for further analysis
      # Test by uncommenting this
      # ua = "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.79 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
      if (ua ||= env['HTTP_USER_AGENT']).present?
        require 'device_detector'
        # Ensure all header values are strings to prevent nil gsub errors in device_detector
        safe_headers = (env || {}).select { |k, v| k.start_with?('HTTP_') }.transform_values { |v| v.nil? ? '' : v.to_s }
        dd = DeviceDetector.new(ua, safe_headers)
        # log bots
        if dd.bot?
          bn = dd.bot_name
          rule_details['bots'] = redis_match['bots'] || []
          rule_details['bots'] << bn unless rule_details['bots'].include?(bn)
          rule_details['bots'].sort!
        end
      end
      # Log Referring page
      # Test by uncommenting this
      # ref = "http://www.somepage.com/referring_page"
      if (ref ||= env['HTTP_REFERER']).present?
        rule_details['ref'] = redis_match['ref'] || []
        rule_details['ref'] << ref unless rule_details['ref'].include?(ref)
        rule_details['ref'].sort!
      end
      # Log original request path and query string
      if (uri = env['REQUEST_URI']).present?
        rule_details['uri'] = redis_match['uri'] || []
        rule_details['uri'] << uri unless rule_details['uri'].include?(uri)
        rule_details['uri'].sort!
      end
      rule_details['counter'] = redis_match.fetch('counter', 0).to_i + 1
      rule_details['last_use'] = Time.current.to_s
      redis.set(rule_key, rule_details.to_json)
    end
    # logger&.info "[REDIRECT] #{rule_details.inspect}"
    # Stringify the keys in our rack response to prevent puma from choking
    # https://medium.com/@ssscripting/puma-typeerror-no-implicit-conversion-of-symbol-into-string-d58df06a780b
    rack_response[1]&.stringify_keys! if rack_response[1].respond_to?(:stringify_keys!)
    # Don't invoke the app if applying the rule returns a rack response
    return rack_response unless rack_response === true
  end
  @app.call(env)
end