Rack中间件是什么?
首先,Rack确切地说有两个方面:
- 一个Web服务器接口规定
- 一个gem
Rack - Web服务器接口
Rack的基本原理是一个简单的规定。每个符合Rack标准的Web服务器总是会在你提供的对象上调用一个call方法,并返回该方法的结果。Rack确切地规定了这个方法的调用方式和返回值。这就是Rack。
我们来试一试。我将使用符合Rack标准的Web服务器WEBrick,但其他服务器也都可以。让我们创建一个简单的Web应用程序,它返回一个JSON字符串。为此,我们将创建一个名为config.ru的文件。配置文件config.ru将自动由Rack gem的命令rackup调用,该命令会在符合Rack标准的Web服务器中运行config.ru的内容。因此,我们来将以下内容添加到config.ru文件:
class JSONServer def call(env) [200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']] end end map '/hello.json' do run JSONServer.new end
根据规定,我们的服务器有一个名为call的方法,它接受一个环境哈希并返回一个形如[status,headers,body]的数组,供Web服务器提供服务。我们来试一试,只需简单地调用rackup即可。一个默认的符合Rack规范的服务器,例如WEBrick或Mongrel,将启动并立即等待请求。
$ rackup [2012-02-19 22:39:26] INFO WEBrick 1.3.1 [2012-02-19 22:39:26] INFO ruby 1.9.3 (2012-01-17) [x86_64-darwin11.2.0] [2012-02-19 22:39:26] INFO WEBrick::HTTPServer#start: pid=16121 port=9292
我们来通过curl或访问url http://localhost:9292/hello.json
来测试我们的新JSON服务器,结果如下:
$ curl http://localhost:9292/hello.json { message: "Hello!" }
它可以工作。太好了!这是每个Web框架的基础,无论是Rails还是Sinatra。它们都会实现一个call方法,在整个框架代码中运行,并最终以典型的[status,headers,body]格式返回响应。
例如,在Ruby on Rails中,Rack请求会打到ActionDispatch::Routing.Mapper
类,代码如下:
module ActionDispatch module Routing class Mapper ... def initialize(app, constraints, request) @app, @constraints, @request = app, constraints, request end def matches?(env) req = @request.new(env) ... return true end def call(env) matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ] end ... end end
因此,基本上Rails根据env哈希的依赖性检查是否有任何匹配的路由。如果匹配,则将env哈希传递给应用程序以计算响应,否则立即使用404响应。因此,符合Rack接口规范的任何Web服务器都能够提供完全成熟的Rails应用程序。
中间件
Rack还支持创建中间件层。它们基本上会拦截请求,对其进行某些操作,然后将其传递。这非常适用于多种任务。
假设我们想在我们的JSON服务器中添加记录,记录还会测量请求需要多长时间。我们可以简单地创建一个这样的中间件记录器:
class RackLogger def initialize(app) @app = app end def call(env) @start = Time.now @status, @headers, @body = @app.call(env) @duration = ((Time.now - @start).to_f * 1000).round(2) puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms" [@status, @headers, @body] end end
当它被创建时,它会保存实际的Rack应用程序副本。在我们的例子中,这是一个JSON服务器实例。Rack自动在中间件上调用call方法,并期望返回一个[status,headers,body]
数组,就像我们的JSON服务器返回的那样。
因此,在这个中间件中,会取出起点,然后使用@app.call(env)
进行实际调用JSON服务器,然后记录器输出记录条目,最后返回响应作为[@status,@headers,@body]
。
为了让我们的小网站rackup.ru使用这个中间件,可以像这样添加一个use RackLogger:
class JSONServer def call(env) [200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']] end end class RackLogger def initialize(app) @app = app end def call(env) @start = Time.now @status, @headers, @body = @app.call(env) @duration = ((Time.now - @start).to_f * 1000).round(2) puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms" [@status, @headers, @body] end end use RackLogger map '/hello.json' do run JSONServer.new end
重启服务器,轻轻松松地输出每个请求的日志。Rack允许你添加多个中间件,按照他们添加的顺序调用。这只是一个在不改变rack应用程序核心的情况下添加功能的绝佳方法。
Rack - The Gem
Rack不仅是一种约定,也是提供了很多功能的gem。其中之一是我们已经为JSON服务器使用过的rackup命令。但还有更多!rack gem为许多用例提供了小型应用程序,例如提供静态文件或整个目录。让我们看看如何提供一个简单的文件,例如位于htmls/index.html的非常基本的HTML文件:
The Index Index Page
我们可能希望从网站根目录提供此文件,因此让我们将以下内容添加到我们的config.ru中:
map '/' do run Rack::File.new "htmls/index.html" end
如果我们访问http://localhost:9292
,我们会看到我们的HTML文件完美地呈现出来。这很容易,是吧?
让我们通过在/javascripts下创建一些JavaScript文件并将以下内容添加到config.ru中来添加整个JavaScript文件目录:
map '/javascripts' do run Rack::Directory.new "javascripts" end
重新启动服务器,访问http://localhost:9292/javascript
,您将看到所有JavaScript文件的列表,现在可以从任何地方直接包含它们。
Rack作为设计
Rack中间件不仅是“过滤请求和响应的方法” - 它是使用Rack针对Web服务器实现管道设计模式的实现。
它非常清晰地分离了处理请求的不同阶段 - 分离关注点是所有设计良好的软件产品的关键目标。
例如,使用Rack,我可以有独立的管道阶段执行以下操作:
-
身份验证:请求到达时,用户的登录详细信息是否正确?如何验证OAuth、HTTP基本身份验证、名称/密码?
-
授权:用户是否被授权执行特定任务?
-
缓存:我是否已经处理过这个请求?可以返回缓存结果吗?
-
装饰:如何增强请求以使下游处理更好?
-
性能和使用情况监控:可以从请求和响应中获取什么统计信息?
-
执行:实际处理请求并提供响应。
能够分离不同的阶段(并可选择包含它们)有助于开发良好结构化的应用程序。
社区
围绕Rack中间件还有一个伟大的生态系统正在发展 - 您应该能够找到预先构建的Rack组件来执行上述所有步骤以及更多操作。请参见Rack GitHub Wiki的中间件列表。
中间件是什么?
中间件是指任何辅助但不直接参与某个任务执行的软件组件/库。非常常见的例子是日志记录、身份验证和其他通用的、水平处理组件。这些通常是每个应用都需要的东西,但不太需要太多人(或不应该)自己构建。
更多信息
-
关于它是一种过滤请求的方法的评论可能来自RailsCast episode 151: Rack Middleware屏幕录影。
-
中间件起源于Rack,介绍了中间件的基础。
-
维基百科有关于中间件的介绍在这里。