Show HN: 一个纯ARM64汇编编写的Web服务器,现可在Linux上运行,并可无理由支持CGI
摘要
ymawky 是一个完全用ARM64汇编编写的Web服务器,支持CGI、静态文件和多种HTTP方法,现已可在Linux上运行。
查看缓存全文
缓存时间: 2026/06/23 07:41
imtomt/ymawky 来源:https://github.com/imtomt/ymawky
ymawky —— ARM 汇编实现的网页服务器
ymawky(发音 yuh maw kee)是一个完全用 ARM64 汇编编写的网页服务器。ymawky 是一个仅使用系统调用、无 libc、基于每连接一进程(fork-per-connection)的网页服务器,手动编写支持 macOS 和 Linux(参见 Linux 分支 https://github.com/imtomt/ymawky/tree/linux)。
构建
需要 Xcode 命令行工具。使用 xcode-select --install 安装。ymawky 仅在 Apple Silicon(arm64)上运行。运行 make 构建。确保在 ymawky 可执行文件旁边有一个 www/ 目录,这是 ymawky 搜索文件的文档根目录。空文件名 GET / 会搜索 www/index.html,因此最好也准备一个 index.html。当客户端请求出错(例如 404)时,ymawky 会尝试提供静态错误页面。这些页面位于 err/(code).html,所以请确保 err/ 与 ymawky 和 www/ 并列存在。参见配置修改默认文件和文档根目录。
运行
./ymawky在127.0.0.1:8080上启动网页服务器。./ymawky [端口]在127.0.0.1:[端口]上启动网页服务器。./ymawky [除0-9外的任意字符]以调试模式在 127.0.0.1:8080 上启动网页服务器。调试模式禁用进程 fork,仅处理一个请求。(我需要这样做,因为lldb不允许我调试子进程,唉。)
遗憾的是,虽然支持自定义端口,但不支持自定义地址。目前 ymawky 只能在 127.0.0.1 上运行。这完全是因为我还没有实现该功能——如果你愿意将其视为安全特性,那也可以认为是故意的。
要查看 ymawky 的运行效果,先用 ./ymawky [端口] 启动。然后打开你喜欢的网页浏览器(或使用 curl),访问 127.0.0.1:8080/ 或 127.0.0.1:8080/pretty/index.html。感受汇编的温暖吧。
它能做什么?
ymawky 是一个 静态文件 动态网页服务器。它不确实支持服务器端代码来动态生成内容,以及更高级的 URL 解析(如 /search?query=term),通过 CGI 脚本实现。
- 支持的 HTTP 方法:
- GET
- PUT
- DELETE
- OPTIONS
- HEAD
- POST(通过 CGI 脚本)
- 基本防护 slowloris 式拒绝服务攻击
- 解码 % 十六进制编码,例如
%20解码为空格,%61解码为a - 智能路径遍历检测与防御。阻止
..遍历路径,同时不禁止多个连续句点(当它们是文件名的一部分时):GET /../../../etc/passwd->403 ForbiddenGET /ohwell...txt->200 OKGET /../src/ymawky.S->403 ForbiddenGET /hehe..txt->200 OK
- 自动在请求的文件前加上
www/。GET /index.html会获取www/index.html - 空
GET /请求默认处理为GET www/index.html PUT请求支持上传最大 1GiB 的文件(可通过配置调整更大文件)PUT是原子操作,先写入临时文件再重命名,允许多个并发PUT请求而不留下部分写入的文件- 在
PUT请求中解析和验证Content-Length: - MIME 类型检测,在响应头中给出
Content-Type及对应 MIME 类型 - 接受 GET 请求中的
Range: bytes=范围,支持完整范围bytes=X-N、后缀范围bytes=-N和开放范围bytes=X-。视频拖动(scrubbing)支持良好 - 基本 HTTP 版本解析。请求需指定
HTTP/1.1或HTTP/1.0,如果请求HTTP/1.1,则请求头中必须包含Host:字段。目前 ymawky 不对 Host 做任何操作,但根据 RFC 9112 第 3.2 节,该头字段必须发送 - 为错误码(如 404 或 500)提供自定义 HTML 页面。参见
err/目录中的示例 - 如果请求的资源是目录,则列出该目录中的所有文件和子目录。注意这不包括
www/(或你的文档根目录):GET /在没有给出文件时总是搜索 index.html - CGI 脚本支持。所有 CGI 脚本必须位于
CGI_DIR(在config.S中定义,默认为(docroot)/cgi-bin/)内。 - 查询字符串(
/cgi-bin/ratbook?q=do+you+like+rats&a=yes!)支持 - ymawky 解析 CGI 脚本的头部并将其转发到客户端响应
- 强制执行最低限度的 CGI 合规性:所有 CGI 脚本必须以头部开始响应,如果响应有正文,头部必须包含 Content-Length 字段。
- HTTP 响应码由 CGI 脚本的 Status: 头部字段决定,因此脚本可以发送自己的 404 或 500 等。如果未提供 Status,则默认使用
200 OK。
“安全性”
这是一个完全用手工编写的 ARM64 汇编网页服务器,是一个有趣的个人项目。它可能包含许多我未意识到的漏洞。不过,我已尽力使其更安全。以下是 ymawky 采取的一些安全措施。
- 拒绝路径长度 >= PATH_MAX(4096 字节)的请求
- 拒绝任何包含路径遍历的路径 –
/../.. - 拒绝任何路径长度小于 16 字节的请求
- 限制在
www/内。任何请求的路径都会在前面加上www/ - 使用
O_NOFOLLOW_ANY拒绝包含符号链接的路径 - PUT 写入临时文件
www/.ymawky_tmp_。成功接收整个文件后,该临时文件会被重命名为请求的文件名。这可以防止部分或损坏的 PUT 请求覆盖现有文件 - 拒绝任何以
www/.ymawky_tmp_开头的路径的请求。这可以防止他人GET临时文件,或发送PUT /.ymawky_tmp_4533等请求 - 必须在 10 秒内接收数据。如果速度较慢,连接将关闭。如果整个头部在 10 秒内未接收完毕,连接也将关闭。这用于防止 slowloris 式攻击
- CGI 脚本支持仅限于(可配置的)
cgi-bin/目录。任何通过cgi-bin发送的请求都会以相同方式处理,因此你不能将文件 PUT 到cgi-bin内部的目标位置,它只会作为 CGI 脚本执行(如果存在) - 请注意,CGI 脚本支持目前是实验性的,并且没有 PUT 那样严格的超时设置。CGI 脚本理论上可以永远循环、永远读取输入、永远挂起,ymawky 不会杀死该脚本。你不应该在真实服务器上运行 ymawky(笑),但如果必须运行,请删除
www/cgi-bin/目录,并禁止 CGI 支持。
CGI 脚本支持
CGI(通用网关接口)是一种接口规范,使网页服务器能够执行外部程序来处理 HTTP 用户请求(感谢维基百科)。简单来说,CGI 脚本是服务器上的可执行脚本。脚本运行并根据用户请求生成动态内容,而不是提供单个静态文件。
ymawky 支持查询字符串:URL 中 ? 之后的所有内容。因此,如果你有一个名为 logbook 的 CGI 脚本,你可以发送请求 /cgi-bin/logbook?q=nice+job,ymawky 将使用环境变量 QUERY_STRING 设置为 q=nice+job 来执行 logbook。
CGI 限制
ymawky 中的 CGI 支持是有限的。ymawky 不支持 PATH_INFO;在类似 /blog/2024/01 的请求中,blog 可以是可执行路径,/2024/01 通过 PATH_INFO 环境变量传递。ymawky 只是将每个路径视为字面路径,它会查找文件 /blog/2024/01。
CGI 安全说明
CGI 脚本自身可能存在漏洞,因为它们是独立的完整程序。它们需要自行进行错误处理、输入解析等。ymawky 所做的很简单(可以说是):找到可执行文件,设置一些环境变量,fork,执行 CGI 脚本,并在用户和 CGI 脚本之间写入 HTTP 内容。
HTTP 状态码
ymawky 当前支持并可返回以下状态码:
200 OK201 Created204 No Content206 Partial Content400 Bad Request403 Forbidden404 Not Found408 Request Timeout409 Conflict411 Length Required413 Content Too Large414 URI Too Long416 Range Not Satisfiable418 I'm a teapot431 Request Header Fields Too Large500 Internal Server Error501 Not Implemented502 Bad Gateway503 Service Unavailable505 HTTP Version Not Supported507 Insufficient Storage
自定义 HTML 页面将与错误码(400+)一起提供。这些 HTML 文件位于 err/(code).html。你可以使用 build_err_pages.sh 为每个状态码创建页面,并自由修改文本。编辑 build_err_pages.sh 的源代码以修改每页的文本,并修改 err/template.html 以修改基础模板。
在 err/template.html 中:
{{CODE}}- HTTP 状态码:例如 404{{TITLE}}- 标题文本:例如 “Not Found”{{MSG}}- 自定义消息:例如 “the rats ate this page”
MIME 类型
MIME 类型通过分析文件扩展名来检测。以下 MIME 类型已被识别。
网页相关文件:
.html->text/html; charset=utf-8.htm->text/html; charset=utf-8.css->text/css; charset=utf-8.csv->text/csv; charset=utf-8.xml->text/xml; charset=utf-8.js->text/javascript; charset=utf-8.json->application/json.wasm->application/wasm.mjs->text/javascript; charset=utf-8.map->application/json
图片文件:
.png->image/png.jpg->image/jpeg.jpeg->image/jpeg.gif->image/gif.svg->image/svg+xml.ico->image/x-icon.webp->image/webp.avif->image/avif.bmp->image/bmp.tiff->image/tiff.apng->image/apng
字体文件:
.woff->font/woff.woff2->font/woff2.ttf->font/ttf.otf->font/otf
文档文件:
.txt->text/plain; charset=utf-8.pdf->application/pdf.doc->application/msword.docx->application/vnd.openxmlformats-officedocument.wordprocessingml.document.epub->application/epub+zip.rtf->application/rtf
视频文件:
.mp4->video/mp4.webm->video/webm.mkv->video/x-matroska.avi->video/x-msvideo.mov->video/quicktime
音频文件:
.mp3->audio/mpeg.ogg->audio/ogg.wav->audio/wav.flac->audio/flac.aac->audio/aac.m4a->audio/mp4.opus->audio/opus
归档文件:
.zip->application/zip.gz->application/gzip.tar->application/x-tar.7z->application/x-7z-compressed.bz2->application/x-bzip2.rar->application/vnd.rar
配置
你可以通过 config.S 文件配置 ymawky。选项说明如下。
#define DOCROOT "www/"– 这是文档根目录。改为你的 HTML 文件所在位置(相对于 ymawky),或使用绝对路径:#define DOCROOT "www/"#define DOCROOT "/Library/WebServer/Documents"#define DOCROOT "./"
#define CGI_DIR "cgi-bin/"– 这是存储 CGI 脚本的目录。只有 CGI 脚本应存储在这里!CGI_DIR 内的任何请求都将执行请求的文件#define ERR_DIR "err/"– 这是 ymawky 搜索自定义错误 HTML 页面的目录,例如err/404.html或err/500.html#define DEFAULT_FILE "index.html"– 这是 ymawky 收到空GET / HTTP/1.1请求时提供的默认文件.equ RECV_TIMEOUT, 10– ymawky 等待接收数据后关闭连接的秒数。如果两次read()之间的时间超过RECV_TIMEOUT秒,ymawky 将以408 Request Timed Out关闭连接.equ HEADER_REQ_TIMEOUT_SECS, 10– ymawky 等待接收完整头部的最长秒数。如果接收头部所需时间超过此值,ymawky 将以408 Request Timed Out关闭连接.equ PUT_GRACE_SECS, 5– ymawky 根据Content-Length动态计算每个 PUT 的最长时间。最长时间定义为PUT_GRACE_SECS + Content-Length / PUT_MIN_BPS。这是当计算出的文件上传时间小于 1 秒时允许的最小宽限时间.equ PUT_MIN_BPS, 1024 * 16– 最低每秒字节数。值越大越严格,越小越宽松。由于使用.equ指令,支持算术运算,1024 * 16在汇编时计算为16384(16KB).equ MAX_BODY_SIZE, 1024 * 1024 * 1024– PUT 允许的最大 Content-Length 字节数。默认为 1GB(102410241024 = 1073741824 字节)。Content-Length 大于此值的文件将被拒绝,并返回413 Content Too Large.equ MAX_PROCS, 256– 允许 ymawky 同时运行的最大进程数。由于 ymawky 是每连接一进程的服务器,需要确保不耗尽 PID 空间。ymawky 会在超限时返回503 Service Unavailable
实现说明
ymawky 是为 macOS 编写的(抱歉……)。代码中有很多(应该说远不止“很多”)macOS 特定的内容,无法移植。
- macOS 上的系统调用使用
x16作为系统调用号,并用svc #0x80调用。Linux 使用x8和svc #0。 - 错误报告方式不同。macOS 在出错时设置进位标志,并将
errno放入x0。Linux 在x0中返回一个负值,如-ENOENT。所有b.cs都需要替换为cmp x0, #0/b.lt ...,并且需要取反x0来得到 errno。 fork()行为不同:macOS 在子进程中将x1设为 1,而 Linux 将x0设为 0。SO_NOSIGPIPE在 Linux 上不存在。O_NOFOLLOW_ANY也是 macOS 特有的。renameatx_np()也是 macOS 特有的。Linux 有renameat2(),但标志值不同。- 结构体布局和偏移量会不同。
stat64结构体、itimerval结构体和sockaddr_in结构体都需要重新考虑。 adr xN, foo@PAGE/add xN, xN, foo@PAGEOFF是 Mach-O 的重定位操作符。Linux ELF 使用不同的语法,例如:pg_hi21:和:lo12:。adr_l、ldr_l和str_l宏需要重写或替换。- 我个人最喜欢的一点:信号处理在 Linux 和 macOS 上工作方式不同。macOS 的
sigaction结构体包含一个sa_tramp字段,内核在跳转到你的处理函数之前会先跳转到它。ymawky 直接将sa_tramp用作处理函数本身,完全跳过了 libc 的蹦床(trampoline)和sigreturn。由于处理函数仅发送 408 并退出,无需返回,这在没有 libc 的情况下工作得很好。sigaction调用需要为 POSIX 系统重写。幸运的是,我已经将 ymawky 移植到了 Linux,并为你完成了所有工作!该代码位于本仓库的 Linux 分支中。
特别感谢:
- asmhttpd (https://github.com/jcalvinowens/asmhttpd),一个 x86_64 Linux HTTP 服务器,是重要的灵感来源
- Bob Johnson
- Bob Johnson’s Therapist
相似文章
Show HN: 用汇编语言构建 Web 服务器,为我的生命赋予(些许缺乏的)意义
ymawky 是一个专为 macOS 编写的 ARM64 汇编 Web 服务器,其特点是不依赖 libc 仅使用系统调用,并具备基本的 HTTP 功能。
用 aarch64 汇编构建 Web 服务器,给我的生活(缺乏)意义
本文介绍了 'ymawky',一个完全用 aarch64 汇编为 macOS 编写的最小 HTTP Web 服务器,使用原始系统调用而无需 libc 包装器,以探索底层系统机制。
用 x86_64 汇编写成的 Linux 桌面
一位开发者借助 Claude Code,用纯 x86_64 汇编重建了完整的 Linux 桌面栈——从 shell、终端、窗口管理器到各种工具,实现微秒级启动,并延长数小时续航。
编写可移植的ARM64汇编代码
一份关于编写可在Apple Darwin和Linux/BSD系统间移植的ARM64汇编代码的指南,涵盖ABI、符号命名和向量助记符的差异。
Plexus P/20 模拟器
一款全新的开源 WebAssembly 模拟器,重现 1980 年代 Plexus P/20 Unix 服务器,让用户可在浏览器中运行 SystemV Unix。