2015 年春节期间,通读了 Puma 的源代码。阅读 Puma 的源码是一个愉快的技术探索过程,这里把阅读所得分享出来。
Puma 是一个面向 ruby 语言的并发的 Web 服务器(a modern, concurrent web server for ruby)。它的官方网站是puma.io.
一个标准的 Web 服务器遵循 HTTP 协议,接受 HTTP Request 请求,返回 HTTP Response 的响应结果。请求和响应都是字符串流的格式。如图 1 所示:

使用 curl 命令可以很方便的测试和学习 HTTP 协议。比如执行下面的 curl 命令,可以看到 http 的请求和响应。
curl  -v http://z.cn
Http 的请求由 curl 发出,curl 此时就是 Web Client。
GET / HTTP/1.1
User-Agent: curl/7.37.1
Host: z.cn
Accept: */*
然后 z.cn 的 web 服务器收到 HTTP Request 请求,并返回 HTTP Response 的响应结果,如下:
HTTP/1.1 301 Moved Permanently
Date: Thu, 26 Feb 2015 07:25:05 GMT
Location: http://www.amazon.cn/ref=z_cn?tag=zcn0e-23
Content-Length: 250
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://www.amazon.cn/ref=z_cn?tag=zcn0e-23">here</a>.</p>
</body></html>
Web 服务器根据 Http 请求的路径和其他头部信息来决定如何输出 Http 的响应结果。Web 服务器有三种方式处理 Http 请求:
对于第三种的动态请求,又有很多接口规范,比如 cgi/fastcgi, servlet, rack 等。其中 cgi/fastcgi 是语言无关的,servlet 是特定于 java 语言的,而 rack 是特定于 ruby 语言的。
Rack 的官方网站是rack.github.io。支持 Rack 的 Web 服务器的架构如图 2 所示:

Rack 应用的输入是一个表示环境的 hash,输出是一个数组包括三个元素:
Rack 服务器和 Rack 应用之间通过 ruby 对象交互,Rack 服务器直接调用 Rack 应用;而 Web 服务器和 Web 客户端之间则是通过字符串流交互。
Puma 是一个支持 Ruby/Rack applications 的 Web 服务器。其它支持 Rack 规范的 Web 服务器还有 Unicorn/Thin/Passenger 等。
Puma 是一个典型的 Rack 服务器。其总体目录结构与关键源代码文件如下:
├── bin   可执行脚本
│   ├── puma      启动脚本
│   ├── puma-wild
│   └── pumactl   控制脚本
├── docs 文档
├── examples  一些例子文件
├── ext   ruby的原生扩展,实现快速解析HTTP的头部
│   └── puma_http11  
├── lib
│   ├── puma
│   │   ├── app
│   │   │   └── status.rb     响应pumactl的控制请求
│   │   ├── binder.rb         启动并绑定Tcp/Unix端口
│   │   ├── cli.rb            Puma的命令行接口,解析命令行参数
│   │   ├── client.rb         代表Http请求的客户端
│   │   ├── cluster.rb            Puma的集群模式
│   │   ├── configuration.rb  Puma的配置参数
│   │   ├── control_cli.rb     命令行控制脚本的实现代码
│   │   ├── daemon_ext.rb      实现puma进程的daemon化
│   │   ├── events.rb          服务器的事件支持
│   │   ├── null_io.rb            当request没有body时rack.input的值
│   │   ├── rack_default.rb   指定缺省的rack handler
│   │   ├── rack_patch.rb     Patch CommonLogger to use after_reply
│   │   ├── reactor.rb        所有需要监听新数据的client链接,放到reactor的@sockets队列中
│   │   ├── runner.rb     启动Puma Server的基类,Single/Cluster继承它
│   │   ├── server.rb     The HTTP Server itself. Serves out a single Rack app.
│   │   ├── single.rb     Puma的单进程模式
│   │   ├── thread_pool.rb 线程池,其中是所有可执行的worker
│   ├── puma.rb
│   └── rack
│       └── handler
│           └── puma.rb       缺省的puma rack handler实现
├── test  测试代码
└── tools 进程监控脚本:init.d/upstart
    ├── jungle
    │   ├── init.d
    │   └── upstart
本系列后续部分将详细分析 Puma 如何启动、如何监听 tcp 连接,如何解析 http header,如何执行 rack app,如何响应控制命令,如何处理文件上传,如何实现集群模式,如何使用 reactor 模型处理 io 等。
Puma 中关于 Jruby 和 SSL 支持的部分本文档不会涉及。因为笔者自己都是在 MRI ruby 上跑 puma 的,没有用过 jruby 跑 puma,而且 SSL 都是交给 web 接入层比如 nginx 处理的,所以 puma 中这两个功能点的代码就不关心了。
Puma 源代码分析 - 启动流程 Puma 源代码分析 - 单进程模式 Puma 源代码分析 - 集群模式 Puma 源代码分析 - IO 处理 Puma 源代码分析 - http 协议解析 Puma 源代码分析 - 完结篇