Althttpd 是一个简单的网页服务器,自 2004 年以来一直在运行 https://sqlite.ac.cn/ 网站。 Althttpd 追求简单、安全和低资源使用。
截至 2024 年,sqlite.org 的 althttpd 实例每天处理超过 500,000 个 HTTP 请求(大约每秒 5 或 6 个),每天提供约 200GB 的内容(大约每秒 18 兆位),在一台每月 40 美元的 Linode 上。 这台机器上的平均负载通常保持在 0.5 左右。 大约 19% 的 HTTP 请求是针对各种 Fossil 源代码存储库的 CGI。
设计理念
Althttpd 通常从 xinetd 或 systemd 或类似程序启动。 每个传入连接都会启动一个单独的进程,该进程完全专注于服务该连接。 单个 althttpd 进程将通过相同连接处理一个或多个 HTTP 请求。 连接关闭后,althttpd 进程退出。
Althttpd 也可以独立运行。 Althttpd 本身监听端口 80 上的传入 HTTP 请求(或 443 上的传入 HTTPS 请求),然后派生自身的一个副本以处理每个传入连接。 每个连接仍然使用单独的进程处理。 唯一的区别是连接处理程序进程现在由主 althttpd 实例启动,而不是由 xinetd 或 systemd 启动。
Althttpd 没有配置文件。 所有配置都是使用几个命令行参数处理的。 这有助于保持配置简单,并减轻了对通过配置错误的网页服务器引入安全漏洞的担忧。
由于每个 althttpd 进程只需要服务一个连接,因此 althttpd 是单线程的。 此外,每个进程只在单个连接的持续时间内存在,这意味着 althttpd 不需要过多地担心内存泄漏。 这些设计因素有助于保持 althttpd 源代码简单,这有助于安全审计和分析。
对于服务 TLS 连接,有两种选择
althttpd 可以通过定义
ENABLE_TLS
宏并链接到-lssl -lcrypto
来构建,然后使用--cert fullchain.pem
和--pkey privkey.pem
标志启动。althttpd 可以通过外部连接服务(如 stunnel4)启动,将
-https 1
标志传递给 althttpd,告诉它通过该服务“间接”以 HTTPS 模式运行。
首选第一个选项(使用内置 TLS)。
源代码
althttpd 的完整源代码包含在 单个 C 代码文件 中,没有标准 C 库以外的依赖关系,如果选择 ENABLE_TLS 选项,则加上 OpenSSL。 此外,构建过程需要 VERSION.h
,它是由 包含的 Makefile 生成的。
althttpd 源代码有大量注释并且可以访问。 它应该很容易定制以满足特殊需求。
要构建和安装 althttpd,请选择 Makefile 开头列出的构建目标之一,然后运行
make THAT_TARGET
如果没有提供目标,它将假设 libssl 可用,并将尝试构建名为 althttpd
和 althttpsd
的 HTTP-only 和 HTTPS-aware 二进制文件,分别。 要安装它们,只需将它们移动到您选择的目录即可。
相应的构建规则很简单,因此可以轻松地移植到其他构建基础设施中。
SQLite 网站 使用 静态构建,因此无需在服务器上安装 OpenSSL。
安装
在您的系统上运行 Althttpd 的方法有很多。 以下是几种变体
以上不是详尽的列表。 基本思想是,每次在您的网页服务器端口(通常是端口 80 或 443)上出现新的套接字连接时,您都会启动一个新的 althttpd 进程副本来处理该连接。
完整的安装规范,包括所有命令行选项和配置选项的列表,都在 althttpd.c 源代码文件 中的一个大型头注释中。
托管多个域
Althttpd 使用每个 HTTP 请求的 HTTP_HOST 标头来确定应该从哪里提供内容。 HTTP_HOST 标头是导致网页浏览器发出 HTTP 请求的 URL 的域名。 Althttpd 会复制此名称,将所有 ASCII 字母字符转换为小写,将所有其他字符更改为“_”,然后附加“.website”。 例如,如果 HTTP_HOST 为“www.SQLite.org”,则转换后的名称将为“www_sqlite_org.website”。 然后,Althttpd 在其“-root”目录中查找具有该名称的目录,并从该目录中提供内容。 如果没有找到这样的目录,或者 HTTP 请求省略了 HTTP_HOST 标头,则使用“default.website”目录。 因此,您只需拥有多个 *.website 文件夹,就可以从同一台机器上提供多个网站。 在提供 SQLite 网站的 Linode 上,有(至少有)42 个 *.website 文件夹和符号链接,包括
- www_sqlite_org.website
- default.website ← 指向前面的符号链接
- fossil_scm_org.website
- pikchr_org.website
- www_cvstrac_org.website
- androwish_org.website
网站内容
在每个 *.website 文件夹中,普通文件被作为静态内容提供。 可执行文件作为 CGI 运行。 Althttpd 通常不会提供名称以“.”或“-”开头的文件。 这是一项安全功能 - 请参阅下文。
GZip 内容压缩
Althttpd 对服务器端内容压缩提供基本支持,这通常可以将文件传输成本降低一半以上。 而不是在 althttpd 中添加对压缩库的依赖,它依赖于网站开发人员以压缩和未压缩两种形式提供内容。
当提供文件时,如果客户端表示支持 gzip 压缩,并且找到了具有相同名称加上 .gz
扩展名的文件,则将文件的 gzip 版本提供给客户端,并使用响应标头指示它是 gzip 的。 对用户来说,它看起来像是以压缩形式提供的原始请求文件。 然而,在幕后,服务的是一个不同的文件。
请注意,此功能仅适用于静态文件,不适用于 CGI。
安全功能
为了防御恶意行为,对 althttpd 将提供服务的文件的名称有限制。 在请求 URI 中,除字母数字和“,-./:~”以外的所有字符都被转换为单个“_”。 此外,如果请求 URI 的任何路径元素以“.”或“-”开头,则 althttpd 将始终返回 404 未找到错误。 因此,可以安全地将辅助文件(例如,数据库或 CGI 使用的其他内容)放在文档层次结构中,只要文件名以“.”或“-”开头即可。
当 althttpd 返回 404 时,它会尝试确定请求是否恶意,如果它认为请求恶意,它可以选择 暂时阻止客户端的 IP。
例外:虽然 althttpd 通常对任何路径元素以“.”开头的请求返回 404 未找到,但它确实允许 URI 以“/.well-known/”开头的请求。 “/.well-known/”下的文件或目录名称允许以“.”或“-”开头(但不允许以“..”开头)。 此例外是必要的,以允许 LetsEncrypt 验证网站的所有权。
基本身份验证
如果名为“-auth”的文件出现在内容层次结构中的任何位置,则访问该目录中的文件需要 HTTP 基本身份验证,如“-auth”文件的内容所定义。“-auth”文件仅适用于给定的目录,不递归地应用于子目录。“-auth”文件是纯文本和面向行的。 空行和以“#”开头的行被忽略。 其他行具有以下含义
http-redirect
如果存在 http-redirect 行,则所有 HTTP 请求将重定向到 HTTPS 请求。“-auth”文件按顺序读取和处理,因此“http-redirect”行下面的行永远不会被看到或处理 http 请求。
https-only
如果存在 https-only 行,则只允许 HTTPS 请求。 任何 HTTP 请求都会导致 404 未找到错误。 https-only 行通常出现在 http-redirect 行之后。
realm NAME
此形式的单行建立基本身份验证的“领域”。 网页浏览器通常会将领域名称显示为询问用户名和密码的对话框上的标题。
user NAME LOGIN:PASSWORD
有多个用户行,每行对应一个有效的用户。 LOGIN:PASSWORD 参数定义了用户必须键入才能访问网站的用户名和密码。 密码是明文的 - HTTP 基本身份验证不是最安全的身份验证机制。 登录成功后,NAME 将存储在 REMOTE_USER 环境变量中,以便 CGI 脚本可以访问它。 NAME 和 LOGIN 通常相同,但可以不同。
anyone
如果遇到“anyone”行,则表示允许任何请求,即使没有提供用户名和密码。 此行与“http-redirect”结合使用很有用,可以使所有普通的 HTTP 请求重定向到 HTTPS,而无需登录凭据。
基本身份验证示例
http://www.sqlite.org/ 网站在顶级目录中包含一个“-auth”文件,如下所示
http-redirect anyone
该 -auth 文件导致所有 HTTP 请求重定向到 HTTPS,而无需任何进一步的登录。(试试看:访问 https://sqlite.ac.cn/ 并验证您是否被重定向到 https://sqlite.ac.cn/。)
在 https://fossil-scm.org/private/ 处有一个“-auth”文件,它看起来像这样
realm Access To All Fossil Repositories http-redirect user drh drh:xxxxxxxxxxxxxxxx
当然,除了密码不是“x”字符的一行之外。 这演示了 -auth 文件的典型用法。 授予单个用户对“private”子目录中的内容的访问权限,前提是用户通过 HTTPS 而不是 HTTP 进入。“http-redirect”行强烈建议用于所有基本身份验证,因为密码包含在请求标头中,如果请求通过 HTTP 发送,则可能会被坏人拦截和窃取。
日志文件
如果在 althttpd 命令行中给出 -logfile 选项,则会为每个 HTTP 请求将一行附加到命名文件中。 日志文件采用 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
-logfile 选项中的文件名可能包含 strftime() 扩展的时间相关字符。 因此,为了让每个天都使用一个新的日志文件,您可以使用类似以下内容
-logfile /var/logs/althttpd/httplog-%Y%m%d.csv
客户端 IP 阻止
如果在 althttpd 中包含了 --ipshun DIRECTORY
选项,并且 DIRECTORY 是一个绝对路径名(以“/”开头)并且可以在 chroot 监狱内访问,并且如果客户端的 IP 地址以文件形式出现在该目录中,那么 althttpd 可能会返回 503 服务不可用,而不是处理请求。
如果文件大小为零字节,则始终返回 503。因此,您可以“touch”一个包含 IP 地址名称的文件,以永久禁止该客户端。
如果文件大小为 N 字节,则如果文件的修改时间早于 300*N 秒,则返回 503。换句话说,客户端将被禁止五分钟,每个字节对应五分钟的禁止时间。
如果 althttpd 收到一个本来会返回 404 未找到的请求,并且在检查 REQUEST_URI 时发现该请求可疑,则会自动创建禁止文件。例如,任何包含 /../ 的请求都被认为是黑客尝试。还有其他常见的漏洞探测也被检查。随着经验的积累,这个漏洞探测列表可能会增长。
禁止文件在 5 分钟/字节后会自动被删除。
禁止文件最初的大小为 1 字节。但是,如果一个禁止过期,然后在 5 分钟/字节的阻塞文件大小之前又收到一个新请求,那么该文件会增加一个字节,并且修改时间会重置。
可以在构建时通过传递 -DBANISH_TIME=N
来配置 5 分钟的禁止时间,其中 N 是秒数,默认值为 300。