Advanced Rack

阅读这篇文章的读者假设你已经了解HTTP相关的一些东西,例如状态码返回2XXX表示请求成功并且Web服务器成功响应;同时读者也默认读书对Ruby有一定的了解。

什么是Rack

先来一张图:
rack.jpg
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

运行试试:

demo.jpg
继续阅读:
What is ‘Rack’ in Ruby/Rails?

0 条评论
您想说点什么吗?