什么是Rack中间件?

时间:2010-02-13 05:16:56

标签: ruby-on-rails ruby http web-applications rack

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

9 个答案:

答案 0 :(得分:331)

Rack as Design

机架中间件不仅仅是“过滤请求和响应的一种方式” - 它是使用pipeline design pattern的Web服务器Rack的实现。

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

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

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

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

  • 缓存:我是否已处理此请求,是否可以返回缓存结果?

  • 装修:如何更好地提升下游处理效果?

  • 效果&使用情况监控:我可以从请求和响应中获取哪些统计信息?

  • 执行:实际处理请求并提供回复。

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

社区

围绕Rack Middleware开发了一个很好的生态系统 - 您应该能够找到预先构建的机架组件来执行上述所有步骤以及更多步骤。请参阅the Rack GitHub wiki for a list of middleware

什么是中间件?

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

更多信息

答案 1 :(得分:69)

首先,Rack正好是两件事:

  • 网络服务器接口约定
  • 宝石

Rack - 网络服务器界面

机架的基础知识是一个简单的约定。每个符合机架的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

由于约定指定我们的服务器有一个名为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

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

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

有效。大!这是每个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

所以基本上是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]。 / p>

为了让我们的小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   

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

Rack - The Gem

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

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

  <body>
    <p>Index Page</p>
  </body>
</html>

我们可能想从网站root提供此文件,所以让我们将以下内容添加到我们的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文件的列表。

答案 2 :(得分:20)

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

反馈非常受欢迎。

答案 3 :(得分:6)

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。

答案 4 :(得分:6)

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

答案 5 :(得分:4)

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

  1. Catching JSON parse errors with custom Rack middleware并在客户端提交已破坏的JSON
  2. 时返回格式错误的错误消息
  3. Content Compression via Rack::Deflater
  4. 在这两种情况下都提供了非常优雅的修复。

答案 6 :(得分:4)

什么是Rack?

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

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

Rack会将环境哈希(一个Hash,包含在客户端的HTTP请求中,包含类似CGI的头文件)传递给您的Rack应用程序,它可以使用此哈希中包含的内容来执行任何操作。

什么是机架应用程序?

要使用Rack,您必须提供一个应用程序&#39; - 使用Environment Hash作为参数响应#call方法的对象(通常定义为env)。 #call必须返回一个恰好包含三个值的数组:

  • 状态代码(例如&#39; 200&#39;),
  • 标题哈希
  • 响应主体(必须响应Ruby方法,each)。

你可以编写一个返回这样一个数组的Rack应用程序 - 这将通过Rack在 Response 中发送回你的客户端(这实际上是一个实例班级Rack::Response [点击进入文档]。

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

  • gem install rack
  • 创建一个config.ru文件 - Rack知道要查找此内容。

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

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

在浏览器中访问相关端口时,我们将看到&#34; Hello,World!&#34;在视口中渲染。

#./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!&#39;呈现。

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

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

顺便提一下,如果您想查看环境哈希的外观,只需将puts env放在def call(env)下面。

最小,你在这里写的是一个Rack应用程序!

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

进行交互

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

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

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

来自环境哈希的Rack文档: &#34; QUERY_STRING:请求网址中的?部分,如果有的话。可能是空的,但总是需要的!&#34;

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

现在,rackup并访问localhost:9292?hello?hello是查询字符串),您应该看到&#39; hello&#39;在视口中渲染。

机架中间件

我们会:

  • 将一个Rack Middleware插入我们的代码库 - 一个类:MessageSetter
  • Environment hash将首先命中此类,并将作为参数传递:env
  • MessageSetter会在env哈希中插入'MESSAGE'密钥,如果'Hello, World!'为空,则其值为env['QUERY_STRING']; env['QUERY_STRING']如果没有,
  • 最后,它会返回@app.call(env) - @app成为&#39; Stack&#39;:MessageApp中的下一个应用。

首先,&#39;长手&#39;版本:

#./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 docs我们看到Rack::Builder实现了一个小型DSL,以迭代方式构建Rack应用程序。这基本上意味着你可以构建一个&#39; Stack&#39;由一个或多个中间件和一个底层组成的&#39;申请发送到。所有进入底层应用程序的请求将首先由您的中间件处理。

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

机架中间件必须:

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

在我们的案例中,&#39;中间件&#39;是MessageSetter,&#39;构造函数&#39;是MessageSetter的initialize方法,&#39; next application&#39;堆栈中的MessageApp

所以在这里,由于Rack::Builder所做的事情,app MessageSetter方法的initialize参数为MessageApp

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

因此,每件中间件基本上都会传递下去&#39;现有的环境哈希到链中的下一个应用程序 - 所以你有机会在将中间件传递给堆栈中的下一个应用程序之前改变中间件中的环境哈希。

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

结论

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

您可以构建一个&#39;请求管道&#39;由几个处理诸如以下内容的中间件组成:

  • 验证
  • 授权
  • 缓存
  • 装饰
  • 表演&amp;使用情况监控
  • 执行(实际处理请求并提供响应)

(在此主题的另一个答案的上面的要点)

您经常会在专业的Sinatra应用程序中看到这一点。 Sinatra使用Rack!有关Sinatra IS 的定义,请参阅here

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

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

use MessageSetter
run MessageApp.new

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

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

有用的链接

答案 7 :(得分:4)

image showing rack in between unicorn and rails

Rack是一个gem,它提供了一个用于抽象HTTP请求/响应的简单接口。机架位于Web框架(Rails,Sinatra等)和Web服务器(独角兽,Puma)之间,作为适配器。从上面的图像可以使独角兽服务器完全独立于对Rails的了解,而Rails对独角兽一无所知。这是loose couplingseparation of concerns的一个很好的例子。

上图是在https://youtu.be/3PnUV9QzB0g机架上的Rails会议上的演讲,我建议观看它以加深了解。

答案 8 :(得分:0)

机架-Web和App Server的界面

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

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

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

  • HTTP响应代码
  • 标题的哈希
  • 响应正文,必须对每个请求做出响应。

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

varchar

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

rackup