Ruby中的机架中间件是什么?对于他们所说的“中间件”,我找不到任何好的解释。


机架作为设计

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

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

例如,使用Rack,我可以有管道的单独阶段:

Authentication: when the request arrives, are the users logon details correct? How do I validate this OAuth, HTTP Basic Authentication, name/password? Authorization: "is the user authorised to perform this particular task?", i.e. role-based security. Caching: have I processed this request already, can I return a cached result? Decoration: how can I enhance the request to make downstream processing better? Performance & Usage Monitoring: what stats can I get from the request and response? Execution: actually handle the request and provide a response.

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

社区

还有一个围绕机架中间件开发的很好的生态系统——你应该能够找到预先构建的机架组件来完成上面所有的步骤。请参阅机架GitHub维基获取中间件列表。

中间件是什么?

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

更多的信息

关于它是过滤请求的一种方式的评论可能来自RailsCast第151集:机架中间件的屏幕放映。 机架中间件从机架发展而来,在机架中间件介绍中有一个很好的介绍。 维基百科上有关于中间件的介绍。


首先,Rack就是两件事:

web服务器接口约定 一颗宝石

机架- web服务器接口

机架的基本原理是一个简单的约定。每一个机架兼容的web服务器将总是调用一个调用方法在你给他的对象上,并提供该方法的结果。Rack精确地指定了这个调用方法必须是什么样子,以及它必须返回什么。这是架。

让我们简单地试一试。我将使用WEBrick作为机架兼容的web服务器,但它们中的任何一个都可以。让我们创建一个返回JSON字符串的简单web应用程序。为此,我们将创建一个名为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服务器服务。让我们通过简单地调用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来测试我们的新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哈希值检查是否有匹配的路由。如果是,它将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自动调用中间件上的调用方法,并期望返回一个[status, headers, body]数组,就像JSONServer返回的那样。

