Ruby On Rails-2.0.2源代码分析(4)-寻找Contro…

2008-04-02 02:27:10来源: 阅读 ()

新老客户大回馈,云服务器低至5折

  • 前言

  经过一番试验和考虑...一,我尝试了一些思维导图工具(MindMapper,FREEMIND),但我始终没有找到一种好的方式将自己学习Rails源代码的思路表述出来,就此作罢(顺便问问,有研究思维导图的同学么?能否推荐两个自己觉得用起来比较顺手的工具)。二,不再打算整理代码运行顺序图,对不熟悉Rails源代码的同学们来说,这个图可能的确没什么帮助,甚至会把人搞晕。我现在打算从Rails源代码功能点的角度出发,根据具体功能点,结合Rails源代码进行学习,整理,总结。如果某些源代码比较复杂,牵涉类比较繁多,我仍然打算整理一个类图,从一个高的层次了解系统内部对象的关系。
  前面三篇文章,我们看到了Rails启动的大致功能和流程,包括初始化多种环境变量,初始化Route表,启动Web服务器开始侦听客户端请求。。。那么接下来,当然是开门迎客,等待客户端(浏览器)的请求,并进行处理,最终将结果返回客户端(浏览器)呈现。那么熟悉Rails的同学都知道,首先,Rails必须根据客户端的一个请求,决定将要执行哪个Controller的哪个Action,这也是本文的主要目的。

  • 寻找Controller

首先,我们先来看一看Rails通过客户端请求,查找Controller的大致流程图
  (一)生成DisapatchServlet实例,开始服务吧
  源代码:gems/rails-2.0.2/lib/webrick_server.rb
  在第一篇文章,讲解Rails的启动时,我提到webrick_server.rb中定义了DisapatchServlet类,此类启动了WEBrick,开始侦听客户端请求。当有客户端请求到达时,会生成一个DispatchServlet实例,具体代码如下:

 

Ruby代码 复制代码
  1. class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet   
  2.   
  3.   def initialize(server, options) #:nodoc:   
  4.     @server_options = options   
  5.     @file_handler = WEBrick::HTTPServlet::FileHandler.new(server, options[:server_root])   
  6.     # Change to the RAILS_ROOT, since Webrick::Daemon.start does a Dir::cwd("/")   
  7.     # OPTIONS['working_directory'] is an absolute path of the RAILS_ROOT, set in railties/lib/commands/servers/webrick.rb   
  8.     Dir.chdir(OPTIONS['working_directory']) if defined?(OPTIONS) && File.directory?(OPTIONS['working_directory'])   
  9.     super  
  10.   end  
  11.   ...   
  12. end  


  初始化参数server是web服务器的类型,当然,在我的环境中是WEBRick::HTTPServer。option是一个hash,包含了一些列的环境参数,这里,我将一些比较重要的参数罗列出来:

名称 类型 参考值
port Fixnum 3000
ip String 0.0.0.0(因为我是本机操作)
environment String development
charset String UTF-8
working_directory String D:\Project\Ruby\blog
  初始化中,首先将option参数赋DispatchServlet的@server_options变量,然后生成一个FileHandler对象,这个对象的具体作用马上会提到。紧接着将Rails的工作目录设置为“working_directory”,也就是前面文章提到过的RAIL_ROOT。至此,DsipatchServlet的初始化工作完成了。WEBRick会执行此Servlet的service方法。

  (二)是否存在相应html
  源代码:gems/rails-2.0.2/lib/webrick_server.rb
  第一步生成了Servlet实例,并且,开始执行service方法,我们先来看看service方法的具体内容:

 

