Rack

最后编辑于 2024-12-26

Rack既是一个规范,也是一个实现这个规范的Ruby库。

Rack充当了App Server(Unicorn,Puma等)和Web App(Rails,Sinatra)之间的桥梁。 符合Rack App规范的App Server和Web App可以轻松组合在一起。

Rack定义

Rack App的定义为:

A Rack application is a Ruby object (not a class) that responds to call. It takes exactly one argument, the environment and returns a non-frozen Array of exactly three values: The status, the headers, and the body.

一个Rack App是一个响应call方法的Ruby对象(而不是类)。它只接受一个参数,即环境参数,并且返回一个包含HTTP状态码,头部,body的non-frozen数组

一个简单的Rack App:

1
2
3
4
5
6
7
8
9
class HelloWorld
  def call(env)                                    # 环境参数
    [
      200,                                         # 状态码
      {'Content-Type' => 'text/plain'},            # 响应头
      ['Hello World']                              # 响应体
    ]
  end
end

环境参数的定义(The Environment)

The environment must be an unfrozen instance of Hash that includes CGI-like headers. The Rack application is free to modify the environment.

环境参数必须是一个Hash,它包含了一系列CGI风格头部,Rack App可以随意修改这个环境参数。 这里的CGI风格指的传统的CGI接口命名:

  • 使用 大写字母 + 下划线 的命名方式,比如 HTTP_HOSTHTTP_USER_AGENT
  • HTTP_ 开头的变量表示 HTTP 请求头。例如:
    • HTTP 请求头 Host: example.com 会映射为 HTTP_HOST
    • HTTP 请求头 User-Agent: curl/7.68.0 会映射为 HTTP_USER_AGENT

例如:

1
2
3
env = {} 
env = { HTTP_HOST: 'test.com',  HTTP_USER_AGENT: 'curl/7.68.0' } 
env = { host: 'test.com',  user_agent: 'curl/7.68.0' } 

规范里规定了一些除空值情况外必须包含的环境参数

App Server和Web App也可以在环境参数里保存自己的数据,需要注意的是,参数key必须包含一个.,并且具有唯一的前缀。rack.前缀是为rack库保留的,不能随意使用

The Input Stream

The input stream is an IO-like object which contains the raw HTTP POST data.

环境参数中的rack.input存储了POST请求的原始数据,当可用时,它必须以ASCII 8bit二进制编码的格式被打开,并且能响应gets, each, read方法

rack.errorsrack.input类似,用于记录/响应错误。

响应

状态码

必须是大于等于100的整数HTTP状态码

头部

The headers must be a unfrozen Hash. The header keys must be Strings.

Body

The Body is typically an Array of String instances, an enumerable that yields String instances, a Proc instance, or a File-like object.

关于环境参数及响应的具体要求可以参考Rack SPEC。

Rack的用途

那么有了Rack能做什么呢?Rack充当了Ruby App Server和Ruby Web框架两者的胶水,实现关注点分离,开发者可以随时替换任一部分,例如Rails可以使用Unicorn,也可以使用Puma作为App Server,后续有更新的Server或Web框架都可以使用。之所以可以实现这点,就是因为Unicorn,Puma,Rails都是遵循Rack规范的。

每个Rack App都由Handler来调用(一般是Puma这种App Server提供),即由handler提供给我们编写的Rack App上下文(Web请求),由我们编写的Rack App来处理该请求。

最极简的流程就是Puma(Handler)获取到了HTTP请求,把请求交给Rack App处理(Handler调用Rack App的call),Puma(Handler)获取返回结果后响应给用户浏览器。下面的例子就展示了这样的流程。

一个简单的Rack App例子

code.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
require 'rack'
require 'rack/handler/puma'

# 一个简单的 Rack 应用
app = Proc.new do |env|
  # 返回 HTTP 200状态码,头部为Content-Type,Body为"Hello, Puma!"
  [200, { 'Content-Type' => 'text/plain' }, ["Hello, Puma!"]]
end

# 由puma的Rack Handler运行上述 Rack 应用
# run的内部会调用app.call(env)
Rack::Handler::Puma.run app

运行上述代码:ruby code.rb

运行rack app

运行rack app浏览器显示

一个简单的Web App就完成了,Rails和Sinatra这类Web框架都是基于Rack的扩展,Rack的设计对于中间件编写也同样方便,而且符合Rack规范的中间件可以在不同的Web框架中使用。

Rack除了是一个规范,还实现了一个Rack库,提供了一系列方便编写及运行Rack App的工具,包括一些简单的App Server(WEBrick,Thin等)、中间件和命令行工具rackup等。

参考资料: