首先 Rails4+,默认的 routes 是’config/routes.rb’。要想拆分 routes,可以往 paths 里加入:
class Application < Rails::Application
config.paths['config/routes.rb'].concat ['config/routes2.rb']
end
在 rails3 里,采用的是 config.paths['config/routes’]
ActionDispatch::Routing::Mapper::HttpHelpers 中定义了在 route 里可以设置的 5 种 HTTP via
get 'bacon', to: 'food#bacon’
post 'bacon', to: 'food#bacon’
patch 'bacon', to: 'food#bacon’
put 'bacon', to: 'food#bacon’
delete 'broccoli', to: 'food#broccoli’
最终调用的还是 match 方法
match 'path' => 'controller#action', via: patch
match 'path', to: 'controller#action', via: :post
match 'path', 'otherpath', on: :member, via: :together
所以,直接写成 match 方法,似乎,也算少执行一些代码。
Rails 路由的加载的地方,所有被拆分的路由文件放在@paths里
ActionDispatch::Routing::Mapper::Base 里定义了路由的匹配规则,具体可以看看代码注释 ActionDispatch::Routing::RouteSet
通过 initializer :add_routing_paths 的初始器,指定了 routes.rb 的文件位置,
initializer :add_routing_paths do |app|
routing_paths = self.paths["config/routes.rb"].existent
if routes? || routing_paths.any?
app.routes_reloader.paths.unshift(*routing_paths)
app.routes_reloader.route_sets << routes
end
end
可以在 app.routes_reloader.paths 中加入多个 routes 文件。
set_routes_reloader_hook 初始化器,开始执行 routes 文件里的代码,
initializer :set_routes_reloader_hook do |app|
reloader = routes_reloader
reloader.execute_if_updated
reloaders << reloader
app.reloader.to_run do
# We configure #execute rather than #execute_if_updated because if
# autoloaded constants are cleared we need to reload routes also in
# case any was used there, as in
#
# mount MailPreview => 'mail_view'
#
# This means routes are also reloaded if i18n is updated, which
# might not be necessary, but in order to be more precise we need
# some sort of reloaders dependency support, to be added.
require_unload_lock!
reloader.execute
end
end
initializer :build_middleware_stack do
build_middleware_stack
end
alias :build_middleware_stack :app
def app
@app || @app_build_lock.synchronize {
@app ||= begin
stack = default_middleware_stack
config.middleware = build_middleware.merge_into(stack)
config.middleware.build(endpoint)
end
}
end
def endpoint
self.class.endpoint || routes
end
def routes
@routes ||= ActionDispatch::Routing::RouteSet.new_with_config(config)
@routes.append(&Proc.new) if block_given?
@routes
end
简而言之,就是:build_middleware_stack 初始化一个 RouteSet,作为第一个 rack 加入 middleware,:add_routing_paths 指定了路由的 path,:set_routes_reloader_hook 执行路由文件,装配路由。
当发送请求时,http 服务器会开始调用 middleware 的 call 方法,最底层的 RouteSet,会根据 env 生成一个 ActionDispatch::Request 对象。
def call(env)
req = make_request(env)
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
@router.serve(req)
end
@router 是一个 ActionDispatch::Journey::Router 对象,里面包含一个@routes,是 ActionDispatch::Journey::Routes 的对象,@routes里包含许多 ActionDispatch::Journey::Route 对象,每一个都是一条 http 的请求匹配模式。
每一个 http 请求先包装成一个 ActionDispatch::Routing::Mapper 对象,指定了@scope_level,@concerns,@scope。
mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
@set.add_route(mapping, ast, as, anchor)
每一个 Mapper 对象,包装成 ActionDispatch::Routing::Mapper::Mapping 对象。 RouteSet 的初始化方法
def initialize(config = DEFAULT_CONFIG)
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names
self.default_url_options = {}
@config = config
@append = []
@prepend = []
@disable_clear_and_finalize = false
@finalized = false
@env_key = "ROUTES_#{object_id}_SCRIPT_NAME".freeze
@set = Journey::Routes.new
@router = Journey::Router.new @set
@formatter = Journey::Formatter.new self
end
此处的@set就是 RouteSet 对象,@set.add_route里调用了 Journey::Routes.new.add_route
def add_route(name, mapping)
route = mapping.make_route name, routes.length
routes << route
partition_route(route)
clear_cache!
route
end
def make_route(name, precedence)
route = Journey::Route.new(name,
application,
path,
conditions,
required_defaults,
defaults,
request_method,
precedence,
@internal)
route
end
在 Journey::Routes.new.add_route 里,调用 mapping.make_route,make_route 生成一条 http 请求的匹配模式,加入到 Journey::Routes 中 Journey::Routes << Journey::Route RouteSet.@set = Journey::Routes RouteSet.@router = Journey::Router.new @set 至此 RouteSet 和 @router,Routes,Route 关联起来了。
当调用 RouteSet 的 call 方法,就调用@router的 serve 方法
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
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
return [404, { "X-Cascade" => "pass" }, ["Not Found"]]
end
此处的 route.app 是 make_route 创建 route 是,传入的 application,是一个 Routing::Endpoint 类,这里是它的一个子类 ActionDispatch::Routing::RouteSet::Dispatcher 对象
def serve(req)
params = req.path_parameters
controller = controller req
res = controller.make_response! req
dispatch(controller, params[:action], req, res)
rescue ActionController::RoutingError
if @raise_on_name_error
raise
else
return [404, { "X-Cascade" => "pass" }, []]
end
end
此时 make_response 定义在 ActionController::Base 中,生成 ActionDispatch::Response 对象
dispatch 调用到对应的 action,返回 rack 的返回值
def dispatch(name, request, response) #:nodoc:
set_request!(request)
set_response!(response)
process(name)
request.commit_flash
to_a
end
def rack_response(status, header)
if NO_CONTENT_CODES.include?(status)
[status, header, []]
else
[status, header, RackBody.new(self)]
end
end
此处的 self 就是 ActionDispatch::Response 对象。