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 源代码分析 - 完结篇