Althttpd
是一款 Web Server
, 和 nginx/apache
相似, 相比之下, 该款更轻量化, 功能也较为简洁, sqlite.org
在用. 经朋友推荐学习, 不得不提另一款 Tinyhttpd, 更简洁的设计和模型, 只是拿来学习使用, 似乎并未用于实际业务. 由于实际文件只有三个, 我这里就做全量解析, Marddown
文件主要做翻译工作, Makefile
文件主要做命令解析(可能会在翻译那部分一起做), 源码文件当然挨个分析.
althttpd.md
Althttpd
是一个简单的 web
服务器, 自从2004年就在 sqlite.org
站点投入使用, 主要追求轻量化, 安全性和低资源使用.
截至2018年, 部署在每月40刀的 Linode VPS上 Althttpd
支撑的 sqlite.org
平均每天响应50w的请求(每秒大约5到6个), 分发将近50G内容每天(大约4.6兆每秒), 这台机器上的负载基本保持在0.1或0.2. 大约19%的请求是访问不同的源码仓库 [Fossil](https://fossil-scm.org/)
.
设计哲学
Althttpd
通常从 xinetd 或 stunnel4 启动, 一个为服务每个连接的隔离进程, 一个将处理通过同一连接的一个或多个请求的独立 Althttpd
进程. 当连接关闭, Althttpd
进程就结束了.
Althttpd
也可以独立运行, 自身监听80端口为了即将达到的请求, 然后创建一个自己的子进程去处理每个已到达的连接, 当然每个处理的连接是在相互独立的进程. 唯一的区别就是每个处理连接的子进程是从 Althttpd
主进程启动的, 而不是 xinetd
或 stunnel4
.
Althttpd
没有配置文件, 所有的配置都从命令参数中获取, 有利于保持简洁的配置和降低通过异常配置站点带来的弱安全性的担忧.
Althttpd
没有自身的 TLS
连接处理. 对于 HTTPS
连接, Althttpd
通过响应 stunnel4
去处理 TLS
协议, 解密和加密.
由于每个 Althttpd
进程只需要服务于一个独立的连接, 所以它也是个独立线程. 而且, 每个进程只存活于一个独立连接的生命周期中, 这意味着不需要担心过多的内存泄漏问题. 这些促进安全审核和分析的设计因素有利于保持 Althttpd
源码的简洁.
源码
有关 Althttpd
完整的源码都在一个没有任何非标准外部库依赖的独立文件, 文件名为 althttpd.c
. 为了构建和安装 Althttpd
, 运行 gcc -Os -o /usr/bin/althttpd althttpd.c
. 源码有非常多的注释, 易于理解, 所以也容易做一些自定义特殊化需求的适配.
xinetd
配置
下面展示的就是 sqlite.org
中使用的支持 IPv4/IPv6
非加密请求的 Althttpd
完整配置. 你可以使用这作为模版创建自己的
安装设置.
service http
{
port = 80
flags = IPv4
socket_type = stream
wait = no
user = root
server = /usr/bin/althttpd
server_args = -logfile /logs/http.log -root /home/www -user www-data
bind = 45.33.6.223
}
service http
{
port = 80
flags = REUSE IPv6
bind = 2600:3c00::f03c:91ff:fe96:b959
socket_type = stream
wait = no
user = root
server = /usr/bin/althttpd
server_args = -logfile /logs/http.log -root /home/www -user www-data
}
这个关键在于每个通过80端口进来的 TCP/IP
连接启动 /usr/bin/althttpd
的副本的附加参数都会一起作为这个 web
服务的配置.
需要注意的是, Althttpd
是使用超级用户运行的, 这不是必需的, 但如果是这样配置的, 那么, Althttpd
将自身关于站点根目录层次的权限纳入控制, 删除之前监听读取任意内容的超级用户权限. 通过 -user
选项告诉 Althttpd
访问用户将变成 www-data
.
-root
选项告诉 Althttpd
寻找文档结构的位置. 在 sqlite.org
这个例子中, 所有内容都是从 /home/www
提供的. 位于文档结构的顶端都是一些以 .website
结尾的目录, 每个这样的目录都是一个独立的站点. 这种目录是基于到达请求的域名和参数被选择的. 这儿有一部分 sqlite.org
的目录列表:
3dcanvas_tcl_lang_org.website
3dcanvas_tcl_tk.website
androwish_org.website
canvas3d_tcl_lang_org.website
canvas3d_tcl_tk.website
cvstrac_org.website
default.website
fossil_scm_com.website
fossil_scm_hwaci_com.website
fossil_scm_org.website
system_data_sqlite_org.website
wapp_tcl_lang_org.website
wapp_tcl_tk.website
www2_alt_mail_net.website
www_androwish_org.website
www_cvstrac_org.website
www_fossil_scm_com.website
www_fossil_scm_org.website
www_sqlite_org.website
对于每个请求, Althttpd
截取请求中 host
参数中的文本, 转化成小写, 将非 ASCII
字母数字的字符转化成 _
. 这个结果将确定使用的子目录. 如果没有匹配项, 默认使用 default.website
目录.
举个例子, 如果 host
参数为 www.SQLite.org
, 将会转成 www_sqlite_org.website
, 这就是提供内容的目录. 如果 host
参数为 fossil-scm.org
, 将会转成 fossil_scm_org.website
. 大多时候, 两个或多个名字指向同一站点. 比如 fossil-scm.org
/ www.fossil-scm.org
/ fossil-scm.com
/ www.fossil-scm.com
都是同样的站点. 在这种情况下, 通常只有一个目录是真实目录, 其他的都是链接. 在仅有一个独立站点的最小安装情况下, 有一个 default.website
子目录就足够了.
在 *.website
目录内, 文件是通过请求资源标识定位( URI: Uniform Resource Identifier
)提供服务. 这些文件都被标记为通过 CGI
运行的可执行文件. 以 .scgi
结尾的不可执行文件在遇到 SCGI hostname port
形式的请求将提供一个 SCGI
请求给 hostname port
. 所有其他不可执行文件都是原样分发的.
如果请求 URI
指定 *.website
内目录名称, Althttpd
将追加 /home
, /index.html
, 和 /index.cgi
, 以这样的顺序, 查找匹配项. 如果请求 URI
的前缀与可执行文件名称配置, 那个文件将以 CGI
方式运行. 对于原样内容, 请求 URI
必须匹配正确的文件名.
对于原样内容分发, 将从编译到 Althttpd
的一个表中根据文件名扩展推断出 MIME
类型.
使用 stunnel4
配置 https
Althttpd
本身没有任何加密, 所以为了使用 Althttpd
的站点配置加密, 推荐使用 stunnel4
. 在 sqlite.org
站点, 在 stunnel.conf
文件中有关的配置下如下:
cert = /etc/letsencrypt/live/sqlite.org/fullchain.pem
key = /etc/letsencrypt/live/sqlite.org/privkey.pem
[https]
accept = :::443
TIMEOUTclose = 0
exec = /usr/bin/althttpd
execargs = /usr/bin/althttpd -logfile /logs/http.log -root /home/www -user www-data -https 1
这个配置和 xinetd
很像, 一个关键差别是 -https 1
选项, 这个告诉 Althttpd
连接是加密的. 这一点非常重要, 以便 Althttpd
知道如何设置
CGI
的 https
环境变量. xinetd
和 stunnel4
同时配置运行 Althttpd
也是可以的. 实际上, 这正是 sqlite.org
的运行方式, 通过 xinetd
访问 http://sqlite.org/
, 通过 stunnel4
访问 https://sqlite.org/
.
独立运行
在作者的工作台桌面, 他的 home
目录下有个名为 ~/www/default.website
包含了一些文件和 CGI
脚本的子目录, Althttpd
可通过以下命令提供该内容 althttpd -root ~/www -port 8080
. -port 8080
是配置 Althttpd
在独立运行模式中监听的端口为 8080
. 作者的 Althttpd
独立运行模式只用于测试. 由于 Althttpd
本身不支持 TLS
访问, 在正式环境中更推荐使用 stunnel4
.
安全功能
为了防止恶意访问, Althttpd
提供的文件命名有一些严格的限制. 在请求 URI
中, 所有的非 ASCII
字母数字的编码和 ,-./:~
都会被转成 一个单字符 _
. 此外, 在请求 URI
中任意以 .
或 -
开始的路径元素, Althttpd
总会触发 404
错误. 所以在文档结构中放一些以 .
或 -
开始命名的辅助文件也是安全的.
一个异常: 尽管 Althttpd
对于路径元素中以 .
开始命名的请求总是 404
, 但它是允许 /.well-known/
开头的请求访问, 在 /.well-known/
目录下的目录或文件是允许以 .
或 -
开头(但是禁止 ..
). 这个异常主要是为了允许 LetsEncrypt
验证站点所有者关系.
基本认证
如果一个命名为 -auth
的文件出现在内容层次中, 那么所有同级和处于更低级的文件都需要 HTTP basic authentication 验证, 由 -auth
文件的内容定义. 这个 -auth
是面向行的纯文本的文件, 空行和以 #
开头的行都会被忽略, 其余行如下:
http-redirect
http-redirect
行, 如果存在, 将会导致所有请求都被重定向成https
请求.-auth
是顺序读取和执行的, 所以在这行之下的内容对于请求将永不可见或执行.https-only
https-only
行, 如果存在, 表示只允许https
请求访问, 任一http
请求都会触发404
错误, 这行通常出现在http-redirect
行之后.realm
一个为了建立realm
基础认证表单的独立行, 浏览器通常将realm
名称展示为一个询问名称和密码的对话框的名称.user
NAME LOGIN:PASSWORD
这是批量行, 一行时每个有效的用户,LOGIN:PASSWORD
参数定义了用户必须输入才能访问站点的名称和密码, 密码是纯文本,HTTP Basic Authentication
也不是最安全的认证策略. 在成功登陆后, 名称会被保存在REMOTE_USER
环境变量中, 所以它可以通过CGI
脚本.NAME
和PASSWORD
通常是一样的, 但最好设置成不同的.anyone
如果anyone
一旦出现, 它就意味着任何请求都是被允许的, 即使没有提供用户和密码. 这个和http-redirect
结合使用对于从HTTP
重定向到不需要登陆标识的HTTPS
请求是非常有用的.
认证实例
sqlite.org 站点在顶级目录中包含了一个 -auth
文件:
http-redirect
anyone
这个文件导致所有 HTTP
请求被定向到 https
请求, 不需要任何登陆.
fossil-scm.org 配置的 -auth
文件如下:
realm Access To All Fossil Repositories
http-redirect
user drh drh:xxxxxxxxxxxxxxxx
当然, 这个密码不是一串 x
字符, 对于 -auth
, 这种显示是很常见的. 访问控制对于独立用户授权是在 private
子目录下的内容, 这主要是提供用于 https
访问. 对于所有需要将密码放在请求头的基础认证, 强烈推荐设置 http-redirect
, 通过 http
传输凭证是会被截取的.
日志文件
如果 -logfile
选项在 Althttpd
命令行中给出, 那么, 每个请求都会新增一个独立行到指定文件. 这个日志文件的内容是由逗号间隔的值或RFC4180标准指定的 CSV
格式.
在源码中有些解释每个字段在输出行意思的注释. 实际上, CSV
形式的日志文件很容易导入 sqlite
分析, 使用像下面这种脚本:
CREATE TABLE log(
date TEXT, /* Timestamp */
ip TEXT, /* Source IP address */
url TEXT, /* Request URI */
ref TEXT, /* Referer */
code INT, /* Result code. ex: 200, 404 */
nIn INT, /* Bytes in request */
nOut INT, /* Bytes in reply */
t1 INT, t2 INT, /* Process time (user, system) milliseconds */
t3 INT, t4 INT, /* CGI script time (user, system) milliseconds */
t5 INT, /* Wall-clock time, milliseconds */
nreq INT, /* Sequence number of this request */
agent TEXT, /* User agent */
user TEXT, /* Remote user */
n INT, /* Bytes of url that are in SCRIPT_NAME */
lineno INT /* Source code line that generated log entry */
);
.mode csv
.import httplog.csv log
在 -option
选项中配置的文件名可能包含一些被 strftime() 填充的时间字符. 所以, 为了创建每日日志文件, 你可能需要这样使用 -logfile /var/logs/althttpd/httplog-%Y%m%d.csv
.
Makefile
有关 Makefile
的相关知识, 尚不太熟悉, 就不在这里做介绍, 直接看内容中的命令:
althttpd: althttpd.c
cc -Os -Wall -Wextra -o althttpd althttpd.c
clean:
rm -f althttpd
althttpd
创建可执行文件-Os
去掉了那些会导致最终可执行程序增大的优化;-Wall
显示所有警告信息;-Wextra
显示附加警告信息;-o
指定生成的输出文件.
clean
清除althttpd
可执行文件(当前目录).
在这部分, 我们了解了怎么去使用, 下面, 将去了解它的具体实现.