kivanio (owner)

Fork Of

gist: 208916 by jtrupiano A rack middleware for defin...

Revisions

gist: 227920 Download_button fork
public
Public Clone URL: git://gist.github.com/227920.git
Embed All Files: show embed
Ruby #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
117
118
119
120
121
122
123
124
# This is actually available as a gem: gem install rack-rewrite
# Full source code including tests is on github: http://github.com/jtrupiano/rack-rewrite
 
module Rack
  # 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.
  class Rewrite
    def initialize(app, &rule_block)
      @app = app
      @rule_set = RuleSet.new
      @rule_set.instance_eval(&rule_block) if block_given?
    end
    
    def call(env)
      if matched_rule = find_first_matching_rule(env)
        rack_response = matched_rule.apply!(env)
        # 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
        
    private
      def find_first_matching_rule(env) #:nodoc:
        @rule_set.rules.detect { |rule| rule.matches?(env['REQUEST_URI']) }
      end
 
    class RuleSet
      attr_reader :rules
      def initialize #:nodoc:
        @rules = []
      end
 
      protected
        # We're explicitly defining private functions for our DSL rather than
        # using method_missing
        
        # Creates a rewrite rule that will simply rewrite the REQUEST_URI,
        # PATH_INFO, and QUERYSTRING headers of the Rack environment. The
        # user's browser will continue to show the initially requested URL.
        #
        # rewrite '/wiki/John_Trupiano', '/john'
        # rewrite %r{/wiki/(\w+)_\w+}, '/$1'
        def rewrite(from, to)
          @rules << Rule.new(:rewrite, from, to)
        end
        
        # Creates a redirect rule that will send a 301 when matching.
        #
        # r301 '/wiki/John_Trupiano', '/john'
        # r301 '/contact-us.php', '/contact-us'
        def r301(from, to)
          @rules << Rule.new(:r301, from, to)
        end
        
        # Creates a redirect rule that will send a 302 when matching.
        #
        # r302 '/wiki/John_Trupiano', '/john'
        # r302 '/wiki/(.*)', 'http://www.google.com/?q=$1'
        def r302(from, to)
          @rules << Rule.new(:r302, from, to)
        end
    end
 
    # TODO: Break rules into subclasses
    class Rule #:nodoc:
      attr_reader :rule_type, :from, :to
      def initialize(rule_type, from, to) #:nodoc:
        @rule_type, @from, @to = rule_type, from, to
      end
 
      def matches?(path) #:nodoc:
        case self.from
        when Regexp
          path =~ self.from
        when String
          path == self.from
        else
          false
        end
      end
 
      # Either (a) return a Rack response (short-circuiting the Rack stack), or
      # (b) alter env as necessary and return true
      def apply!(env) #:nodoc:
        interpreted_to = self.send(:interpret_to, env['REQUEST_URI'])
        case self.rule_type
        when :r301
          [301, {'Location' => interpreted_to}, ['Redirecting...']]
        when :r302
          [302, {'Location' => interpreted_to}, ['Redirecting...']]
        when :rewrite
          # return [200, {}, {:content => env.inspect}]
          env['REQUEST_URI'] = interpreted_to
          if q_index = interpreted_to.index('?')
            env['PATH_INFO'] = interpreted_to[0..q_index-1]
            env['QUERYSTRING'] = interpreted_to[q_index+1..interpreted_to.size-1]
          else
            env['PATH_INFO'] = interpreted_to
            env['QUERYSTRING'] = ''
          end
          true
        else
          raise Exception.new("Unsupported rule: #{self.rule_type}")
        end
      end
      
      private
        # is there a better way to do this?
        def interpret_to(path) #:nodoc:
          if self.from.is_a?(Regexp)
            if from_match_data = self.from.match(path)
              computed_to = self.to.dup
              (from_match_data.size - 1).downto(1) do |num|
                computed_to.gsub!("$#{num}", from_match_data[num])
              end
              return computed_to
            end
          end
          self.to
        end
    end
  end
end