router.rb 3.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
require 'action_dispatch/journey/router/utils'
require 'action_dispatch/journey/router/strexp'
require 'action_dispatch/journey/routes'
require 'action_dispatch/journey/formatter'

before = $-w
$-w = false
require 'action_dispatch/journey/parser'
$-w = before

require 'action_dispatch/journey/route'
require 'action_dispatch/journey/path/pattern'

module ActionDispatch
15 16 17
  module Journey # :nodoc:
    class Router # :nodoc:
      class RoutingError < ::StandardError # :nodoc:
18 19
      end

20
      # :nodoc:
21 22 23 24
      VERSION = '2.0.0'

      attr_accessor :routes

25 26
      def initialize(routes)
        @routes = routes
27 28
      end

29
      def serve(req)
30
        find_routes(req).each do |match, parameters, route|
31 32 33
          set_params  = req.path_parameters
          path_info   = req.path_info
          script_name = req.script_name
34 35

          unless route.path.anchored
36 37
            req.script_name = (script_name.to_s + match.to_s).chomp('/')
            req.path_info = match.post_match
38 39
          end

40
          req.path_parameters = set_params.merge parameters
41

42
          status, headers, body = route.app.call(req.env)
43 44

          if 'pass' == headers['X-Cascade']
45 46
            req.script_name     = script_name
            req.path_info       = path_info
47
            req.path_parameters = set_params
48 49 50 51 52 53 54 55 56
            next
          end

          return [status, headers, body]
        end

        return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
      end

57
      def recognize(rails_req)
58
        find_routes(rails_req).each do |match, parameters, route|
59
          unless route.path.anchored
60 61
            rails_req.script_name = match.to_s
            rails_req.path_info   = match.post_match.sub(/^([^\/])/, '/\1')
62 63
          end

64
          yield(route, parameters)
65 66 67 68 69 70 71
        end
      end

      def visualizer
        tt     = GTG::Builder.new(ast).transition_table
        groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
        asts   = groups.values.map { |v| v.first }
72
        tt.visualizer(asts)
73 74 75 76
      end

      private

77 78 79
        def partitioned_routes
          routes.partitioned_routes
        end
80

81 82 83
        def ast
          routes.ast
        end
84

85 86 87
        def simulator
          routes.simulator
        end
88

89 90 91
        def custom_routes
          partitioned_routes.last
        end
92

93 94
        def filter_routes(path)
          return [] unless ast
95
          simulator.memos(path) { [] }
96
        end
97

98
        def find_routes req
99 100 101 102
          routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
            r.path.match(req.path_info)
          }
          routes.concat get_routes_as_head(routes)
103

104
          routes.sort_by!(&:precedence).select! { |r| r.matches?(req) }
105

106 107
          routes.map! { |r|
            match_data  = r.path.match(req.path_info)
108
            path_parameters = r.defaults.dup
109 110 111
            match_data.names.zip(match_data.captures) { |name,val|
              path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
            }
112
            [match_data, path_parameters, r]
113 114
          }
        end
115

116 117
        def get_routes_as_head(routes)
          precedence = (routes.map(&:precedence).max || 0) + 1
118
          routes.select { |r|
119 120 121 122 123 124 125 126 127 128 129
            r.verb === "GET" && !(r.verb === "HEAD")
          }.map! { |r|
            Route.new(r.name,
                      r.app,
                      r.path,
                      r.conditions.merge(request_method: "HEAD"),
                      r.defaults).tap do |route|
                        route.precedence = r.precedence + precedence
                      end
          }
        end
130 131 132
    end
  end
end