Ruby Rack 中间件简单理解及例子

yhuiche · 2016年08月13日 · 最后由 suffering 回复于 2016年09月09日 · 3773 次阅读

此博客原文链接

注: 这里是根据网上的常见的 rack 例子更改

rack 版本

  • Rack 为使用 Ruby 开发 web 应用提供了一个最小的模块化和可修改的接口。用可能最简单的方式来包装 HTTP 请求和响应,它为 web 服务器,web 框架和中间件的 API 进行了统一并提纯到了单一的方法调用。

  • 测试中 rack gem 版本为 1.6.4


示例代码

module Rack
  class A
    def initialize(app)
      @app = app            # @app: Rack::B
      @header_name = "X-A"
    end

    def call(env)
      start_time = Time.now
      status, headers, body = @app.call(env)  #  Rack::B 实例调用 call
      request_time = Time.now - start_time

      if !headers.has_key?(@header_name)
        headers[@header_name] = "%0.6f" % request_time
      end

      [status, headers, body]
    end
  end

  class B
    def initialize(app)
      @app = app          # @app: Rack::C
      @header_name = "X-test"
    end

    def call(env)
      status, headers, body = @app.call(env)   #  Rack::C 实例调用 call

      if !headers.has_key?(@header_name)
        headers[@header_name] = 'yyyyyy'
      end

      [status, headers, body+['aaaaaa']]
    end
  end

  class C
    def call(env)
      [200, {'Content-Type' => 'text/plain'}, ['hello world!']]
    end
  end
end

use Rack::A
use Rack::B

run Rack::C.new
  • 为了探究中间件的加载运行过程,这里用 A、B、C 顺序来定义 class
  • 实际运行的代码存在打印语句,后面有打印结果
  • 其中 Rack::C 就相当于 Rails App
  • A、B 则相当于中间件
  • A、B、C 都为 rack app
  • 通过实例变量@app实现 rack app 一种嵌套结构
  • 文件命名为 ~/test/config.ru
  • call 方法统一返回格式,每次返回结果都会在内层返回的结果上做处理后返回结果给外层的调用

运行打印结果

  • 运行命名下面名,然后用浏览器访问本地 9292 端口http://localhost:9292
  • cd ~/test
  • rackup
~/test $ rackup

new B  -------------
@app: #<Rack::C:0x007fa4b2ad4298>
------------------------------
new A-------------
@app: #<Rack::B:0x007fa4b2ad41f8>
------------------------------

Thin web server (v1.6.3 codename Protein Powder)
Maximum connections set to 1024
Listening on localhost:9292, CTRL+C to stop

A call -------------
@app: #<Rack::B:0x007fa4b2ad41f8>
------------------------------
B call ----------------
@app: #<Rack::C:0x007fa4b2ad4298>
------------------------------
C call ----------
#<Rack::C:0x007fa4b2ad4298>
------------------------------
127.0.0.1 - - [08/Aug/2016:00:57:19 +0800] "GET / HTTP/1.1" 200 - 0.0041

A call -------------
@app: #<Rack::B:0x007fa4b2ad41f8>
------------------------------
B call ----------------
@app: #<Rack::C:0x007fa4b2ad4298>
------------------------------
C call ----------
#<Rack::C:0x007fa4b2ad4298>
------------------------------
127.0.0.1 - - [08/Aug/2016:00:57:20 +0800] "GET /favicon.ico HTTP/1.1" 200 - 0.0006
  • 可以看到在 class Rack::A 中,@app为 class Rack::B 的一个实例;而在 class Rack::B 中,@app则为 Rack::C。
  • 在接受到浏览器的访问后,调用了中间件的 call 方法,这里有点类似于递归的调法。从代码及返回结果可以看出,是 Rack::A 中的 call 方法先被调用,然后是 Rack::B 中的,最后是 Rack::C 中的。返回是顺序刚好相反,先是 Rack::C 中的 call 方法先执行完,然后是 Rack::B,最后是 Rack::A。
  • 这段日志有两个访问记录,一个是 http://localhost:9292 主体,一个是网站收藏图标 favicon.ico

浏览器查看到的部分数据

HTTP/1.1 200 OK
Content-Type: text/plain
X-test: yyyyyy
X-A: 0.000032
Transfer-Encoding: chunked
Connection: close
Server: thin

浏览器页面看到的结果

hello world!aaaaaa
  • 这里看到的是浏览器中查看到了部分数据
  • 可以看出 rack 默认的 web 服务器是 thin
  • X-text、X-A 为 call 方法中返回的部分数据
  • 页面看到的东西则为 call 方法中返回的 body 部分


这个中间件是不是相当于 os 的管道 前面的输出 作为后面的输入

#1 楼 @tablecell 之前表述不清楚。rack app 之前形成一种嵌套结构。外层@app的 call 方法里调用内存@app的 call 方法,内层 call 方法先返回,之后外层 call 方法接收数据并处理后返回给更外层的 call 方法。

#2 楼 @yhuiche 这种应该是调用栈 就象函数调用一样 不是管道

需要 登录 后方可回复, 如果你还没有账号请 注册新账号