aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/journey/router.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/journey/router.rb')
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb153
1 files changed, 153 insertions, 0 deletions
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
new file mode 100644
index 0000000000..89a164f968
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+require "action_dispatch/journey/router/utils"
+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
+ module Journey # :nodoc:
+ class Router # :nodoc:
+ attr_accessor :routes
+
+ def initialize(routes)
+ @routes = routes
+ end
+
+ def eager_load!
+ # Eagerly trigger the simulator's initialization so
+ # it doesn't happen during a request cycle.
+ simulator
+ nil
+ end
+
+ def serve(req)
+ find_routes(req).each do |match, parameters, route|
+ set_params = req.path_parameters
+ path_info = req.path_info
+ script_name = req.script_name
+
+ unless route.path.anchored
+ req.script_name = (script_name.to_s + match.to_s).chomp("/")
+ req.path_info = match.post_match
+ req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
+ end
+
+ parameters = route.defaults.merge parameters.transform_values { |val|
+ val.dup.force_encoding(::Encoding::UTF_8)
+ }
+
+ req.path_parameters = set_params.merge parameters
+
+ status, headers, body = route.app.serve(req)
+
+ if "pass" == headers["X-Cascade"]
+ req.script_name = script_name
+ req.path_info = path_info
+ req.path_parameters = set_params
+ next
+ end
+
+ return [status, headers, body]
+ end
+
+ [404, { "X-Cascade" => "pass" }, ["Not Found"]]
+ end
+
+ def recognize(rails_req)
+ find_routes(rails_req).each do |match, parameters, route|
+ unless route.path.anchored
+ rails_req.script_name = match.to_s
+ rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1')
+ end
+
+ parameters = route.defaults.merge parameters
+ yield(route, parameters)
+ end
+ end
+
+ def visualizer
+ tt = GTG::Builder.new(ast).transition_table
+ groups = partitioned_routes.first.map(&:ast).group_by(&:to_s)
+ asts = groups.values.map(&:first)
+ tt.visualizer(asts)
+ end
+
+ private
+
+ def partitioned_routes
+ routes.partition { |r|
+ r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
+ }
+ end
+
+ def ast
+ routes.ast
+ end
+
+ def simulator
+ routes.simulator
+ end
+
+ def custom_routes
+ routes.custom_routes
+ end
+
+ def filter_routes(path)
+ return [] unless ast
+ simulator.memos(path) { [] }
+ end
+
+ def find_routes(req)
+ routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
+ r.path.match(req.path_info)
+ }
+
+ routes =
+ if req.head?
+ match_head_routes(routes, req)
+ else
+ match_routes(routes, req)
+ end
+
+ routes.sort_by!(&:precedence)
+
+ routes.map! { |r|
+ match_data = r.path.match(req.path_info)
+ path_parameters = {}
+ match_data.names.zip(match_data.captures) { |name, val|
+ path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
+ }
+ [match_data, path_parameters, r]
+ }
+ end
+
+ def match_head_routes(routes, req)
+ verb_specific_routes = routes.select(&:requires_matching_verb?)
+ head_routes = match_routes(verb_specific_routes, req)
+
+ if head_routes.empty?
+ begin
+ req.request_method = "GET"
+ match_routes(routes, req)
+ ensure
+ req.request_method = "HEAD"
+ end
+ else
+ head_routes
+ end
+ end
+
+ def match_routes(routes, req)
+ routes.select { |r| r.matches?(req) }
+ end
+ end
+ end
+end