Ruby代码 复制代码
  1. class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet   
  2.   
  3.   def service(req, res) #:nodoc:   
  4.     unless handle_file(req, res)   
  5.       begin  
  6.         REQUEST_MUTEX.lock unless ActionController::Base.allow_concurrency   
  7.         unless handle_dispatch(req, res)   
  8.           raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."  
  9.         end  
  10.       ensure  
  11.         unless ActionController::Base.allow_concurrency   
  12.           REQUEST_MUTEX.unlock if REQUEST_MUTEX.locked?   
  13.         end  
  14.       end  
  15.     end  
  16.   end  
  17.   ...   
  18. end  


  此方法算是处理一个Request的最高层次描述,首先是方法handle_file。这个方法会使用初始化生成的FileHandler对象,查找针对客户端请求的path,在RAILS_ROOT/public目录下是否存在相应的html。例如客户端的请求是http://localhost:3000/posts,那么首先Rails就使用FileHandler查找在public根目录下面是否存在posts.html,如果存在的话,则直接向客户端呈现这个html,如果不存在,OK,开始寻找Controller吧。
  (这里,值得一提是并发控制,默认情况下,Rails只允许一次dispatch一个request,当然,我们可以通过在程序配置文件中设置ActionController::Base.allow_concurrency来改变这个默认的行为。)
  (我想你应该知道很多Rails书籍提到过,如果你在routes.rb中通过map.root :controller=>'posts'的方式,使得当用户通过http://www.yoursite.com访问站点时,显示相应的功能页面。但是你必须把public下的index.html删除掉,就是这个原因。)
  (handle_file源代码不列出,因为他十分简单,只是调用FileHandler的相应方法,而WEBRick暂不在研究范围内。)

  (三)开始Dispatch吧
  源代码:gems/rails-2.0.2/lib/webrick_server.rb
              gems/actionpack-2.0.2/lib/action_controller/dispatcher.rb

  第二步说了,如果没有相应的html存在的话,Rails将执行Dispatch过程。我们先来看一看handle_dispatch方法:

Ruby代码 复制代码
  1. def handle_dispatch(req, res, origin = nil#:nodoc:   
  2.   data = StringIO.new  
  3.   Dispatcher.dispatch(   
  4.     CGI.new("query", create_env_table(req, origin), StringIO.new(req.body || "")),   
  5.     ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,   
  6.     data   
  7.   )   
  8.   ...   
  9. end  


  这里,可以看到Dispatch的主角Dispatcher对象开始登场了。要执行dispatch,首先生成一个CGI对象(默认CGI类型是“query”,并且将环境配置传递给CGI对象,包括:主机名称,查询字符串,字符集,Path信息...等,以及默认的Session管理方式),其中的data表示对用户的返回数据(StringIO请参考相应的API)。然后执行Dispatcher的类方法dispatch。此方法内容如下:

Ruby代码 复制代码
  1. class Dispatcher   
  2.   class << self  
  3.     # Backward-compatible class method takes CGI-specific args. Deprecated   
  4.     # in favor of Dispatcher.new(output, request, response).dispatch.   
  5.     def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)   
  6.       new(output).dispatch_cgi(cgi, session_options)   
  7.     end  
  8.   ...   
  9. end  


  此类方法将生成一个Dispatcher实例,并调用其dispatch_cgi实例方法(从前面的方法调用,我想不难看出每一个参数是什么)。我们继续接着看dispatch_cgi方法:

Ruby代码 复制代码
  1. def dispatch_cgi(cgi, session_options)   
  2.   if cgi ||= self.class.failsafe_response(@output'400 Bad Request') { CGI.new }   
  3.     @request = CgiRequest.new(cgi, session_options)   
  4.     @response = CgiResponse.new(cgi)   
  5.     dispatch   
  6.   end  
  7. rescue Exception => exception   
  8.   failsafe_rescue exception   
  9. end  


  前面也有request和reponse,这里又生成了一个request和response。我是这样理解的,前面handle_dispatch接收的req和res是“原生”的对象----WEBRick::HTTPRequest和WEBRick::HTTPResponse(是WEBRick和Rails的通讯方式),而这里的request和response是CgiRequest和CgiResponse对象,是针对Dispatch的通讯(CgiRequest和CgiResponse的细节这里先略过,我们看看主流程)。有了request和response对象,真正的dispatch过程开始了:

Ruby代码 复制代码
  1. def dispatch   
  2.   run_callbacks :before  
  3.   handle_request   
  4. rescue Exception => exception   
  5.   failsafe_rescue exception   
  6. ensure  

上一篇:有人用过cell么?

下一篇:Ruby On Rails-2.0.2源代码分析(3)-named route和resource