什么是机架中间件?


265

Ruby中的Rack中间件是什么?对于“中间件”的含义,我找不到很好的解释。


4
现在,RailsGuide上还有一个指南全面介绍了Rack,包括中间件:guides.rubyonrails.org/rails_on_rack.html
xji 2015年

非常感谢PhusionPassenger团队,他们的博客上有一篇很好解释的文章。rubyraptor.org/...
拉面

机架和机柜中间件在解释条。还介绍了有关创建基于机架的应用程序的信息。
shashwat srivastava

Answers:


352

机架设计

Rack中间件不仅仅是“过滤请求和响应的一种方式”-它是使用Rack的 Web服务器管道设计模式的实现。

它非常清晰地分离出处理请求的不同阶段-分离关注点是所有精心设计的软件产品的关键目标。

例如,使用Rack,我可以在管道中进行单独的操作:

  • 身份验证:当请求到达时,用户登录详细信息是否正确?如何验证此OAuth,HTTP基本身份验证,名称/密码?

  • 授权:“是否授权用户执行此特定任务?”,即基于角色的安全性。

  • 缓存:我已经处理了此请求,可以返回缓存结果吗?

  • 装饰:如何提高要求以使下游加工更好?

  • 性能和使用情况监控:我可以从请求和响应中获得哪些统计信息?

  • 执行:实际处理请求并提供响应。

能够分离不同的阶段(并可以选择包括它们)对于开发结构良好的应用程序有很大的帮助。

社区

机架中间件周围还开发了一个很棒的生态系统-您应该能够找到预建的机架组件,以完成上述所有步骤以及更多操作。有关中间件列表,请参见Rack GitHub Wiki

什么是中间件?

中间件是一个可怕的术语,指的是有助于但不直接参与某些任务执行的任何软件组件/库。非常常见的示例是日志记录,身份验证和其他常见的水平处理组件。这些往往是每个人都需要在多个应用程序中使用的东西,但是并不是有太多人对(或应该)对自己进行构建感兴趣。

更多信息


我不清楚的一件事:所有中间件都共享相同的数据吗?为了安全起见,是否可以将它们分开(即沙箱之一)?
Brian Armstrong 2010年

2
机架是应用程序的一部分,因此所有中间件都将请求的同一副本组成组件,并且每个组件都可以按照他们想要的任何方式对其进行修改。AFAIK,没有办法以相同的方式对它们进行沙箱处理,也没有办法在相同的流程中将一个对象与另一个对象进行沙箱处理(尽管尝试使用Ruby沙箱处理)。
克里斯·麦考利

1
并且一定要了解Rack与Rake不同。
Manish Shrivastava 2014年

1
我喜欢将中间件视为位于我的应用程序中间的任何东西,这些东西介于我编写的代码和往返于服务器的信息之间……托管在机架空间上。众所周知,“机架中间件”一词之所以令人困惑,是因为Confucius编写了2000年前的所有原始机架中间件。在法国。
LpLrich

74

首先,Rack正好有两件事:

  • Web服务器界面约定
  • 一颗宝石

机架-Web服务器界面

机架的基本知识很简单。每个机架兼容的Web服务器将始终在您提供给他的对象上调用call方法,并提供该方法的结果。Rack确切指定此调用方法的外观以及返回的内容。那是架子。

