什麽是 Rack?

本文章同步发布於 我的部落格

什麽是 Rack ?

Rack 是 Ruby 所有的网路框架背後最底层的技术,Rack 可以是:

1。 一个架构:Rack 定义了一个非常简单的结构,任何符合这个结构的程序码都可以在 Rack 的应用程序中被应用,这让我们在建构小型、集中、可重复使用的程序码段落变得简单,然後组合这些 Rack 变成一个更大的应用程序。

2。一个 Ruby Gem:以 Ruby Gem 的形式发布,并且提供了组合程序码所需要的 glue code。

Rack Interface

Rack 定义了一个很简单的 interface 以及接口,符合 Rack 标准的程序码包含下面三点:

1。他必须对 call 做出回应,call 方法必须接受一个参数,通常是 env 或是 environment,因为他会汇集所有关於这次请求的资料,就像是这次请求送过来的所有参数。

2。call 方法必须回应一个由 HTTP状态码 & Headers 以及 body(内容) 所组成的阵列

3。关於 call 有个很棒的点就是 Proc 以及 lambda 可以被当作 Rack 的物件做使用

Rack Hello World

接下来的例子是一个符合 Rack 标准的 app,简单地回应 Hello World

require 'rack'

class HelloWorld
  def call(env)
    [ 200, { "Content-Type" => "text/plain" }, ["Hello World"] ]
  end
end

run HelloWorld.new

在上面的例子里面,我们实践了一个类别搭配一个实体方法 call,并且加入环境变数,然後总是回传 HTTP Status 200、一个 Content-type 的 headers,最後是内容 Hello World

还有一个需要注意的地方,Rack 期待 body 回传的部分是能够套用 each 方法,所以单纯的字串需要被放到阵列里面才能够正确的回应,但 body 大部分都是其他类型的 IO 物件,而不是单纯的字串,只要不是单纯字串,都可以不需要套入阵列之中。

探索 env 物件

require 'rack'

class HelloWorld
  def call(env)
    [ 200, { "Content-Type" => "text/plain" }, [env.inspect] ]
  end
end

将原本的程序码改成上面的样子,就可以看到 env 物件本身是一个 hash,然後内部集合了所有关於这次 request 和 response 的内容。

里面包含了使用者使用的装置,cookies 以及 request 的路径等等,全部的值都是简单的字串,而不是像 rails 一样复杂结构的 hash,但是所有的东西都在这包 hash 内,也供我们能够检查和组成我们的服务器端的回应!

使用 lambda

这个示范只是好玩,并证明上面提到可以使用 lambda 或是 Proc 来执行 rack!

require 'rack'

app = -> (env) do
  [200, { 'Content-type' => 'text/plain'}, [env.inspect]]
end

run app

Middleware

Middleware 是用 rack 模式所组成的,通常会是大型应用程序的小积木,每一个 middleware 都是一个和 rack 相容的程序,而最终的应用程序就是透过这些 middleware 组合再一起而成。

Logging Middleware 实作

不像一般的 rack 程序,middleware 必须是类别,因为需要 initialize 来存放 app 并且传送给执行链上的下一个 middleware 或是 rack。

像是下面的 middleware 示范,我们在 request 以及 response 中间加入了 logs 来计算回应所花费的时间。

但在一开始,我们需要让 Rack app 先沈睡三秒,这样我们才有东西可以计算,来看看怎麽写吧!

require 'rack'

app = ->(env) do
  sleep 3
  [200, {'Content-type' => 'text/plain'}, ['Hello, World']]
end

class LoggingMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    before = Time.now.to_i
    status, headers, body = @app.call(env)
    after = Time.now.to_i
    log_messages = "App Work #{after - before} second."

    [status, headers, [log_messages.inspect]]
  end
end

run LoggingMiddleware.new(app)

这里我们可以看到,我们交给 LoggingMiddlewareapp 是最基础的应用程序,被我们的 middleware 给包覆住了。

在 middleware 的 call 方法中,我们撷取了时间戳记,并把请求转送给执行链上的下一个 app,每一个 middleware 都只知道下一个是谁,所以在最终的结果出来之前,可能有超级多层的 middleware。

