什么是Rack中间件?
Ruby中的Rack中间件是什么? 对于“中间件”的含义,我找不到任何好的解释。
机架devise
Rack中间件不仅仅是“过滤请求和响应的一种方式” – 它是使用Rack的 web服务器的pipe道devise模式的实现。
它很清楚地区分处理请求的不同阶段 – 将关注点分离成所有devise良好的软件产品的关键目标。
例如与机架我可以有不同的pipe道阶段:
-
身份validation :请求到达时,用户login详细信息是否正确? 如何validation此OAuth,HTTP基本validation,名称/密码?
-
授权 :“被授权执行这个特定任务的用户?”,即基于angular色的安全性。
-
caching :我已经处理了这个请求,我可以返回一个caching的结果吗?
-
装修 :如何提高下游加工的要求?
-
性能和使用情况监控 :我可以从请求和响应中获得什么状态?
-
执行 :实际处理请求并提供响应。
能够分离不同的阶段(可选地包括它们)对于开发结构良好的应用程序非常有帮助。
社区
围绕机架中间件还有一个很好的生态系统 – 您应该能够find预制的机架组件来完成上述所有步骤。 请参阅Rack GitHub wiki获取中间件列表 。
什么是中间件?
中间件是一个可怕的术语,指的是任何协助但不直接参与某个任务执行的软件组件/库。 很常见的例子是日志logging,authentication和其他常见的水平处理组件 。 这些往往是每个人都需要跨越多个应用程序的事情,但是没有太多人对自己构build自己感兴趣(或应该)。
更多信息
-
关于它是一种过滤请求的方式可能来自RailsCast第151集:机架中间件屏幕轮播。
-
Rack中间件是从Rack进化而来的, 介绍Rack中间件有一个很好的介绍。
-
这里有一个关于维基百科中间件的介绍。
首先,Rack正好是两件事情:
- Web服务器接口约定
- gem
机架 – Web服务器接口
机架的基础是一个简单的约定。 每个机架兼容的networking服务器将始终调用您给他的对象的调用方法,并提供该方法的结果。 Rack精确地指定了这个调用方法的样子,以及返回的内容。 这是机架。
我们来简单的尝试一下。 我将使用WEBrick作为符合机架的networking服务器,但其中任何一个都可以。 我们来创build一个返回JSONstring的简单Web应用程序。 为此,我们将创build一个名为config.ru的文件。 config.ru会自动被机架gem的命令rackup调用,它将简单地运行符合机架的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服务器使用。 让我们试着简单地调用机架。 一个默认的机架兼容的服务器,也许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
让我们testing我们新的JSON服务器,通过curling或访问url http://localhost:9292/hello.json
和http://localhost:9292/hello.json
:
$ curl http://localhost:9292/hello.json { message: "Hello!" }
有用。 大! 这是每个Web框架的基础,无论是Rails还是Sinatra。 在某些时候,他们实现一个调用方法,遍历所有的框架代码,最后以典型的[status,headers,body]forms返回一个响应。
例如,在Ruby on Rails中,机架请求会触发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。因此,任何符合机架接口约定的web服务器都能够提供完全成熟的Rails应用程序。
中间件
Rack还支持创build中间件层。 他们基本上拦截了一个请求,用它做了一些事情并传递给它。 这对于多function任务非常有用。
比方说,我们希望将日志logging添加到我们的JSON服务器,该服务器还会测量请求所花费的时间。 我们可以简单地创build一个中间件logging器来完成这个工作:
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
创build后,它会自行保存实际机架应用程序的副本。 在我们的例子中,这是我们的JSONServer的一个实例。 Rack自动调用中间件的调用方法,期望返回一个[status, headers, body]
数组,就像我们的JSONServer返回一样。
因此,在这个中间件中,我们使用起始点,然后使用@app.call(env)
对JSONServer进行实际的调用,然后logging器输出日志logging,最后以[@status, @headers, @body]
。
为了使我们的小rackup.ru使用这个中间件,添加一个使用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允许你添加多个被添加顺序调用的中间件。 这是增加function而不改变机架应用核心的好方法。
机架 – gem
虽然机架 – 首先 – 是一个惯例,它也是一个gem,提供了很好的function。 其中一个我们已经用于我们的JSON服务器,rackup命令。 但还有更多! 机架gem提供了很多用例的应用程序,例如提供静态文件甚至整个目录。 让我们看看我们如何提供一个简单的文件,例如位于htmls / index.html的非常基本的HTML文件:
<!DOCTYPE HTML> <html> <head> <title>The Index</title> </head> <body> <p>Index Page</p> </body> </html>
我们可能想从网站的根目录来提供这个文件,所以我们把下面的代码添加到我们的config.ru中:
map '/' do run Rack::File.new "htmls/index.html" end
如果我们访问http://localhost:9292
我们会看到我们的html文件完美呈现。 这很简单,对吧?
让我们通过在/ javascripts下创build一些javascript文件并添加以下内容到config.ru来添加一个完整的javascript文件目录:
map '/javascripts' do run Rack::Directory.new "javascripts" end
重新启动服务器并访问http://localhost:9292/javascript
,您将看到所有可从任何地方直接进入的JavaScript文件的列表。
我有一个问题的理解架上自己一个很长的时间。 在完成自己制作这个小型的Ruby Web服务器之后,我才完全理解它。 我在我的博客上分享了我关于Rack的知识(以故事的forms): http : //gauravchande.com/what-is-rack-in-ruby-rails
反馈是比欢迎。
机架中间件是过滤进入您的应用程序的请求和响应的一种方式。 中间件组件位于客户端和服务器之间,处理入站请求和出站响应,但不仅仅是可用于与Web服务器交互的接口。 它用于对模块(通常是Ruby类)进行分组和sorting,并指定它们之间的依赖关系。 Rack中间件模块只能: – 具有将堆栈中的下一个应用程序作为参数的构造函数 – 响应以“环境散列”作为参数的“call”方法。 从这个调用返回值是一个数组:状态码,环境哈希和响应体。
config.ru
最小的可运行示例
app = Proc.new do |env| [ 200, { 'Content-Type' => 'text/plain' }, ["main\n"] ] end class Middleware def initialize(app) @app = app end def call(env) @status, @headers, @body = @app.call(env) [@status, @headers, @body << "Middleware\n"] end end use(Middleware) run(app)
运行rackup
并访问localhost:9292
。 输出是:
main Middleware
所以显然Middleware
包装和调用主应用程序。 因此,它能够预处理请求,并以任何方式后处理响应。
正如http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack所解释的,Rails使用Rack中间件提供了很多function,你也可以使用;config.middleware.use
家庭方法。
在中间件中实现function的好处是你可以在任何Rack框架上重用它,因此所有主要的Ruby框架,而不仅仅是Rails。
我使用Rack中间件来解决几个问题:
- 使用自定义Rack中间件捕获JSONparsing错误,并在客户端提交已破坏的JSON时返回格式良好的错误消息
- 内容压缩通过Rack :: Deflater
它在两种情况下都提供了相当优雅的修复。
什么是机架?
Rack提供支持Ruby和Ruby框架的Web服务器之间的最小接口。
使用Rack可以编写一个机架应用程序。
Rack将环境哈希(Hash,包含在来自客户端的HTTP请求中,包含类似CGI的头文件)传递给您的机架应用程序,它可以使用这个哈希中包含的内容来做任何事情。
什么是机架应用程序?
要使用Rack,你必须提供一个'app' – 一个响应#call
方法和Environment Hash作为参数(通常定义为env
)的对象。 #call
必须返回一个正好三个值的数组:
- 状态代码 (例如'200'),
- 一串头 ,
- 响应主体 (
each
必须响应Ruby方法)。
您可以编写一个Rack应用程序返回这样一个数组 – 这将在一个响应中 (这实际上是Class Rack::Response
一个实例 ,点击进入文档)通过Rack发送给您的客户端。
非常简单的机架应用:
-
gem install rack
- 创build一个
config.ru
文件 – 机架知道寻找这个。
我们将创build一个小型的机架应用程序,返回一个响应(Response Rack::Response
) 。
我们将使用命令rackup
来启动本地服务器。
在浏览器中访问相关端口时,我们会看到“Hello,World!” 在视口中呈现。
#./message_app.rb class MessageApp def call(env) [200, {}, ['Hello, World!']] end end #./config.ru require_relative './message_app' run MessageApp.new
用rackup
启动本地服务器并访问localhost:9292 ,你应该看到'Hello,World!' 渲染。
这并不是一个全面的解释,但实际上这里发生的事情是客户端(浏览器)通过本地服务器向机架发送HTTP请求,Rack实例化MessageApp
并运行call
,将Environment Hash作为parameter passing给方法( env
参数)。
Rack获取返回值(数组),并使用它创buildRack::Response
一个实例,并将其发送回客户端。 浏览器使用魔法打印“Hello,World!” 到屏幕上。
顺便说一句,如果你想看看环境哈希的样子,只需把puts env
放在def call(env)
。
最小的,你在这里写的是一个Rack应用程序!
使机架应用程序与传入环境散列进行交互
在我们的小机架应用程序中,我们可以与env
散列进行交互(请参阅这里了解关于Environment散列的更多信息)。
我们将实现用户将自己的查询stringinput到URL中的function,因此,该string将出现在HTTP请求中,并作为值封装在Environment哈希的一个键/值对中。
我们的Rack应用程序将从Environment哈希中访问该查询string,并通过响应中的主体将其发送回客户端(在本例中为我们的浏览器)。
从Environment Hash上的Rack文档: “QUERY_STRING:请求URL的部分,如果有的话,可能是空的,但总是必需的!
#./message_app.rb class MessageApp def call(env) message = env['QUERY_STRING'] [200, {}, [message]] end end
现在, rackup
并访问localhost:9292?hello
( ?hello
是查询string),你应该在视口中看到'hello'。
机架中间件
我们会:
- 插入一个机架中间件到我们的代码库 – 一个类:
MessageSetter
, - 环境哈希将首先击中这个类,并将作为参数传入:
env
, -
MessageSetter
会在env hash里插入一个'MESSAGE'
键,它的值是'Hello, World!'
如果env['QUERY_STRING']
为空;env['QUERY_STRING']
如果不是, - 最后,它将返回
@app.call(env)
–@app
作为“堆栈”中的下一个应用程序:MessageApp
。
首先,“长手”版本:
#./middleware/message_setter.rb class MessageSetter def initialize(app) @app = app end def call(env) if env['QUERY_STRING'].empty? env['MESSAGE'] = 'Hello, World!' else env['MESSAGE'] = env['QUERY_STRING'] end @app.call(env) end end #./message_app.rb (same as before) class MessageApp def call(env) message = env['QUERY_STRING'] [200, {}, [message]] end end #config.ru require_relative './message_app' require_relative './middleware/message_setter' app = Rack::Builder.new do use MessageSetter run MessageApp.new end run app
从Rack :: Builder文档中,我们看到Rack::Builder
实现了一个小的DSL来迭代地构buildRack应用程序。 这基本上意味着你可以build立一个由一个或多个中间件和一个“底层”应用程序组成的“堆栈”。 所有通往底层应用程序的请求将首先由您的中间件处理。
#use
指定中间件在堆栈中使用。 它以中间件作为参数。
机架中间件必须:
- 有一个构造函数将堆栈中的下一个应用程序作为参数。
- 响应将Environment哈希值作为参数的
call
方法。
在我们的例子中,“中间件”是MessageSetter
,“构造器”是MessageSetter的initialize
方法,堆栈中的“下一个应用程序”是MessageApp
。
因此,在这里,由于Rack::Builder
在Rack::Builder
做了什么, MessageSetter
的initialize
方法的app
参数是MessageApp
。
(在继续之前,先把你的头转过来)
因此,每一个中间件都将现有的环境散列“传递”给链中的下一个应用程序,所以你有机会在中间件中改变这个环境散列,然后把它传递给栈中的下一个应用程序。
#run
采用的参数是响应#call
并返回Rack Response( Rack::Response
一个实例)的对象。
结论
使用Rack::Builder
您可以构build中间件链,任何对应用程序的请求都将由每个中间件依次处理,最后由堆栈中的最后一块(在本例中为MessageApp
)进行处理。 这是非常有用的,因为它分离出处理请求的不同阶段。 就“关注点分离”而言,它不可能更清洁!
您可以构build一个由多个中间件组成的“请求pipe道”,这些中间件处理诸如:
- authentication
- 授权
- 高速caching
- 装饰
- 性能和使用情况监测
- 执行(实际处理请求并提供响应)
(从这个线程的另一个答案上面的项目符号点)
您将经常在专业的Sinatra应用程序中看到这一点。 Sinatra使用机架! 在这里看到Sinatra 是什么的定义!
作为最后一个注意事项,我们的config.ru
可以写成一个简短的风格,产生完全相同的function(这是你通常会看到的):
require_relative './message_app' require_relative './middleware/message_setter' use MessageSetter run MessageApp.new
为了更清楚地显示MessageApp
正在做什么,下面是它的“长手”版本,它清楚地显示#call
正在创buildRack::Response
的新实例,并带有所需的三个参数。
class MessageApp def call(env) Rack::Response.new([env['MESSAGE']], 200, {}) end end
有用的链接
- 完整的代码为这个职位(Github回购承诺)
- 好博客post,“机架中间件入门”
- 一些好的Rack文档