让我们尝试一下。我将使用WEBrick作为兼容机架的Web服务器,但是它们中的任何一个都可以。让我们创建一个返回JSON字符串的简单Web应用程序。为此,我们将创建一个名为config.ru的文件。机架gem的命令rackup将自动调用config.ru,该命令将在兼容机架的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来尝试一下。默认的机架兼容服务器(可能是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

让我们通过卷曲或访问url http://localhost:9292/hello.json和voila 来测试我们的新JSON服务器:

$ curl http://localhost:9292/hello.json
{ message: "Hello!" }

有用。大!这是每个Web框架的基础,无论是Rails还是Sinatra。在某些时候,他们实现了一个调用方法,遍历了所有框架代码,最后以典型的[状态,标头,正文]形式返回响应。

例如,在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哈希传递到应用程序以计算响应,否则它将立即以404响应。因此,任何符合机架接口约定的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

创建后,它会为自己保存实际机架应用程序的副本。在我们的例子中,这是JSONServer的一个实例。Rack自动调用中间件上的call方法,并期望返回一个[status, headers, body]数组,就像JSONServer返回一样。

因此,在此中间件中,采用起点,然后使用进行对JSONServer的实际调用@app.call(env),然后记录器输出记录项,最后将响应返回为[@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   

重新启动服务器,瞧,它会在每个请求上输出日志。机架允许您添加按添加顺序调用的多个中间件。这是在不更改机架应用程序核心的情况下添加功能的好方法。

机架-宝石

尽管机架-首先-是一种惯例,但它也是提供强大功能的瑰宝。我们已经在JSON服务器中使用过其中之一,即rackup命令。但是还有更多!rack 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下创建一些javascript文件并将以下内容添加到config.ru中来添加整个javascript文件目录:

map '/javascripts' do
  run Rack::Directory.new "javascripts"
end

重新启动服务器并访问http://localhost:9292/javascript,您将看到可立即从任何地方包含的所有javascript文件的列表。


3
但不是Rack中间件?
2012年

1
如果您不知道机架是什么,那么在阅读此博客文章后,您将确切知道机架是什么以及如何使用。非常好。具有讽刺意味的是,帖子末尾的官方机架文档链接不再可用!
科林

您的权利,谢谢。我将内容包含在帖子中,并删除了无效链接。
Thomas Fankhauser

我会说这不是惯例。它是一个接口,一个为请求-响应模型定义的合同
Ron Klein

20

我在长时间了解Rack时遇到了问题。我自己亲自制作了微型Ruby Web服务器后才完全理解它。我在博客上以故事的形式分享了我对Rack的了解:http : //gauravchande.com/what-is-rack-in-ruby-rails

反馈非常受欢迎。


13
在Stack Overflow上不鼓励仅链接的答案,因为如果将来链接所用的资源变得不可用,答案将变得无用。请至少总结您博客文章的相关内容,并将其添加到此答案中。

谢谢你的帖子。我是一个非常初级的Rails程序员,并且在您的明确文章中了解了机架的概念。
Eduardo Ramos'8

很棒的博客文章。IMO的其他答案似乎有些复杂。

真棒的解释。谢谢,高拉夫。
rovitulli16年

7

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中间件,您也可以使用config.middleware.use家族方法添加自己的中间件。

在中间件中实现功能的优势在于,您可以在任何Rack框架上重用它,从而可以在所有主要的Ruby框架上重用,而不仅仅是Rails。


6

机架中间件是一种过滤进入您的应用程序的请求和响应的方法。中间件组件位于客户端和服务器之间,用于处理入站请求和出站响应,但它不仅是可用于与Web服务器通信的接口。它用于对模块(通常是Ruby类)进行分组和排序,并指定它们之间的依赖关系。机架中间件模块仅必须:–具有以堆栈中的下一个应用程序为参数的构造函数–响应以环境哈希为参数的“调用”方法。此调用返回的值是一个数组:状态码,环境哈希和响应主体。



4

什么是机架?

Rack在支持Ruby和Ruby框架的Web服务器之间提供了最小的接口。

使用机架,您可以编写机架应用程序。

Rack会将环境哈希值(哈希值,包含在来自客户端的HTTP请求中,由类似CGI的标头组成)传递给您的Rack Application,该应用程序可以使用此哈希值中包含的内容执行所需的任何操作。

什么是机架应用程序?

要使用Rack,您必须提供一个“ app”-一个对象,该对象#call使用环境哈希作为参数来响应该方法(通常定义为env)。#call必须返回正好三个值的数组:

  • 状态码(例如“200”),
  • 一个头的哈希
  • 响应体(必须向红宝石方法反应,each)。

您可以编写一个Rack Application来返回这样的数组-Rack将在响应中将它发送回客户端(这实际上是Class的一个实例Rack::Response [单击以转到docs])。

一个非常简单的机架应用程序:

  • gem install rack
  • 创建config.ru文件-Rack知道要查找该文件。

我们将创建一个小型的Rack应用程序,该应用程序返回一个Response(作为的实例Rack::Response),该应用的Response Body是包含String:的数组"Hello, World!"

我们将使用命令启动本地服务器rackup

在浏览器中访问相关端口时,我们会看到“世界你好!” 在视口中渲染。

#./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!”。呈现。

这不是一个全面的解释,但实际上发生的是客户端(浏览器)通过您的本地服务器向Rack发送一个HTTP请求,Rack实例化MessageApp并运行call,将环境哈希作为参数传递给方法(该env参数)。

Rack获取返回值(数组),并使用它创建的实例,Rack::Response并将其发送回客户端。浏览器使用魔术来打印“ Hello,World!”。到屏幕。

顺便说一句,如果您想查看环境哈希的样子,只需将puts env其放在下面def call(env)

简而言之,您在这里编写的是一个Rack应用程序!

使机架应用程序与传入环境哈希交互

在我们的小型Rack应用中,我们可以与env哈希进行交互(有关环境哈希的更多信息,请参见此处)。

我们将实现用户将自己的查询字符串输入URL的功能,因此,该字符串将出现在HTTP请求中,并封装为Environment哈希的键/值对之一中的值。

我们的Rack应用程序将从“环境”哈希中访问该查询字符串,并将其通过响应中的“正文”发送回客户端(在这种情况下,是我们的浏览器)。

从“环境哈希的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是查询字符串),您应该会在视口中看到“ hello”呈现。

机架中间件

我们会:

  • 插入了一段Rack中间件到我们的代码库-一类:MessageSetter
  • 环境散列将首先命中此类,并将作为参数传递: env
  • MessageSetter将一个'MESSAGE'密钥插入到env哈希中,其值为'Hello, World!'if 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来迭代构造Rack应用程序。基本上,这意味着您可以构建一个由一个或多个中间件和一个“底层”应用程序组成的“堆栈”,以将其分发。到达底层应用程序的所有请求将首先由中间件处理。

#use指定要在堆栈中使用的中间件。它以中间件作为参数。

机架中间件必须:

  • 具有一个构造函数,该构造函数将堆栈中的下一个应用程序作为参数。
  • 响应以call环境哈希为参数的方法。

在我们的例子中,“中间件”是MessageSetter,“构造函数”是MessageSetter的initialize方法,堆栈中的“下一个应用程序”是MessageApp

因此,在这里,由于实际Rack::Builder情况,的方法的app论据为。MessageSetterinitializeMessageApp

(继续前进之前,请先绕过上面)

因此,每个中间件基本上都将现有的环境哈希“传递”到链中的下一个应用程序-因此,您有机会在中间件内将该环境哈希进行变异,然后再传递给堆栈中的下一个应用程序。

#run接受一个参数,该参数是一个响应#call并返回Rack Response(的实例Rack::Response)的对象。

结论

使用,Rack::Builder您可以构造中间件链,每个对中间件的请求都将依次由每个中间件处理,然后再由堆栈中的最后一块(在我们的情况下MessageApp)进行处理。这非常有用,因为它可以分离出处理请求的不同阶段。就“关注点分离”而言,这再干净不过了!

您可以构建一个由多个中间件组成的“请求管道”,这些中间件处理诸如以下内容:

  • 认证方式
  • 授权书
  • 快取
  • 装饰
  • 性能和使用情况监控
  • 执行(实际上处理请求并提供响应)

(在该主题的另一个答案之上的要点)

您经常会在专业Sinatra应用程序中看到这一点。Sinatra使用机架!看到这里是什么Sinatra IS的定义!

最后一点,我们config.ru可以用简写形式编写,产生完全相同的功能(这是您通常会看到的):

require_relative './message_app'
require_relative './middleware/message_setter'

use MessageSetter
run MessageApp.new

为了更明确地显示MessageApp正在执行的操作,以下是其“长期”版本,该版本明确显示了使用所需的三个参数#call创建的新实例Rack::Response

class MessageApp
  def call(env)
    Rack::Response.new([env['MESSAGE']], 200, {})
  end
end

有用的链接


1

机架-Web和App Server的界面

Rack是一个Ruby软件包,它为Web服务器提供了与应用程序进行通信的接口。在Web服务器和应用程序之间添加中间件组件很容易,以修改您的请求/响应的行为方式。中间件组件位于客户端和服务器之间,处理入站请求和出站响应。

用通俗易懂的话来说,它基本上只是服务器和Rails应用程序(或任何其他Ruby Web应用程序)如何相互通信的一组准则

要使用Rack,请提供一个“ app”:一个响应call方法的对象,将环境哈希作为参数,并返回一个包含三个元素的Array:

  • HTTP响应代码
  • 标头哈希
  • 所述反应体,它必须对每个响应请求

有关更多说明,您可以点击以下链接。

1. https://rack.github.io/
2. https://redpanthers.co/rack-middleware/
3. https://blog.engineyard.com/2015/understanding-rack-apps-and-middleware
4. https://guides.rubyonrails.org/rails_on_rack.html#resources

在rails中,我们将config.ru作为机架文件,您可以使用rackup命令运行任何机架文件。且默认端口为9292。要对此进行测试,您只需rackup在rails目录中运行即可查看结果。您还可以分配要在其上运行它的端口。在任何特定端口上运行机架文件的命令是

rackup -p PORT_NUMBER

1

该图显示了独角兽和铁轨之间的机架

机架是一个宝石,它提供了一个用于抽象HTTP请求/响应的简单接口。机架位于Web框架(Rails,Sinatra等)和Web服务器(独角兽,puma)之间,作为适配器。从上面的图像可以使独角兽服务器完全独立于对Rails的了解,而Rails对独角兽一无所知。这是松散耦合关注点分离的一个很好的例子。

上图是来自在机架https://youtu.be/3PnUV9QzB0g上的这次Rails会议演讲,我建议您观看一下以进一步了解。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.