在我们 call 下一个 app 的那一行,我们解构了 app 回传的阵列到 status, headers, body,这样可以让我们针对回传的结果做检查或是更改。

在这个 LoggingMiddleware 的情况下,我们把时间戳记加入到了回应的 body 内,并且让其显示在画面中。

Middleware 的实际用法

感谢 Rack interface 的简单性和灵活性,middleware 被用在大量不同的功能。

有一个很酷的东西叫做 rack-honeypot 可以拿来侦测和应用程序互动的垃圾邮件或是恶意机器人,他在所有的 response 中加入了一个隐藏的栏位,是只有机器人才有可能填写的,然後他会解析这个所有的请求,并取消掉有填了隐藏栏位的请求。

Rack::Builder

虽然 Rack 和使用 middleware 是一个很好把应用程序组合起来的方式,但他可能会变得有点冗长,而 Rack::Builder 是一个模式,可以让我们更简洁地定义我们的应用程序和 middleware

我们可以在 config.ru 中定义,这是一个 Rack 专用的文件 ( .ru 代表 rackup )

app = ->(env) do
  sleep 3
  [200, {'Content-type' => 'text/plain'}, ['Hello, World']]
end

class LoggingMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    before = Time.now.to_i
    status, headers, body = @app.call(env)
    after = Time.now.to_i
    log_messages = "App Work #{after - before} second."

    [status, headers, [log_messages.inspect]]
  end
end

use LoggingMiddleware
run app

主要的变化就是可以透过 run & use 来建构我们的应用程序,可以一目了然地知道使用了什麽样子的 middleware

The middleware stack

上面的 Rack::Builder 方法可以让我们更轻松地建构应用程序的 middleware stack,middleware 将会已特定地顺序排序,并以特定地顺序执行。

这边就是所谓的 FILO ( First In Last Out ),所以执行的顺序会先是 run 的那个主程序,因为他是最後放进 stack 里面的,接着再往上执行( 就是往程序码的上放继续执行下去 )

一个 request 将会穿越这个 middleware stack 来到最终的 app,然後所有的 response 都会透过执行链向上传递。

use FirstMiddleware #第四个执行
use SecondMiddleware #第三个执行
use ThirdMiddleware #第二个执行

run app #第一个执行

用 rackup 跑起来!

当我们有了 config.ru 来组织我们的 middleware,我们就可以利用 rack gem 提供的 rackup 来执行我们的 rack app

rackup 执行的时候,将会自动载入 rack,并且 use & run 方法将会被使用。

我们可以用 Middleware 做些什麽?

Middleware 很适合拿来用在非应用程序的特定逻辑。像是设置暂存的 headers,logging,解析 request 物件等等...

上述提到的几个用法,都是在 Rails 里面很常见的 middleware!

结语

Rack 本身提供了强大又简洁的抽象概念,让我们可以去分离应用程序的核心逻辑和外围的关注点 ( ex: 解析 HTTP headers )

他几乎是所有 Ruby 有关的网路框架拿来运行和 server 之间的 interface,真的是非常好用!

会想写关於 Rack 的东西是因为,今天被有被问到关於 Rack 的问题,但觉得自己解释的并没有很好,所以才赶快来研究并且纪录!


<<:  Visual studio 2019 使用ClangFormat对程序码(C/C++)风格排版格式化

>>:  C# Console 用法整理

视觉化的沟通框架 影响力地图impact mapping

今天要来分享的是产品经理在做需求管理、和对外沟通的时候,都很需要的一个思考框架 - 影响力地图 im...

第二十九天:为 IntelliJ Platform 设计的 TeamCity Plugin

在我们整个系列教学里,所有的操作都是在 TeamCity 的 Web UI 上完成,而 TeamCi...

[Day 14] Reverse 小入门

时间飞逝,已到第14天了 明天就一半ㄌ,好感动眼睛流汗 今天我们要干大事!!! 要来解 REVERS...

Android学习笔记15

上次试了一般转圈圈的progressbar,这次换进度条的progressbarr 首先是xml &...

Day16【Web】网路攻击:连线劫持/Cookie 窃取

Cookie 窃取/连线劫持 英文为 Session Hijacking 或 Sidejacking...