什么是Rack中间件?

chr*_*yal 260 ruby rack web-applications ruby-on-rails http

什么是Ruby中的Rack中间件?对于"中间件"的含义,我找不到任何好的解释.

Chr*_*ley 341

机架设计

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

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

例如,对于Rack,我可以在管道的各个阶段执行:

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

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

  • 缓存:我已经处理过这个请求,我可以返回一个缓存的结果吗?

  • 装修:如何提高下游处理效果的要求?

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

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

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

社区

围绕Rack Middleware开发了一个很好的生态系统 - 您应该能够找到预先构建的机架组件来执行上述所有步骤以及更多步骤.有关中间件列表,请参阅Rack GitHub wiki.

什么是中间件?

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

更多信息

  • Rack是应用程序的一部分,因此所有中间件都会组合请求的相同副本,并且每个中间件都可以以他们想要的任何方式对其进行修改.AFAIK,没有办法以相同的方式对它们进行沙盒化,因为在同一进程中无法将一个对象与另一个对象进行沙盒化(尽管尝试使用Ruby沙盒). (2认同)
  • 我喜欢将中间件视为位于我的应用程序中间的任何东西,介于我编码的内容和进出服务器的内容......托管在机架空间上。众所周知,“机架中间件”一词之所以令人困惑,是因为 2000 多年前,孔子编写了所有原始的机架中间件。在法国。 (2认同)

Tho*_*ser 70

首先,Rack正是两件事:

  • Web服务器接口约定
  • 宝石

Rack - Web服务器接口

机架的基础知识是一个简单的约定.每个符合机架的Web服务器将始终在您提供给他的对象上调用call方法,并提供该方法的结果.Rack确切地指定了此调用方法的外观,以及它必须返回的内容.那是架子.

我们来试试吧.我将WEBrick用作符合机架标准的网络服务器,但其中任何一个都可以.让我们创建一个返回JSON字符串的简单Web应用程序.为此,我们将创建一个名为config.ru的文件.config.ru将由rack gem的命令rackup自动调用,它将简单地在符合机架的网络服务器中运行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
Run Code Online (Sandbox Code Playgroud)

正如约定所指定的那样,我们的服务器有一个名为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
Run Code Online (Sandbox Code Playgroud)

让我们通过卷曲或访问网址测试我们的新JSON服务器http://localhost:9292/hello.json并瞧瞧:

$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
Run Code Online (Sandbox Code Playgroud)

有用.大!这是每个Web框架的基础,无论是Rails还是Sinatra.在某些时候,他们实现了一个调用方法,处理所有框架代码,最后以典型的[status,headers,body]形式返回响应.

例如,在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
Run Code Online (Sandbox Code Playgroud)

所以基本上是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
Run Code Online (Sandbox Code Playgroud)

