什么是Ruby中的Rack中间件?对于“中间件”的含义,我找不到任何好的解释。
机架中间件不仅仅是“过滤请求和响应的一种方式” - 它是使用pipeline design pattern的web服务器的Rack实现。
它非常清晰地区分了处理请求的不同阶段 - 关注点分离是所有精心设计的软件产品的关键目标。
例如,对于Rack,我可以在管道的各个阶段执行:
能够分离不同阶段(并且可选地包括它们)对于开发结构良好的应用程序是非常有帮助的。
围绕Rack Middleware开发了一个很好的生态系统 - 您应该能够找到预先构建的机架组件来执行上述所有步骤以及更多步骤。见the Rack GitHub wiki for a list of middleware。
中间件是一个可怕的术语,指的是任何协助但不直接参与某项任务执行的软件组件/库。非常常见的示例是日志记录,身份验证和其他常见的水平处理组件。这些往往是每个人在多个应用程序中需要的东西,但没有太多人对构建自己感兴趣(或应该)。
首先,Rack正是两件事:
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
正如约定所指定的那样,我们的服务器有一个名为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]
的形式返回响应。
为了使我们的小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允许您添加按添加顺序调用的多个中间件。这是在不改变机架应用程序核心的情况下添加功能的好方法。
机架 - 宝石
虽然机架 - 首先 - 是一个惯例,它也是一个提供强大功能的宝石。其中一个我们已经用于我们的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文件的列表,您现在可以直接从任何地方包含这些文件。
我有很多时间了解Rack自己的问题。在我自己制作这个miniature Ruby web server之后,我才完全理解它。我在博客上分享了关于Rack(以故事形式)的知识:http://gauravchande.com/what-is-rack-in-ruby-rails
反馈非常受欢迎。
Rack中间件是一种过滤进入应用程序的请求和响应的方法。中间件组件位于客户端和服务器之间,处理入站请求和出站响应,但它不仅仅是可用于与Web服务器通信的接口。它用于对模块进行分组和排序,这些模块通常是Ruby类,并指定它们之间的依赖关系。机架中间件模块必须: - 具有将堆栈中的下一个应用程序作为参数的构造函数 - 响应“call”方法,该方法将环境哈希作为参数。从此调用返回的值是以下数组:状态代码,环境哈希和响应正文。
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。
我使用Rack中间件来解决几个问题:
它在两种情况下都提供了非常优雅的修复。
Rack在支持Ruby和Ruby框架的Web服务器之间提供了一个最小的接口。
使用Rack,您可以编写Rack应用程序。
Rack会将环境哈希(一个Hash,包含在来自客户端的HTTP请求中,包含类似CGI的头文件)传递给您的Rack应用程序,它可以使用此哈希中包含的内容来执行任何操作。
要使用Rack,您必须提供一个'app' - 一个响应#call
方法的对象,其中Environment Hash作为参数(通常定义为env
)。 #call
必须返回一个恰好有三个值的数组:
each
)。你可以写一个返回这样一个数组的Rack应用程序 - 这将通过Rack发送回你的客户端,在一个Response中(这实际上是一个类Rack::Response
的实例[点击转到docs])。
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
使用rackup
启动本地服务器并访问localhost:9292,您应该看到'Hello,World!'渲染。
这不是一个全面的解释,但基本上这里发生的是客户端(浏览器)通过本地服务器向机架发送HTTP请求,并且机架实例化MessageApp
并运行call
,将环境哈希作为参数传入方法(env
论证)。
Rack获取返回值(数组)并使用它来创建Rack::Response
的实例并将其发送回客户端。浏览器使用magic打印'Hello,World!'到屏幕。
顺便说一句,如果你想看看环境哈希的样子,只需将puts env
放在def call(env)
下面。
最小的是,你在这里写的是Rack应用程序!
在我们的小型Rack应用程序中,我们可以与env
哈希进行交互(有关Environment哈希的更多信息,请参阅here)。
我们将实现用户将自己的查询字符串输入到URL中的能力,因此,该字符串将出现在HTTP请求中,封装为环境哈希的一个键/值对中的值。
我们的Rack应用程序将从Environment哈希中访问该查询字符串,并通过响应中的Body将其发送回客户端(在本例中为我们的浏览器)。
来自环境哈希的机架文档:“QUERY_STRING:请求URL后面的部分,如果有的话。可能是空的,但总是需要的!”
#./message_app.rb
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
现在,rackup
并访问localhost:9292?hello
(?hello
是查询字符串),你应该在视口中看到'hello'。
我们会:
MessageSetter
,env
,MessageSetter
会在env哈希中插入一个'MESSAGE'
密钥,如果'Hello, World!'
为空,则其值为env['QUERY_STRING']
;如果没有env['QUERY_STRING']
,@app.call(env)
- @app
成为'Stack'中的下一个应用程序: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 docs我们看到Rack::Builder
实现了一个小型DSL,以迭代方式构建Rack应用程序。这基本上意味着您可以构建一个由一个或多个中间件和一个“底层”应用程序组成的“堆栈”来分发。所有进入底层应用程序的请求将首先由您的中间件处理。
#use
指定要在堆栈中使用的中间件。它以中间件为参数。
机架中间件必须:
call
方法。在我们的例子中,'Middleware'是MessageSetter
,'构造函数'是MessageSetter的initialize
方法,堆栈中的'下一个应用程序'是MessageApp
。
所以在这里,由于Rack::Builder
在幕后做了什么,app
的MessageSetter
方法的initialize
论证是MessageApp
。
(继续前行)
因此,每个中间件基本上将现有的Environment散列“传递”到链中的下一个应用程序 - 因此您有机会在将中间件传递到堆栈中的下一个应用程序之前改变该中间件中的环境散列。
#run
接受一个参数,该参数响应#call
并返回一个Rack Response(Rack::Response
的一个实例)。
使用Rack::Builder
,你可以构建中间件链,任何对你的应用程序的请求将由每个中间件依次处理,最后由堆栈中的最后一块(在我们的例子中,MessageApp
)处理。这非常有用,因为它分离出处理请求的不同阶段。在“关注点分离”方面,它可能不会更清洁!
您可以构建一个“请求管道”,其中包含几个处理以下内容的中间件:
(在此主题的另一个答案上面的子弹点)
您经常会在专业的Sinatra应用程序中看到这一点。 Sinatra使用Rack!有关Sinatra IS的定义,请参阅here!
最后一点,我们的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
Rack - 接口b / w Web&App Server
Rack是一个Ruby包,它为Web服务器提供了与应用程序通信的接口。在Web服务器和应用程序之间添加中间件组件很容易,以修改请求/响应的行为方式。中间件组件位于客户端和服务器之间,处理入站请求和出站响应。
用外行的话来说,它基本上只是服务器和Rails应用程序(或任何其他Ruby Web应用程序)如何相互通信的一套指导原则。
要使用Rack,请提供一个“app”:一个响应call方法的对象,将环境哈希作为参数,并返回一个包含三个元素的Array:
有关更多说明,请按照以下链接进行操作。
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