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


当前回答

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

非常欢迎反馈。

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就是两件事:

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文件。

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。