- 浏览: 42065 次
- 性别:
- 来自: 北京
最新评论
-
lijingtx:
为什么我报错了。in `alias_method`:undef ...
Rails中如何更加优雅的处理文件上传 -
gigix:
woody_420420 写道rainchen 写道几时cuc ...
让测试并行起来吧 -
woody_420420:
rainchen 写道几时cucumber也能并发跑scena ...
让测试并行起来吧 -
rainchen:
有时为了保证测试环境和开发、生产的特性一致,减少非必要的环境差 ...
让测试并行起来吧 -
woody_420420:
是的。耗时主要在数据库访问上。
内存数据库,我倒真没想过用这个 ...
让测试并行起来吧
-
前言
经过一番试验和考虑...一,我尝试了一些思维导图工具(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实例,具体代码如下:
class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet def initialize(server, options) #:nodoc: @server_options = options @file_handler = WEBrick::HTTPServlet::FileHandler.new(server, options[:server_root]) # Change to the RAILS_ROOT, since Webrick::Daemon.start does a Dir::cwd("/") # OPTIONS['working_directory'] is an absolute path of the RAILS_ROOT, set in railties/lib/commands/servers/webrick.rb Dir.chdir(OPTIONS['working_directory']) if defined?(OPTIONS) && File.directory?(OPTIONS['working_directory']) super end ... 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方法的具体内容:
class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet def service(req, res) #:nodoc: unless handle_file(req, res) begin REQUEST_MUTEX.lock unless ActionController::Base.allow_concurrency unless handle_dispatch(req, res) raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." end ensure unless ActionController::Base.allow_concurrency REQUEST_MUTEX.unlock if REQUEST_MUTEX.locked? end end end end ... 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方法:
def handle_dispatch(req, res, origin = nil) #:nodoc: data = StringIO.new Dispatcher.dispatch( CGI.new("query", create_env_table(req, origin), StringIO.new(req.body || "")), ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, data ) ... end
这里,可以看到Dispatch的主角Dispatcher对象开始登场了。要执行dispatch,首先生成一个CGI对象(默认CGI类型是“query”,并且将环境配置传递给CGI对象,包括:主机名称,查询字符串,字符集,Path信息...等,以及默认的Session管理方式),其中的data表示对用户的返回数据(StringIO请参考相应的API)。然后执行Dispatcher的类方法dispatch。此方法内容如下:
class Dispatcher class << self # Backward-compatible class method takes CGI-specific args. Deprecated # in favor of Dispatcher.new(output, request, response).dispatch. def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout) new(output).dispatch_cgi(cgi, session_options) end ... end
此类方法将生成一个Dispatcher实例,并调用其dispatch_cgi实例方法(从前面的方法调用,我想不难看出每一个参数是什么)。我们继续接着看dispatch_cgi方法:
def dispatch_cgi(cgi, session_options) if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new } @request = CgiRequest.new(cgi, session_options) @response = CgiResponse.new(cgi) dispatch end rescue Exception => exception failsafe_rescue exception 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过程开始了:
def dispatch run_callbacks :before handle_request rescue Exception => exception failsafe_rescue exception ensure
先来看一看run_callbacks:
def run_callbacks(kind, enumerator = :each) callbacks[kind].send!(enumerator) do |callback| case callback when Proc; callback.call(self) when String, Symbol; send!(callback) when Array; callback[1].call(self) else raise ArgumentError, "Unrecognized callback #{callback.inspect}" end end end
其中的callbacks(hash)是Dispatcher的类属性,用来执行一些dispatch前,后,准备的工作。这里,我们直接看看他的值
{:before=>[:reload_application,:prepare_application],:after=>[:flush_logger,:cleanup_application],:prepare=>[:activerecord_instantiate_observers,"Proc"]}。就这里而言,我们要执行所有:before的callback,不一一列出,只看其中一个:
def reload_application if Dependencies.load? Routing::Routes.reload self.unprepared = true end end
这段代码揭示了在程序运行时,我们改动了routes.rb中的路由信息后,下一次request马上就能生效的原理。另外prepare_application的功能是require我们熟悉的Controller/application.rb,并且验证ActiveRecord的数据库连接是否正常(当然,你需要使用AR框架的话)。好了,这里稍微偏离了主线,接下来,让我们回到dispatch方法中,看看下面的调用handle_request:
def handle_request @controller = Routing::Routes.recognize(@request) @controller.process(@request, @response).out(@output) end
上面的代码非常直观,首先通过Routing系统,根据客户端的request找到相应的controller,然后执行并且将返回数据写入到@output中(这也是我前面提到的那个StringIO对象)。至于如何具体找到controller的,进入下一步吧。
(四)寻找controller
源代码:/actionpack-2.0.2/lib/action_controller/routing.rb
从前面的方法调用中,我们看出寻找controller的入口是RouteSet对象的recognize方法(还记得Routing::Routes是一个RouteSet的对象实例吗?要理解Rails中的Routing子系统,我在第二篇文章中整理的那张类图十分重要!)。下面看看此方法的具体内容:
def recognize(request) params = recognize_path(request.path, extract_request_environment(request)) request.path_parameters = params.with_indifferent_access "#{params[:controller].camelize}Controller".constantize end
首先调用recognize_path方法,其中request.path是客户端请求的路径(比如:如果客户端访问地址是http://localhost:3000/posts
,那么此参数就是/posts,extract_request_environment(request)方法只是得到请求的http方法(get,post,put,delete),当然,此方法返回的结果params便是我们的Controller和Action。我们知道,Routing系统通过path和http
verb就可以确定应该使用哪个Controller的哪个Action,下面看看他是怎么做到的吧:
def recognize_path(path, environment={}) routes.each do |route| result = route.recognize(path, environment) and return result end allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } } if environment[:method] && !HTTP_METHODS.include?(environment[:method]) raise NotImplemented.new(*allows) elsif !allows.empty? raise MethodNotAllowed.new(*allows) else raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}" end end
如果你看过我的第二,三篇文章,我想你应该知道这里的routes数组就是Routing系统中庞大的路由表,routes数组的元素是Route对象,里面记录了相应的path
pattern对应于哪个Controller的哪个Action方法。这里,通过path,和environment(http
verb)参数,调用每一个Route对象的recognize方法,如果找到相应的Controller,则返回;如果未找到,则进行接下来的错误处理,这里我们可以看到很熟悉的“No
route
matches...”。那我们再来看看Route对象是如何通过Path和environment来识别Controller的,先来看看Route类的recognize方法:
def recognize(path, environment={}) write_recognition recognize path, environment end
在recognize方法中调用recognize方法?传说中的死循环?呵呵。当然不是了,看完write_recognition你就知道是怎么回事了:
def write_recognition # Create an if structure to extract the params from a match if it occurs. body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams" body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend" # Build the method declaration and compile it method_decl = "def recognize(path, env={})\n#{body}\nend" instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" method_decl end
这里,Rails利用instance_eval重写了该对象(此Route对象)的recognize方法(可不是override哦)。完成重写后,将再次调用recognize方法,此时,这个方法已经是动态生成的了。那么现在我们来看看这个动态方法长什么样,这里,我假设客户端访问url为http://localhost:3000/posts
,并且,在routes.rb中,我们已经通过map.resources
:posts建立了一系列针对posts的Route,其中当然包括“Get
/posts/”(对应的Controller是posts,对应的Action是index)。那么在调用这个Route对象的write_recognition方法时,将会动态生成如下代码:
def recognize(path, env={}) if (match = /\A\/posts\/?\Z/.match(path)) && conditions[:method] === env[:method] params = parameter_shell.dup params end end
逻辑很简单,只是通过正则表达式来判断此Route的pattern是否与客户端请求的path一致,并且http
verb也匹配,如果是的话,则将parameter_shell方法的结果dup出来,并且返回。
(注意,这里if子句的条件是通过recognition_conditions方法根据不同Route的不同condition动态生成的。因此针对每个Route,此条件都不同。另外recognition_extraction方法我一直没搞懂他干什么用的-_-!)
这里,我们还是看一看parameter_shell方法:
def parameter_shell @parameter_shell ||= returning({}) do |shell| requirements.each do |key, requirement| shell[key] = requirement unless requirement.is_a? Regexp end end end
无非就是将requirements(包括controller和action)塞到shell数组,然后返回。
好啦,针对路由表中的每一个Route,调用其recognize方法,知道找到匹配的Route,然后将结果(controller和action数组)返回(如未找到匹配的,则进行错误处理),接下来,我们的思路得回到RouteSet对象的recognize方法,最终,使用#{params[:controller].camelize}Controller".constantize,将controller参数转换为首字符大写的形式,并且加上“Controller”字符串,最终将整个字符串(“PostsController”),转换为一个常量(PostsController,表示控制器对象),并且,调用此Controller的process方法(此方法其实是ActionController::Base的类方法),接下来的事,后续文章会继续分析。
(这个过程也揭示了Rails中的“约定甚于配置”的精髓)
(就目前而言,我们都在Rails的主线上游走,我觉得下篇文章,应该暂停一下,来看一些细节的东西,已达到更深入了解Rails的目的)
评论
通过你的文章学习到很多东西!搞明白了RAILS的一个大概流程!谢谢
不知道下面的帖子是否对你有帮助
http://www.iteye.com/post/299671?page=3
关于rails的问题:就是我模型里面创建了一个回调的方法,功能是:比如:我要创建一个部落,用回调方法实现自动增加了创建者为酋长和管理员!但是我用夹具创建了一个部落,这些都没有显示出来!是怎么回事?是夹具在加载数据时饶过回调方法了吗?还是直接饶过模型,直接把数据加载到数据库里面?你有关于夹具的源代码吗?我想知道是怎么回事!
发表评论
-
Rack Middleware Profile
2009-05-26 21:27 1571Rack是一个高效,简洁的框架(Webserver Int ... -
column_timestamp plugin
2008-10-16 00:00 1092有些时候,我们可能需要记录某些列的更新时间,类似于rail ... -
为Rails中的validation error增加error_code
2008-08-05 22:49 1994各位同学对model中一 ... -
Rails中如何更加优雅的处理文件上传
2008-07-19 22:23 2268通常,在rails中处理文件上传,我们会这么做,在view ... -
慎用typo(theme_support)的换肤机制
2008-07-17 23:29 1852前言 本文提到的typo版本是目前最新的5.0.3 ... -
Ruby中&&操作符的妙用(旁门左道)
2008-07-09 22:30 1855几乎所有的现代编程 ... -
Ruby生成斐波拉契数列
2008-07-09 13:52 1838不管你是用c,c++,c#,java。。。不管你是用循环, ... -
Ruby On Rails-2.0.2源代码分析(3)-named route和resource
2008-03-21 00:28 2760前言 在《Routing的载入》中,我大致介绍了一 ... -
netbean调试ActiveSupport::OptionMerger需注意的一个问题
2008-03-18 15:08 1718这两天,在调试Rails ... -
Ruby On Rails-2.0.2源代码分析(2)-Routing的载入
2008-03-16 22:58 3918前言 在前一篇 ... -
Ruby On Rails-2.0.2源代码分析(1)-Rails的启动
2008-03-12 23:32 5576前言 本文主要是 ...
相关推荐
Ruby on Rails 安装指南是指安装 Ruby 1.8.6 和 Rails 2.0.2 的详细步骤。首先,需要下载 Ruby One-Click Installer 版本,并安装 Ruby。然后,下载 Rails 2.0.2 版本,并安装。接下来,需要安装 Mongrel 服务器。...
### Ruby on Rails Cheat Sheet 本篇文章将从给定的文件中提炼出关于Ruby on Rails的重要知识点,主要包括命令、URL映射、命名规范、ERB标签、链接创建、数据库配置及查询、模型之间的关系等方面。 #### Ruby on ...
在本文中,我们将深入分析 Ruby on Rails 2.0.2 的源代码,特别是关注其启动过程。 首先,Rails 的启动始于 `config/boot.rb` 文件。这个文件是 Rails 应用程序的入口点,负责检查 Rails 是否已启动。如果没有,它...
Ruby on Rails,简称Rails,是基于Ruby语言的一个开源Web应用程序框架,它遵循MVC(Model-View-Controller)架构模式,旨在使Web开发过程更加高效、简洁。本压缩包中的"Ruby on Rails入门经典代码"提供了新手学习...
3. **创建步骤定义(Step Definitions)**:在Ruby代码中为Gherkin步骤编写对应的实现,将自然语言转化为可执行的代码。 4. **运行测试**:执行特性文件,验证系统是否按预期工作。BDD工具如Cucumber会自动匹配并...
Ruby on Rails(简称RoR或Rails)是一种基于Ruby语言的开源Web应用框架,它遵循Model-View-Controller(MVC)架构模式,旨在使Web开发更简洁、高效。本实例将帮助你深入理解和实践Rails的开发流程。 首先,让我们从...
### Ruby on Rails Guides v2 - Ruby on Rails 4.2.5 #### 一、重要概念及基础假设 - **重要概念**:本指南旨在帮助读者深入理解Ruby on Rails(以下简称Rails)4.2.5版本的核心功能与最佳实践。 - **基础假设**:...
本教程“Ruby on Rails 教程 - 201406”可能是针对2014年6月时的Rails版本,那时候Rails正处于3.x或4.x系列,虽然现在Rails已经发展到6.x版本,但基础概念和核心原则依然适用。 在Rails中,Model负责处理数据和业务...
rails-dev-box, 面向 Ruby on Rails 核心开发的虚拟机 用于 Ruby on Rails 核心开发的虚拟机简介注意:这个虚拟机不是为 Rails 应用程序开发而设计的,只是为。 这个项目自动设置开发环境,以便在 Ruby on Rails ...
Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zipRuby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zipRuby_on_...
这里的是FastCGI的源代码包,它允许Ruby on Rails应用通过FastCGI协议与Lighttpd交互。 5. **ruby-zlib-0.6.0.tar.gz**: Zlib是用于数据压缩的库,Ruby的内置标准库中包含了对Zlib的支持。此包可能用于Ruby与Gzip等...
接下来,Rails 2.0.2是Ruby on Rails的第二个主要版本,它引入了许多改进和新特性。这个版本的Rails强调了“Convention over Configuration”(约定优于配置)的理念,使得开发过程更加高效。安装Rails需要先确保...
### Ruby on Rails 101:深入理解与实践 #### 引言 《Ruby on Rails 101》是一本介绍Ruby on Rails(简称RoR或ROR)的基础书籍,旨在为初学者提供一个全面而深入的学习框架。本书由Peter Marklund编写,包含了五天...
《Ruby on Rails Tutorial》中文版(原书第2版,涵盖 Rails 4) Ruby 是一门很美的计算机语言,其设计原则就是“让编程人员快乐”。David Heinemeier Hansson 就是看重了这一点,才在开发 Rails 框架时选择了 Ruby...
《初识Ruby on Rails:源代码解析》 Ruby on Rails(简称Rails)是一个基于Ruby语言的开源Web应用程序框架,它遵循MVC(Model-View-Controller)架构模式,旨在简化Web开发过程,提高开发效率。本资料包包含了...
《Ruby-on-Rails-3.rar》是一个在Pudn网站上分享的压缩文件,主要针对的是希望通过Ruby语言进行Web开发的初级程序员。该资源的核心内容是《Web开发敏捷之道 - 应用Rails进行敏捷Web开发 - 第三版》这本书的PDF版本。...
Ruby on Rails,简称Rails,是基于Ruby编程语言的一个开源Web应用程序框架,它遵循MVC(模型-视图-控制器)架构模式,旨在提高开发效率和代码的可读性。Rails以其“约定优于配置”(Convention over Configuration)...
Ruby on Rails,简称Rails,是一种基于Ruby语言的开源Web应用框架,它遵循MVC(Model-View-Controller)架构模式,极大地简化了Web应用程序的开发流程。本资源为"Ruby on Rails Web开发学习实录随书光盘"的源代码,...