Advanced Rack
阅读这篇文章的读者假设你已经了解HTTP相关的一些东西,例如状态码返回2XXX表示请求成功并且Web服务器成功响应;同时读者也默认读书对Ruby有一定的了解。
什么是Rack
先来一张图:
Rack为Ruby的Web服务器规定标准的接口
主流的Ruby Web服务器(Puma,WEBrick,Unicorn...)都遵循Rack协议而开发。如果开发的应用遵循Rack应用标准,那么可以免费使用这些Web服务器。
Rack在Ruby Web开发框架的基础构件,比如Rails,Sinatra.它为HTTP的请求响应周期提供了“Ruby风格”的接口。由于Rails基于Rack开发,那么熟悉Rack有助于开发者编写更优雅的Rails代码和测试。
Rack标准
Rack官方文档对其标准的描述如下:
A Rack application is a Ruby object (not a class) that responds to call. It takes exactly one argument, the environment and returns an Array of exactly three values: The status, the headers, and the body.
中文理解就是一个Rack应用程序其实就是一个Ruby对象(而不是一个类),这个对象能够响应调用消息。Rack接受一个“enviroment”参数并且返回带有三个值的数组:响应状态、响应头和响应体。
正如你所见,开发一个Rack应用程序资源开销很少。它就是一个指定的对象并返回另外一个指定对象,这就是“Ruby范”
接下来逐步新建一个Rack App,在开始之前别忘了安装 rack gem gem install rack
一个微型Rack App
这是一个能够响应任何HTTP请求的Rack应用程序:
# cool_rack_application.rb
require "rack"
class CoolRackApplication
def call(env)
http_verb = env["REQUEST_METHOD"]
status = 200
headers = {}
body = ["got #{http_verb} request\n"]
[status, headers, body]
end
end
# Run on localhost, port 9292
Rack::Handler::WEBrick.run CoolRackApplication.new, Port: 9292
上面的CoolRackApplication类没有继承任何类并且没有引用其他模块。cd进入cool_rack_application.rb文件的目录,运行 ruby cool_rack_application.rb
在浏览器中访问http://localhost:9292
。那么你就可以在浏览器中看到“got get request”接下来解释一从参数 env 开始解释一下这段代码。
Rack environment
Rack environment就是传递给call
方法的env
变量。这个变量包含了HTTP请求的一些相关信息,例如请求的方法(get,post等)、请求路径和请求的主机名等。通过env["REQUEST_METHOD"]
获取请求方式、返回200响应状态码、响应头为空以及响应体。需要注意的是Rack规定相应体必须能够被#eacho
方法调用,所以在此我们使用数组来封装相应体body = ["got #{http_verb} request\n"]
Rack::Request
为了更便捷实用Rack environment,Rack把env封装在Rack::Request
类中。request = Rack::Request.new(env)
使用这个封装类,我们可以直接通过request.request_method
来替代env["REQUEST_METHOD"]
是不是显的更加优雅了?不信看看下面的对比。
# env["REQUEST_METHOD"]
request.request_method
# env["rack.request.query_hash"] + env["rack.input"]
request.params
# env["REQUEST_METHOD"] == "GET"
request.get?
直接访问env
变的好乏味,那么使用Rack::Request吧。接下来解析一下Rack::Handler::WEBrick.run CoolRackApplication.new, Port: 9292
这段代码。
Handlers
Rack通过handlers
运行Rack应用程序。每一种Ruby Webserver都有本身的handlers
在演示代码中选择了WEBrick
,WEBrick是ruby默认已经安装了。
如果你安装了puma,可以在puma中跑这段演示代码:
require "rack/handler/puma"
Rack::Handler::Puma.run CoolRackApplication.new, Port: 9292
中间件的出现
演示代码中,CoolRackApplication都是直接接受请求并且直接响应请求,然而在Rack的世界中并不如此。现实场景中,对于web,我们往往是对请求和响应进行处理之后才交由我们最终的应用程序进行执行,此时中间件这个概念应运而生。Rack应用(诸如puma,Unicorn)在最终的应用程序和用户请求之间充当桥梁的作用,事实上Rack的真正特性就是体现在中间件上。接下来演示一段拦截每个PATCH
请求的中间件代码。
class PatchBlockingMiddleware
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
if request.patch?
[405, {}, ["PATCH requests not allowed!\n"]]
else
@app.call(env)
end
end
end
PatchBlockingMiddleware
同样也是一个Rack app,call
和微型Rack app中的call
方法类似,同样接受一个env
参数和返回带有三个元素的数组,但不同的是这个Rack app使用app
进行初始化,这个app是“最后”Rack app,能够把用户请求传递给后端应用。PatchBlockingMiddleware
中,如果用户发送的请求为PATCH
,它能够返回405同时给出PATCH requests not allowed!
错误提示,此时用户请求不再传递给后端应用。反之如果用户请求非PATCH
请求就会被它传递到后端应用。
那么,中间件和后端应用又是如何联系起来的呢?接下来我们看看Rack::Builder
Rack Builder
一句话总结Rack Builder就是它把中间件和后端app联结在一块,使他们之间能够进行信息交流。那么我们试着把CoolRackApplication
和中间件PatchBlockingMiddleware
绑定在一起促使它们能够进行信息交流。
app = Rack::Builder.new do
use PatchBlockingMiddleware
run CoolRackApplication.new
end
Rack::Handler::WEBrick.run app, Port: 9292
每个用户请求首先经过PatchBlockingMiddleware
,如果它通过@app.call(env)
把请求传递给下一个环节,那么CoolRackApplication.new
将接收到传递过来的请求。
此时全部代码应是这样的:
require "rack"
class CoolRackApplication
def call(env)
request = Rack::Request.new(env)
http_verb = request.request_method
status = 200
headers = {}
body = ["got #{http_verb} request\n"]
[status, headers, body]
end
end
class PatchBlockingMiddleware
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
if request.patch?
[405, {}, ["PATCH requests not allowed!\n"]]
else
@app.call(env)
end
end
end
app = Rack::Builder.new do
use PatchBlockingMiddleware
run CoolRackApplication.new
end
Rack::Handler::WEBrick.run app, Port: 9292
运行试试: