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集:机架中间件的屏幕放映。 机架中间件从机架发展而来,在机架中间件介绍中有一个很好的介绍。 维基百科上有关于中间件的介绍。

其他回答

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。我是在自己制作了这个小型Ruby web服务器之后才完全理解它的。我在我的博客http://blog.gauravchande.com/what-is-rack-in-ruby-rails上分享了我对Rack的了解(以故事的形式)

非常欢迎反馈。

首先,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中间件解决了几个问题:

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

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

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

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