创建后,它会自行保存实际机架应用程序的副本.在我们的例子中,这是我们的JSONServer的一个实例.Rack自动调用中间件上的调用方法,并期望返回一个[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   
Run Code Online (Sandbox Code Playgroud)

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

机架 - 宝石

虽然机架 - 首先 - 是一个惯例,它也是一个提供强大功能的宝石.其中一个我们已经用于我们的JSON服务器,即rackup命令.但还有更多!rack gem为很多用例提供很少的应用程序,比如提供静态文件甚至整个目录.让我们看看我们如何提供一个简单的文件,例如位于htmls/index.html的一个非常基本的HTML文件:

<!DOCTYPE HTML>
  <html>
  <head>
    <title>The Index</title>
  </head>

  <body>
    <p>Index Page</p>
  </body>
</html>
Run Code Online (Sandbox Code Playgroud)

我们可能想从网站root提供这个文件,所以让我们在config.ru中添加以下内容:

map '/' do
  run Rack::File.new "htmls/index.html"
end
Run Code Online (Sandbox Code Playgroud)

如果我们访问,http://localhost:9292我们会看到我们的html文件完美呈现.这很简单,对吧?

让我们通过在/ javascripts下创建一些javascript文件并将以下内容添加到config.ru来添加整个javascript文件目录:

map '/javascripts' do
  run Rack::Directory.new "javascripts"
end
Run Code Online (Sandbox Code Playgroud)

重新启动服务器并访问http://localhost:9292/javascript,您将看到现在可以从任何地方直接包含的所有JavaScript文件的列表.

  • 但不是Rack中间件吗? (3认同)
  • 你说得对,谢谢.我现在添加了一个中间件部分. (3认同)
  • 如果您不知道机架是什么,那么在阅读这篇博文后您就会确切地知道它是什么*以及*如何使用它。很不错。但讽刺的是,帖子末尾的官方机架文档链接不再可用! (2认同)

Gau*_*nde 19

我有很多时间了解Rack自己的问题.在我自己制作这个微型Ruby Web服务器之后,我才完全理解它.我在博客上分享了关于Rack(以故事形式)的知识:http://gauravchande.com/what-is-rack-in-ruby-rails

反馈非常受欢迎.

  • [Stack Overflow上不鼓励仅链接答案](http://stackoverflow.com/help/how-to-answer),因为如果链接所使用的资源在将来变得不可用,则答案变得无用.请至少总结一下您博客文章的相关要点,并将其添加到此答案中. (12认同)

And*_*ndy 8

什么是机架?

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

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

Rack 会将 Environment 散列(一个散列,包含在来自客户端的 HTTP 请求中,由类似 CGI 的标头组成)传递给您的 Rack 应用程序,该应用程序可以使用此散列中包含的内容来做任何它想做的事情。

什么是机架应用程序?

要使用 Rack,您必须提供一个“应用程序”——一个以#callEnvironment Hash 作为参数(通常定义为env)响应方法的对象。#call必须返回一个正好包含三个值的数组:

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

您可以编写一个返回这样一个数组的 Rack 应用程序 - 这将通过 Rack 在响应中发送回您的客户端(这实际上是类的一个实例Rack::Response[单击转到文档])。

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

  • gem install rack
  • 创建一个config.ru文件——Rack 知道要查找这个文件。

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

我们将使用命令启动本地服务器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
Run Code Online (Sandbox Code Playgroud)

启动本地服务器rackup并访问localhost:9292,您应该会看到“Hello, World!” 呈现。

这不是一个全面的解释,但本质上这里发生的是客户端(浏览器)通过本地服务器向 Rack 发送 HTTP 请求,然后 Rack 实例化MessageApp并运行call,将 Environment Hash 作为参数传递到方法中(该env参数)。

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

顺便说一句,如果你想看到什么环境散的样子,只是把puts env底下def call(env)

尽管它是最小的,但您在此处编写的是 Rack 应用程序!

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

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

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

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

来自 Environment Hash 上的 Rack 文档: “QUERY_STRING:请求 URL 后面的部分?,如果有。可能为空,但始终是必需的!”

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

现在,rackup访问localhost:9292?hello?hello作为查询字符串),您应该会在视口中看到“hello”。

机架中间件

我们会:

  • 插入了一段Rack中间件到我们的代码库-一类:MessageSetter
  • Environment hash 将首先命中这个类,并将作为参数传入:env,
  • MessageSetter'MESSAGE'在 env 哈希中插入一个键,其值为'Hello, World!'ifenv['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
Run Code Online (Sandbox Code Playgroud)

Rack::Builder 文档中,我们看到它Rack::Builder实现了一个小的 DSL 来迭代构建 Rack 应用程序。这基本上意味着您可以构建一个由一个或多个中间件和一个“底层”应用程序组成的“堆栈”以进行分派。通过您的底层应用程序的所有请求将首先由您的中间件处理。

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

机架中间件必须:

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

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

所以在这里,因为在幕后Rack::Builder做了什么,的方法的app参数是.MessageSetterinitializeMessageApp

(在继续之前请先了解上述内容)

因此,每个中间件本质上都将现有的环境散列“传递”到链中的下一个应用程序 - 因此您有机会在中间件中改变该环境散列,然后再将其传递给堆栈中的下一个应用程序。

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

结论

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

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

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

(以上来自该线程的另一个答案的要点)

您会经常在专业的 Sinatra 应用程序中看到这一点。Sinatra 使用 Rack!见这里的什么西纳特拉的定义IS

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

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

use MessageSetter
run MessageApp.new
Run Code Online (Sandbox Code Playgroud)

为了更明确地显示MessageApp正在做什么,这里是它的“长手”版本,它明确地显示#call正在创建 的新实例Rack::Response,并带有所需的三个参数。

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

有用的链接


Cir*_*四事件 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)
Run Code Online (Sandbox Code Playgroud)

运行rackup并访问localhost:9292.输出是:

main
Middleware
Run Code Online (Sandbox Code Playgroud)

很明显,Middleware包装和调用主应用程序.因此,它能够预处理请求,并以任何方式对响应进行后处理.

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

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


小智 6

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


Vbp*_*Vbp 6

图像显示独角兽和导轨之间的机架

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

上图来自机架上的这个 rails 会议演讲https://youtu.be/3PnUV9QzB0g我建议观看它以加深理解。