Ruby中的机架中间件是什么?对于他们所说的“中间件”,我找不到任何好的解释。
当前回答
在很长一段时间里,我自己都无法理解Rack。我是在自己制作了这个小型Ruby web服务器之后才完全理解它的。我在我的博客http://blog.gauravchande.com/what-is-rack-in-ruby-rails上分享了我对Rack的了解(以故事的形式)
非常欢迎反馈。
其他回答
机架- Web和应用服务器的接口
Rack是一个Ruby包,它为web服务器提供了与应用程序通信的接口。在web服务器和应用程序之间添加中间件组件来修改请求/响应的行为方式是很容易的。中间件位于客户机和服务器之间,处理入站请求和出站响应。
通俗地说,它基本上只是一组指导方针,说明服务器和Rails应用程序(或任何其他Ruby web应用程序)应该如何相互通信。
要使用Rack,提供一个“app”:一个响应调用方法的对象,将环境哈希作为参数,并返回一个包含三个元素的数组:
HTTP响应代码 标头哈希 响应体,必须响应每个请求。
欲了解更多解释,请点击以下链接。
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
我使用Rack中间件解决了几个问题:
使用自定义机架中间件捕获JSON解析错误,并在客户端提交错误JSON时返回格式化良好的错误消息 内容压缩通过机架::Deflater
在这两种情况下,它都提供了相当优雅的修复。
机架作为设计
机架中间件不仅仅是“一种过滤请求和响应的方式”——它是使用机架的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集:机架中间件的屏幕放映。 机架中间件从机架发展而来,在机架中间件介绍中有一个很好的介绍。 维基百科上有关于中间件的介绍。
机架是什么?
Rack提供了支持Ruby和Ruby框架的web服务器之间的最小接口。
使用Rack可以编写一个Rack应用程序。
机架将传递环境哈希(一个哈希,包含在来自客户端的HTTP请求中,由类似cgi的头组成)给您的机架应用程序,该应用程序可以使用这个哈希中包含的内容来做任何它想做的事情。
什么是机架应用程序?
要使用Rack,你必须提供一个“app”——一个响应#call方法的对象,并将环境哈希作为参数(通常定义为env)。#call必须返回一个包含三个值的数组:
状态码(例如“200”), 一个头的哈希, 响应体(必须响应Ruby方法)。
你可以写一个Rack应用程序,返回这样一个数组-这将被发送回你的客户端,由机架,在一个响应(这实际上是类Rack::Response的一个实例[点击进入文档])。
一个非常简单的机架应用程序:
Gem安装架 创建一个config.ru文件- Rack知道要寻找这个文件。
我们将创建一个小型的Rack应用程序,它返回一个响应(Rack::Response的一个实例),其响应体是一个包含String: "Hello, World!"的数组。
我们将使用命令机架启动本地服务器。
当在浏览器中访问相关端口时,我们将看到“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的实例并将其发送回客户端。浏览器使用魔法打印“Hello, World!”对着屏幕说。
顺便说一句,如果你想看看环境哈希是什么样的,只要把puts env放在def call(env)下面。
尽管非常简单,但您在这里编写的是一个机架应用程序!
使机架应用程序与传入环境哈希进行交互
在我们的小Rack应用程序中,我们可以与env哈希进行交互(有关Environment哈希的更多信息,请参阅这里)。
我们将实现用户向URL中输入自己的查询字符串的能力,因此,该字符串将出现在HTTP请求中,封装为Environment散列的一个键/值对中的值。
我们的Rack应用程序将从Environment哈希中访问该查询字符串,并通过响应中的Body将其发送回客户端(在本例中是我们的浏览器)。
从机架文档的环境哈希: QUERY_STRING:请求URL中跟在?后面的部分(如果有的话)。可能是空的,但总是需要的!”
#./message_app.rb
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
现在,安装并访问localhost:9292?你好(?Hello是查询字符串),你应该看到' Hello '呈现在视口中。
架的中间件
我们将:
插入一个机架中间件到我们的代码库-一个类:MessageSetter, Environment哈希将首先碰到这个类,并将作为参数传入: MessageSetter将在env散列中插入一个'MESSAGE'键,其值为'Hello, World!' if env['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
从Rack::Builder文档中,我们看到Rack::Builder实现了一个小的DSL来迭代地构造Rack应用程序。这基本上意味着你可以构建一个“堆栈”,由一个或多个中间件和一个“底层”应用程序组成。所有通过底层应用程序的请求都将首先由中间件处理。
#use指定在堆栈中使用的中间件。它以中间件作为参数。
机架中间件必须:
构造函数将栈中的下一个应用程序作为参数。 响应以Environment散列作为参数的调用方法。
在我们的例子中,“中间件”是MessageSetter,“构造函数”是MessageSetter的初始化方法,堆栈中的“下一个应用程序”是MessageApp。
这里,由于Rack::Builder在底层所做的工作,MessageSetter的initialize方法的app参数是MessageApp。
(在继续前进之前先想想上面的内容)
因此,中间件的每个部分本质上都是将现有的环境哈希传递给链中的下一个应用程序——因此,在将其传递给堆栈中的下一个应用程序之前,您有机会在中间件中改变环境哈希。
#run接受一个参数,该参数是一个响应#call的对象,并返回一个Rack Response (Rack::Response的实例)。
结论
使用Rack::Builder,您可以构建中间件链,对应用程序的任何请求都将由每个中间件依次处理,最后由堆栈中的最后一块处理(在我们的例子中,是MessageApp)。这非常有用,因为它分离了处理请求的不同阶段。就“关注点分离”而言,它再干净不过了!
你可以构建一个“请求管道”,由几个中间件组成,处理如下事情:
身份验证 授权 缓存 装饰 性能和使用监控 执行(实际处理请求并提供响应)
(以上是本帖另一个答案的要点)
您将经常在专业的Sinatra应用程序中看到这一点。Sinatra使用Rack!看这里什么是辛纳屈的定义!
最后要注意的是,我们的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
有用的链接
这篇文章的完整代码(Github回购提交) 好的博文,“机架中间件介绍” 一些好的机架文档
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。
推荐文章
- VS2013外部构建错误"error MSB4019: The imported project <path> was not found"
- Rails:如何在Rails 4中引用CSS中的图像
- 为什么说“HTTP是无状态协议”?
- 我需要HTTP GET请求的内容类型报头吗?
- 如何使用RSpec的should_raise与任何类型的异常?
- 如何创建退出消息
- 忽略GEM,因为没有构建它的扩展
- Rails -嵌套包括活动记录?
- 在Gem::Specification.reset期间未解决的规格:
- Delete_all vs destroy_all
- 双引号vs单引号
- 用any可以吗?'来检查数组是否为空?
- Rails获取“each”循环的索引
- 如何让Chrome允许混合内容?
- Ruby类实例变量与类变量