diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 58a4f80b..a491d1a4 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -77,7 +77,7 @@ so old imports keep working: `public/js/ui/gameLogic.js` (`public/js/ui/gameLogi | path | what | |---|---| | `server/index.js` | the process entry (`npm start`); `startServer()` wires `server/http/` | -| `server/http/` | `config.js` (environment), `websocket.js` (sessions, `/ws`, permessage-deflate), `static.js` (the mounts, the `~` asset routes), `media.js`, `files.js` (MIME, gzip, ETag, ranges), `buildTag.js`, `routes.js` (`/healthz`), `traffic.js` (bytes sent by category and on the WebSocket, `/healthz?traffic=1`), `assetIndex.js` (the content hashes of `public/assets/` and `public/fonts/`, cached in `.cache/asset-hashes.json`), `assetViews.js` (the served `/data/assets.json` and `/data/local-assets.json`: every art URL with its hash segment; ASSETS.md), `common.js`, `boot.js` (a pending update package first, banner, shutdown) | +| `server/http/` | `config.js` (environment), `websocket.js` (sessions, `/ws`, permessage-deflate), `static.js` (the mounts, the `~` asset routes), `media.js`, `files.js` (MIME, brotli / gzip, ETag, ranges), `buildTag.js`, `routes.js` (`/healthz`), `traffic.js` (bytes sent by category and on the WebSocket, `/healthz?traffic=1`), `assetIndex.js` (the content hashes of `public/assets/` and `public/fonts/`, cached in `.cache/asset-hashes.json`), `assetViews.js` (the served `/data/assets.json` and `/data/local-assets.json`: every art URL with its hash segment; ASSETS.md), `common.js`, `boot.js` (a pending update package first, banner, shutdown) | | `server/net.js` | sessions and reconnect tokens, rate limits, message validation | | `server/lobby.js` | rooms, seats, AI seats, spectators; starts a `Match` | | `server/data.js` | loads `data/*.json` once (frozen) | diff --git a/docs/ASSETS.md b/docs/ASSETS.md index 16fcc6ec..4dd39277 100644 --- a/docs/ASSETS.md +++ b/docs/ASSETS.md @@ -492,7 +492,7 @@ each (`server/http/assetIndex.js`, `server/http/assetViews.js`): - The server strips the segment and serves the file at the plain path. When the segment names the file's current bytes (its own hash, or its model's) the answer is `Cache-Control: public, max-age=31536000, immutable`; any other segment — a page loaded before an update, a URL derived from another file's — gets the current file with `no-cache`, so an old - page keeps working. The ETag of every indexed file is its content hash (`"398f32e2ff78d9e6"`, plus `-gz` when + page keeps working. The ETag of every indexed file is its content hash (`"398f32e2ff78d9e6"`, plus `-br` / `-gz` when compressed), on the plain URL too. - A path segment, never `?v=`: `assets.js validSpine` matches `/\.skel$/`, `audio.js SKILL_MODE_FILE` the `.mp3` ending, pixi-spine takes the atlas name from the skeleton's basename and resolves the pages against the atlas's folder — a diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 887b6e4c..0d8015be 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -223,7 +223,7 @@ server { **HTTP/2**:客户端由数百个小脚本模块组成(0.2.0 起进入对局时约 340 个),隔着公网时建议让代理以 HTTP/2 提供页面:所有模块走同一条连接,远距离玩家首次进入对局明显更快。Caddy 默认就是 HTTP/2;Nginx 见上面的 `http2`。服务器本身只说 HTTP/1.1,局域网或本机游玩不受影响。HTTP/2 省的是往返等待,不减少字节数。 -**压缩**:服务器自己压缩脚本、样式、数据和 Spine 的 `.skel` / `.atlas`:浏览器支持 brotli 时用 brotli(0.2.6 起,比 gzip 更小,`.skel` 小得最多),否则用 gzip;图片和音频本来就是压缩格式,原样发送。浏览器只在 HTTPS 页面(以及 `localhost`)上声明支持 brotli,所以通过代理或隧道的 https 地址访问时用 brotli,局域网里的 `http://192.168.x.x:3000` 这类地址用 gzip。代理不需要另开压缩(Caddy 的 `encode`、Nginx 的 `gzip` 遇到已经压缩的响应会跳过)。 +**压缩**:服务器自己压缩脚本、样式、数据和 Spine 的 `.skel` / `.atlas`:浏览器支持 brotli 时用 brotli(0.2.6 起,质量 5,比 gzip 快且更小:实测 `.skel` 小约 27%、游戏数据 JSON 小约 26%、前端库小约 14%、脚本小约 5%),否则用 gzip;图片和音频本来就是压缩格式,原样发送。浏览器只在 HTTPS 页面(以及 `localhost`)上声明支持 brotli,所以通过代理或隧道的 https 地址访问时用 brotli,局域网里的 `http://192.168.x.x:3000` 这类地址用 gzip。代理不需要另开压缩(Caddy 的 `encode`、Nginx 的 `gzip` 遇到已经压缩的响应会跳过)。 **Cloudflare 缓存**(命名隧道或自己的域名经过 Cloudflare 时):Cloudflare 默认只缓存常见扩展名,`.skel`、`.atlas` 和没有扩展名的音频地址 `/media/…` 都不在其中,每个请求都会回到开服的电脑。在 Cloudflare 控制台的「缓存规则」里加一条:主机名是你的游戏域名、且 URI 路径以 `/assets/`、`/fonts/` 或 `/media/` 开头时「符合缓存条件」,边缘 TTL 选「使用源站的 Cache-Control」。0.2.6 起素材清单里的地址带内容哈希(`/assets/~<哈希>/…`),服务器给这类地址一年的 `immutable`,换了内容的文件会换地址,所以长期缓存是安全的;`/data/`、页面和脚本不要加进规则(它们要随服务器更新)。注意:边缘公开缓存官方素材会增加被投诉的风险,大量媒体文件也要留意 Cloudflare 的服务条款;只给自己人玩时不加这条规则也可以。 diff --git a/docs/design/overview.md b/docs/design/overview.md index 3b86d718..0d2756ac 100644 --- a/docs/design/overview.md +++ b/docs/design/overview.md @@ -47,7 +47,7 @@ server/ static.js mounts / → public, /data/, /shared/, /sim/ (.js only), the /data.js browser stand-in, path guards, the ~ segment of /assets/ /fonts/ /media/ URLs stripped (immutable when it names the file) media.js /media/… extension-less audio → public/assets/audio - files.js one file → response: MIME, gzip (.skel/.atlas/.json/.js/.css …), ETag (content hash when known) / 304, + files.js one file → response: MIME, brotli q5 / gzip (.skel/.atlas/.json/.js/.css …), ETag (content hash when known) / 304, cache policy, ranges; serveBuffer for the served manifests buildTag.js build tag of the served browser runtime (/healthz build, public/js/ui/buildGuard.js) routes.js request listener: 414 / 400 / 405 guards, GET /healthz (?traffic=1: the counters), then static files diff --git a/server/http/files.js b/server/http/files.js index f63a7a71..a9ed003f 100644 --- a/server/http/files.js +++ b/server/http/files.js @@ -1,8 +1,9 @@ // server/http/files.js — one file on disk (serveFile) or one body in memory (serveBuffer, the served manifests of // assetViews.js) → an HTTP response (static.js and media.js decide which file): // MIME types incl. .mjs/.js text/javascript, .skel application/octet-stream, .atlas text/plain; -// gzip for text-like types, .skel and uncompressed fonts when the client accepts it (small files are -// compressed once and cached in memory); strong ETag + Last-Modified with 304s — the ETag is the content hash +// brotli (quality 5) for text-like types, .skel and uncompressed fonts when the client accepts it, else gzip +// (level 6) — browsers offer brotli on HTTPS pages and localhost only (small files are compressed once per +// encoding and cached in memory); strong ETag + Last-Modified with 304s — the ETag is the content hash // when the caller has one (assetIndex.js), else size + mtime; Cache-Control (html & code/data: no-cache + // revalidate; public/assets|fonts|vendor: 1 day; a URL whose content-hash segment matches the file (static.js, // media.js): immutable for a year; any `?v=` URL: immutable); single byte-range requests (206/416, used by