Rack
什么是Rack
Rack 最初的灵感来自于Python的wsgi,简单来说,Rack是Ruby应用服务器和Rack应用程序之间的一个接口。
上图给出了一个简单的示意。
用户的请求抵达应用服务器时,应用服务器会调用(call)Rack,Rack对请求进行包装,然后调用你的Rack程序。 Rack程序可以方便地利用Rack所提供的各种API,分析请求,进行处理,并利用Rack提供的响应设施进行输出,Rack会把用户的响应作为输出返回给Ruby应用服务器。
为什么用Rack
标准接口
首先Rack提供了一种标准的接口,便于应用程序和应用服务器之间的交互。一个Rack应用程序可以被任何和Rack兼容的应用服务器调用。 目前几乎所有的主流Ruby应用服务器都支持Rack接口。包括WEBRick、Unicorn、Passenger等。实际上Rack所支持的应用服务器远比这里列出的多得多。
另外一个方面,几乎所有的主流Web框架都支持Rack接口,这意味着,用这些框架编写的应用程序都是标准的Rack应用程序。包括Rails和Sinatra。 这些框架都包含一个Rack适配器(adapter)。 因此,任何用上面列出的框架编写的程序都可以不加修改地被上面列出的所有应用服务器调用。 毫无疑问,未来的Ruby web框架和Ruby Web服务器都会支持Rack接口。
中间件
Rack利用中间件实现了最大程度的模块化。这当然可以提高Web应用程序部件的可 重用性,从而提高开发的效率。
Rack中间件对Ruby Web框架也有着深远的影响,包括:
- 不同的Web框架之间可以重用中间件,这意味这你可以编写的中间件可以在几乎所有的主流框架中使用
- 可以通过不同的中间件组合组装出同一个Web框架的不同变种,以适合不同 的应用场合
- 可以组合多个不同的Web应用框架为同一个更大的系统服务
规格简单
Rack的标准非常简单。整个规格书 http://rack.rubyforge.org/doc/SPEC.html大约只有2页A4纸的内容。
那么一个Rack程序需要符合什么条件呢?Rack规格书中写到:
Rack应用是一个可以响应call的Ruby对象。
它只接受一个环境作为参数
返回包含三个值的数组:status,headers和body
示例
我们学了Ruby的基础,知道在Ruby中能响应call的最简单的Ruby对象就是lambda。
让我们来写个例子:
首先安装rack gem。
gem install rack
打开pry(或irb), 要使用Rack必须先引入rack包。
pry> require 'rack'
pry> rack_app = lambda{|env| [200,{},["hello from lambda"]]}
pry> Rack::Handler::WEBrick.run rack_app ,:Port=>3000
如果此时你再次在浏览器中输入http://localhost:3000,那么将得到 hello from lambda 成功了!我们写出了第一个符合规格的Rack程序。
如果你安装了in服务器,那么你可以:
pry> Rack::Handler::Thin.run rack_app ,:Port=>3000
你照样可以在浏览器上得到相同的结果。
除了lambda之外,其他任意可以被call的对象,都可以。
比如:
AnyClass
def call(env)
[200, {}, ["hello from AnyClass instance with call defined"]]
end
end
rack_app = AnyClass.new
Rack::Handler::Thin.run rack_app ,:Port=>3000
在浏览器输入http://localhost:3000,你可以得到: hello from AnyClass instance with call defined。
Rails on Rack
至此,我们知道了,Rails也是基于Rack的一个Web框架。 那么知道这个事实有什么用呢?
我们之前说了:
用户的请求抵达应用服务器时,应用服务器会调用(call)Rack,Rack对请求进行包装,然后调用你的Rack程序。
那么Rails处理一个请求也是一样的过程:
http request
当请求到达 web server后会即被rack封装, 而后你得到一个env对象. 它包含了客户的请求类型(get/post/put/delete/..), 请求的地址(env[‘PATH_INFO’], QUERY_STRING)等等.
- 通过分析env, 我们知道客户的请求是指向哪个controller#action. 而后查看路由表我们的app能否响应此请求.
- 路由表在新建app对象时通过routes方法来定义, 具体的做法是接受一个block, block内调用match, get, post等方法时, 生成路由规则加入路由表. 路由表里包含路径字符串的匹配正则, controller, action, params等等.
- 参照上一条, 在路由规则中检查 env[‘PATH_INFO’], 若匹配, 就知道了指向哪个controller的哪个action, 以及其params. 通过 ctrl_const = Object.const_get(params[:controller].capitalize)来得到相应的controller.
- 通过 ctrl_const.new(env).call(params[:action]) 可以调用到相应的方法.
- 到这一步, 已经初步描述了一个请求从客户端到服务器端并指向需要的controller#action的基本过程. 也即处理request的过程完成.
Response的过程:
- 在ctrl_const.new上调用action后新的对象内就会拥有相应的实例变量. 这时通过Tiltgem, 按规则生成目标view的名字, 找到它, 而后render, render时将self作为scope传入. 至此view里可以调用action里所有的实例变量.Tilt.new(view).render self 得到了应该返回的html的内容.
- 上一步render得到的只是Controller#action对应的view, 需要将它交给layout处理.同样的使用Tilt, 将上一步得到的partial view 放到block中提交过去. 这样layout中的<%= yield %>关键字生效. 至此, 得到完整的html内容.
- Rack要求调用call后的返回的值是一个三值的array, 分别是[status code, head content, body].上一步得到的是html内容就是body部分.
Rails中使用的中间件
你可以在项目根目录下执行下面命令来查看Rails使用的中间件
rake middleware
输出:
#=>
use Rack::Sendfile
use ActionDispatch::Static
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fd93e3aee58>
use Rack::Runtime
use Rack::MethodOverride
## 等等