什么是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.jsonhttp://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中间件来解决几个问题:

  1. 使用自定义Rack中间件捕获JSONparsing错误,并在客户端提交已破坏的JSON时返回格式良好的错误消息
  2. 内容压缩通过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::BuilderRack::Builder做了什么, MessageSetterinitialize方法的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文档