Show HN: 一个纯ARM64汇编编写的Web服务器,现可在Linux上运行,并可无理由支持CGI

Hacker News Top 工具

摘要

ymawky 是一个完全用ARM64汇编编写的Web服务器,支持CGI、静态文件和多种HTTP方法,现已可在Linux上运行。

这就是ymawky,一个现在支持动态内容的Web服务器,完全用ARM64汇编编写。我之前在这里发布过ymawky的相关信息:<a href="https://news.ycombinator.com/item?id=48080587">https://news.ycombinator.com/item?id=48080587</a><p>在过去的一个半月里,我做了一些重大改进:添加了CGI脚本支持,因此服务器现在支持查询字符串和动态内容;并且将ymawky完全移植到了Linux上,而不再是仅限macOS。<p>除了GET/PUT/HEAD/DELETE/OPTIONS请求外,由于CGI支持,ymawky还接受POST请求(目前仅限CGI资源)。<p>我还更新了更详细的文档以反映CGI支持和Linux移植情况:<a href="https://imtomt.github.io/ymawky/" rel="nofollow">https://imtomt.github.io/ymawky/</a>
查看原文
查看缓存全文

缓存时间: 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/ymawkywww/ 并列存在。参见配置修改默认文件和文档根目录。

运行

  • ./ymawky127.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 Forbidden
    • GET /ohwell...txt -> 200 OK
    • GET /../src/ymawky.S -> 403 Forbidden
    • GET /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.1HTTP/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 OK
  • 201 Created
  • 204 No Content
  • 206 Partial Content
  • 400 Bad Request
  • 403 Forbidden
  • 404 Not Found
  • 408 Request Timeout
  • 409 Conflict
  • 411 Length Required
  • 413 Content Too Large
  • 414 URI Too Long
  • 416 Range Not Satisfiable
  • 418 I'm a teapot
  • 431 Request Header Fields Too Large
  • 500 Internal Server Error
  • 501 Not Implemented
  • 502 Bad Gateway
  • 503 Service Unavailable
  • 505 HTTP Version Not Supported
  • 507 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.htmlerr/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 使用 x8svc #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_lldr_lstr_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

相似文章

用 x86_64 汇编写成的 Linux 桌面

Lobsters Hottest

一位开发者借助 Claude Code,用纯 x86_64 汇编重建了完整的 Linux 桌面栈——从 shell、终端、窗口管理器到各种工具,实现微秒级启动,并延长数小时续航。

编写可移植的ARM64汇编代码

Hacker News Top

一份关于编写可在Apple Darwin和Linux/BSD系统间移植的ARM64汇编代码的指南,涵盖ABI、符号命名和向量助记符的差异。

Plexus P/20 模拟器

Hacker News Top

一款全新的开源 WebAssembly 模拟器,重现 1980 年代 Plexus P/20 Unix 服务器,让用户可在浏览器中运行 SystemV Unix。