rack puma rails
背景
2021 年 五一, 突然对 web server 与 rails 之间的关系,
好奇我们的程序是如何接收到请求, 找到我们的业务程序代码, 并返回对应的结果的;
于是, 便写下这篇文章;
说明
文章所涉及到的代码已上传 GitHub rack-puma-rails
文章涉及到的程序版本
文中提到的 web server 是指的服务器软件, 而非计算机.
简述
rack 是一个 ruby web server(例如: puma, unicorn) 和 应用程序(例如: rails)之间的桥梁;
rack
Rack 是什么
Rack 提供了一个最小化的, 模块化, 可适配化的接口给 Ruby 的 web 应用;
通过简单的方式封装 HTTP 请求和响应, Rack 统一并提取了 API 给 web server,
web 框架以及在两者之间的中间件(middleware).
为什么要使用 Rack
在没有 Rack 之前, web server 的实现各有千秋, web 应用对接不同的 web server,
需要一个 server 对应一套逻辑; 缺乏统一的标准;
这些适配不同 server 的工作, 没有太大意义, 人们期望有一个统一的规范, 能够让 web server 和
web 应用可以随意组合, 且只需要简单的配置.
Rack 的出现解决了 web server 和 web 应用之间配置的问题;
复杂的程序需要分层
通过 Rack, 不同的 web server 和 不同的应用框架可以非常简单地集成;
接下来, 我们就来详细看一下 Rack 的协议和中间件.
Rack 协议与中间件
Rack 作为 web server 与应用框架之间的桥梁.
最为重要的就是定义一套清晰的协议(protocol)
.
Rack 协议
我们也可以使用 Rack 的中间件
我们先来看一下rack 协议
- 对于
web server
: 只需要在Rack::Handler
的模块中创建一个实现了run
方法的类就可以了 - 对于
web 框架
: 需要有一个 Ruby 的对象(注意不是类). 这个对象需要实现call
方法.- call 方法
只有一个
参数env
环境 - 返回一个数组, 数组必须返回是三个值. 分别是 HTTP 的 status , headers, 和 body
- call 方法
Rack 的协议脱胎于 python 的pep-0333 Python Web Server Gateway Interface
我们可以看一个小案例, 来看一下具体的情况;
要确保已安装了 rack
和 puma
我们在 config.ru
简单写一个处理请求的 rack 对象
# config.ru rack的默认配置文件
# rack对象, 接收一个env参数, 且要返回一个数组
APP = ->(env) {
[200, {
}, [env.inspect]] }
run ->(env) {
APP.call(env) }
执行rackup
rackup
我们可以看到 rackup
执行之后, puma
也随之启动了!
我们可以在浏览器中看一下效果 http://localhost:9292/hello_world
在浏览器中可以看到打印的env
对象
接下来, 我们看看rackup
到底做了什么, 能够让 puma 运行起来;
$ which rackup
/d/env/ruby/Ruby26-x64/bin/rackup
我们来看一下对应的代码
# gem自动生成的文件, 方便使用, 省去了一些代码
version = ">= 0.a"
load Gem.activate_bin_path('rack', 'rackup', version)
我们在pry
执行一下看看
[1] pry(main)> require 'rubygems'
=> false
[2] pry(main)> version = ">= 0.a"
=> ">= 0.a"
[3] pry(main)> Gem.activate_bin_path('rack', 'rackup', version)
=> "D:/env/ruby/Ruby26-x64/lib/ruby/gems/2.6.0/gems/rack-2.2.3/bin/rackup"
我们找到对应的bin/rackup
的代码
require "rack"
Rack::Server.start
我们在 pry 中看一下具体执行的位置
[1] pry(main)> require "rack"
=> true
[2] pry(main)> show-source Rack::Server.start
From: D:/env/ruby/Ruby26-x64/lib/ruby/gems/2.6.0/gems/rack-2.2.3/lib/rack/server.rb @ line 167:
Owner: #<Class:Rack::Server>
Visibility: public
Number of lines: 3
def self.start(options = nil)
new(options).start
end
我们再看一下代码lib/rack/server.rb
def self.start