因此,在这个中间件中,取起始点,然后使用@app.call(env)对JSONServer进行实际调用,然后记录器输出日志记录条目,最后以[@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允许您添加多个按照添加顺序调用的中间件。这是一种在不改变机架应用程序核心的情况下添加功能的好方法。

机架-宝石

虽然机架-首先-是一个惯例,它也是一个宝石,提供了伟大的功能。其中一个我们已经在JSON服务器上使用过,即rackup命令。但还有更多!rack gem为很多用例提供了小的应用程序,比如服务静态文件甚至整个目录。让我们看看我们是如何提供一个简单的文件的,例如一个非常基本的HTML文件,位于htmlls /index.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文件来添加一个完整的javascript文件目录,并将以下内容添加到config.ru:

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

重新启动服务器并访问http://localhost:9292/javascript,你会看到一个列表,上面列出了你可以直接从任何地方包含的所有javascript文件。


在很长一段时间里,我自己都无法理解Rack。我是在自己制作了这个小型Ruby web服务器之后才完全理解它的。我在我的博客http://blog.gauravchande.com/what-is-rack-in-ruby-rails上分享了我对Rack的了解(以故事的形式)

非常欢迎反馈。


我使用Rack中间件解决了几个问题:

使用自定义机架中间件捕获JSON解析错误,并在客户端提交错误JSON时返回格式化良好的错误消息 内容压缩通过机架::Deflater

在这两种情况下,它都提供了相当优雅的修复。


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

因此很明显,中间件包装并调用主应用程序。因此,它能够以任何方式预处理请求和后处理响应。

正如在http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack中解释的那样,Rails使用Rack中间件来实现它的许多功能,你也可以使用config.middleware.use家族方法添加你自己的中间件。

在中间件中实现功能的好处是,您可以在任何机架框架上重用它,因此可以重用所有主要的Ruby框架,而不仅仅是Rails。


Rack middleware is a way to filter a request and response coming into your application. A middleware component sits between the client and the server, processing inbound requests and outbound responses, but it's more than interface that can be used to talk to web server. It’s used to group and order modules, which are usually Ruby classes, and specify dependency between them. Rack middleware module must only: – have constructor that takes next application in stack as parameter – respond to “call” method, that takes environment hash as a parameter. Returning value from this call is an array of: status code, environment hash and response body.


机架是什么?

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

使用Rack可以编写一个Rack应用程序。

机架将传递环境哈希(一个哈希,包含在来自客户端的HTTP请求中,由类似cgi的头组成)给您的机架应用程序,该应用程序可以使用这个哈希中包含的内容来做任何它想做的事情。

什么是机架应用程序?

要使用Rack,你必须提供一个“app”——一个响应#call方法的对象,并将环境哈希作为参数(通常定义为env)。#call必须返回一个包含三个值的数组:

状态码(例如“200”), 一个头的哈希, 响应体(必须响应Ruby方法)。

你可以写一个Rack应用程序,返回这样一个数组-这将被发送回你的客户端,由机架,在一个响应(这实际上是类Rack::Response的一个实例[点击进入文档])。

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

Gem安装架 创建一个config.ru文件- Rack知道要寻找这个文件。

我们将创建一个小型的Rack应用程序,它返回一个响应(Rack::Response的一个实例),其响应体是一个包含String: "Hello, World!"的数组。

我们将使用命令机架启动本地服务器。

当在浏览器中访问相关端口时,我们将看到“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请求到机架,通过您的本地服务器,机架实例化MessageApp并运行call,将环境哈希作为参数传递到方法(env参数)。

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

顺便说一句,如果你想看看环境哈希是什么样的,只要把puts env放在def call(env)下面。

尽管非常简单,但您在这里编写的是一个机架应用程序!

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

在我们的小Rack应用程序中,我们可以与env哈希进行交互(有关Environment哈希的更多信息,请参阅这里)。

我们将实现用户向URL中输入自己的查询字符串的能力,因此,该字符串将出现在HTTP请求中,封装为Environment散列的一个键/值对中的值。

我们的Rack应用程序将从Environment哈希中访问该查询字符串,并通过响应中的Body将其发送回客户端(在本例中是我们的浏览器)。

从机架文档的环境哈希: QUERY_STRING:请求URL中跟在?后面的部分(如果有的话)。可能是空的,但总是需要的!”

#./message_app.rb
class MessageApp
  def call(env)
    message = env['QUERY_STRING']
    [200, {}, [message]]
  end
end

现在,安装并访问localhost:9292?你好(?Hello是查询字符串),你应该看到' Hello '呈现在视口中。

架的中间件

我们将:

插入一个机架中间件到我们的代码库-一个类:MessageSetter, Environment哈希将首先碰到这个类,并将作为参数传入: MessageSetter将在env散列中插入一个'MESSAGE'键,其值为'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指定在堆栈中使用的中间件。它以中间件作为参数。

机架中间件必须:

构造函数将栈中的下一个应用程序作为参数。 响应以Environment散列作为参数的调用方法。

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

这里,由于Rack::Builder在底层所做的工作,MessageSetter的initialize方法的app参数是MessageApp。

(在继续前进之前先想想上面的内容)

因此,中间件的每个部分本质上都是将现有的环境哈希传递给链中的下一个应用程序——因此,在将其传递给堆栈中的下一个应用程序之前,您有机会在中间件中改变环境哈希。

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

结论

使用Rack::Builder,您可以构建中间件链,对应用程序的任何请求都将由每个中间件依次处理,最后由堆栈中的最后一块处理(在我们的例子中,是MessageApp)。这非常有用,因为它分离了处理请求的不同阶段。就“关注点分离”而言,它再干净不过了!

你可以构建一个“请求管道”,由几个中间件组成,处理如下事情:

身份验证 授权 缓存 装饰 性能和使用监控 执行(实际处理请求并提供响应)

(以上是本帖另一个答案的要点)

您将经常在专业的Sinatra应用程序中看到这一点。Sinatra使用Rack!看这里什么是辛纳屈的定义!

最后要注意的是,我们的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

有用的链接

这篇文章的完整代码(Github回购提交) 好的博文,“机架中间件介绍” 一些好的机架文档


机架- Web和应用服务器的接口

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

通俗地说,它基本上只是一组指导方针,说明服务器和Rails应用程序(或任何其他Ruby web应用程序)应该如何相互通信。

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

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。要测试这一点,只需在rails目录中运行rackup并查看结果。您还可以指定要在哪个端口上运行它。在任何特定端口上运行机架文件的命令为

rackup -p PORT_NUMBER

Rack提供了一个简单的接口来抽象HTTP请求/响应。Rack位于web框架(Rails, Sinatra等)和web服务器(unicorn, puma)之间,作为适配器。从上图来看,这使得unicorn服务器完全独立于了解rails的服务器,而rails不知道unicorn的服务器。这是一个松耦合、关注点分离的好例子。

上图来自机架https://youtu.be/3PnUV9QzB0g上的rails会议演讲,我建议观看它以获得更深入的理解。