Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions .agents/docs/2026-08-06-add-websocket-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,42 @@ mcpp test -p websocket → test result ok. 1 passed; 0 fa
lint:`lua` 语法、`check_mirror_urls`(plain-string url 不触发镜像约束)、`check_package_name` 全过;
`mcpp xpkg parse` 在本地较新 mcpp(2026.8.4.1)与 CI pin(2026.8.3.3)上均 OK。

## 7. 后续待办
## 7. 后续:server + zlib features(同一天,独立 PR 叠加在 #158 上)

基座合流后,又补了两个可选 feature,`pkgs/c/compat.websocket.lua` 增加 `features` 表:

- **`server`**:把基座剔掉的 4 个 TU 编回来。逐文件核对过,它们只 include 标准库与库内已编的 IX 头,
零新增外部依赖。消费端 `features = ["server"]` 即获得 `ix::WebSocketServer`(`SocketServer` 派生,
自带 `getPort()` 等)。
- **`zlib`**:`defines = { "IXWEBSOCKET_USE_ZLIB=1" }` + `deps = { ["compat.zlib"] = "1.3.2" }`,
把 gzip codec 从 no-op 变成真正的 permessage-deflate 压缩。define 只到包自身 TU,消费端无需
拿到 —— 客户端 `enablePerMessageDeflate()`、server 默认开。

**`server` implies `zlib` —— 一个靠读源码才发现的正确性坑**:`IXWebSocketServer` 默认开启
permessage-deflate(`_enablePerMessageDeflate = true`),而 transport 的扩展协商**不受**
`IXWEBSOCKET_USE_ZLIB` 门控(只有 codec 被门控)。若 server 编了而 zlib 没编,server 会宣称压缩、
却执行不了 —— 因此用具名 feature 的 `implies = { "zlib" }` 强制一起开(参考 `compat.eigen` 的
`use_blas`;`default` feature 的 implies 恒生效是另一个已知坑,具名 feature 不受影响)。

### 验证(与 CI 一致:mcpp 2026.8.3.3 + gcc@16.1.0 + `MCPP_BUILD_CACHE=local`)

```
mcpp test -p websocket → test result ok. 1 passed; 0 failed (默认零依赖基座,回归)
mcpp test -p websocket-features → test result ok. 1 passed; 0 failed (server + zlib)
```

- `websocket-features` 成员:起真实 `ix::WebSocketServer` 于 loopback,text/binary 往返;**压缩在
线路上可观测**:64 KiB 重复 `'a'` 往返,`message.str.size()` = 65536 而 `wireSize` = **80** ——
该字段在 transport 里取原始帧负载大小(压缩后),`str` 是解压后内容,比值即压缩证据。
- **负向验证**:默认成员(无 feature)的构建产物里,4 个 server TU 的 `.o` 不存在,`nm` 全量
无 `WebSocketServer`/`SocketServer`/`HttpServer` 符号 —— 消费端不开 `server` 却引用它会链接报错,
feature 门控真实生效。

## 8. 后续待办

- **CN 镜像**:有 `mcpp-res` 写权限后,`gtc` 建 `mcpp-res/websocket`、传与 GLOBAL 字节一致的
`IXWebSocket-12.0.1.tar.gz`,把三平台 url 改写为 `{ GLOBAL, CN }`(sha 不变)。
- **TLS feature**(可选):索引里有 `compat.openssl`/`compat.mbedtls`,若要支持 wss,可加 feature 把对应
TLS TU 与依赖编入 —— 本包刻意先做零依赖纯客户端。
- **TLS feature**(最后再考虑):索引里有 `compat.openssl`/`compat.mbedtls`,若要支持 wss,可加 feature
把对应 TLS TU 与依赖编入(linux+openssl / windows+mbedtls / mac+SecureTransport)。它是唯一会显著
拖慢 CI 的选项(openssl 走 install() 重型构建),故排在最后。
- **C++23 薄封装头**(可选):用户计划稿提到的 `websocket.hpp`(span/format/RAII 薄层)不阻塞本包,可后续单独加。
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Two kinds of packages live here:
| Native module library (Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) · [`ffmpeg`](pkgs/f/ffmpeg.lua) (module layer; sources compiled directly through `compat.ffmpeg`) · [`opencv`](pkgs/o/opencv.lua) (single repository: the module layer and the full OpenCV 5 source build both live in the package, and only this descriptor stays on the index side) · [`mcpplibs.grpc`](pkgs/g/grpc.lua) (gRPC 1.83.0 — the one library here that CANNOT be a compat descriptor: upstream publishes no self-contained source artifact, its tag archive carrying abseil/protobuf/re2/boringssl/zlib as empty submodule placeholders, so [grpc-m](https://github.com/mcpplibs/grpc-m)'s release tarball IS that artifact. It vendors only gRPC's own source and takes the five dependencies from this index, so a consumer that also uses protobuf links one copy rather than two) |
| C-source compat (with `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) |
| C++-source compat, one depending on the other | [`compat.abseil`](pkgs/c/compat.abseil.lua) (151 TUs; a wildcard over `absl/**` trimmed by upstream's test/benchmark naming conventions) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua) (the libprotobuf runtime, 79 TUs transcribed from upstream's own `src/file_lists.cmake`; declares `compat.abseil` as a dependency because protobuf's public headers include `absl/…`, and its `gzip` feature defines `HAVE_ZLIB` and pulls `compat.zlib`, while `upb` adds protobuf's 64-TU C runtime out of the same tarball) · [`compat.re2`](pkgs/c/compat.re2.lua) (22 TUs, upstream's own `RE2_SOURCES`) |
| C++-source compat, client-only with zero deps | [`compat.websocket`](pkgs/c/compat.websocket.lua) (IXWebSocket 12.0.1 — a pure RFC 6455 client compiled from upstream's `IXWEBSOCKET_SOURCES` minus the four server TUs. TLS is off (no `IXWEBSOCKET_USE_TLS`; the OpenSSL/MbedTLS/AppleSSL TUs aren't built) and zlib is off too (`IXWEBSOCKET_USE_ZLIB` unset, so the gzip codec compiles to a no-op) — no external dependency at all. Its test brings its own minimal RFC 6455 echo server on loopback sockets, so the handshake, masking, fragmentation and close are exercised offline) |
| C++-source compat, zero-dep client + optional components | [`compat.websocket`](pkgs/c/compat.websocket.lua) (IXWebSocket 12.0.1 — a pure RFC 6455 client compiled from upstream's `IXWEBSOCKET_SOURCES` minus the four server TUs, so the **base build has zero external dependencies**: TLS off (the OpenSSL/MbedTLS/AppleSSL TUs aren't built) and `IXWEBSOCKET_USE_ZLIB` unset, so the gzip codec compiles to a no-op. Two optional features add on top: `server` (the four server TUs — `IXWebSocketServer`, `IXSocketServer`, `IXHttpServer`, `IXWebSocketProxyServer` — needing nothing external, and it **implies `zlib`** because upstream's server advertises permessage-deflate by default, which the transport negotiates regardless of the define) and `zlib` (deps `compat.zlib` and turns the codec into real per-message-deflate compression). The default-feature test brings its own minimal RFC 6455 echo server on loopback sockets (handshake, masking, fragmentation and close all exercised offline); a second member, `websocket-features`, runs a real `ix::WebSocketServer` and asserts the compression is observable on the wire — a 64 KiB repeated payload round-trips with `wireSize` = 80) |
| header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
| Runtime loader compat (pure sources, sidestepping upstream codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua) (the Khronos loader: `loader/generated/` is checked in, and the assembly path degrades to plain C through `UNKNOWN_FUNCTIONS_SUPPORTED`, so no CMake/Python/assembler is needed; windows deferred) · [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) |
| Whole-source direct build + generated config (only where a platform lacks one) | [`compat.curl`](pkgs/c/compat.curl.lua) (win32 uses upstream's checked-in config, unix generates one) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua) (win/mac use upstream's checked-in config; linux generates one and enables X11 by hand) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua) (91 TUs; the release tarball already ships `ares_build.h` and a Windows config, so only `ares_config.h` is snapshotted per OS) |
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上
| 原生模块库(Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) · [`ffmpeg`](pkgs/f/ffmpeg.lua)(模块层,源码经 `compat.ffmpeg` 直编) · [`opencv`](pkgs/o/opencv.lua)(单仓库:模块层与 OpenCV 5 全源码构建同在包内,索引侧只留本描述符) · [`mcpplibs.grpc`](pkgs/g/grpc.lua)(gRPC 1.83.0 —— 本索引里唯一**无法**做成 compat 描述符的库:上游不发布任何自包含源码产物,其 tag 归档里 abseil/protobuf/re2/boringssl/zlib 全是空 submodule 占位,因此 [grpc-m](https://github.com/mcpplibs/grpc-m) 的 release tarball 才是那个产物。它只 vendor gRPC 自己的源码,五个依赖全取自本索引,故同时直接使用 protobuf 的消费者链进去的是同一份而非两份)|
| C 源码 compat(含 `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) |
| C++ 源码 compat(彼此依赖) | [`compat.abseil`](pkgs/c/compat.abseil.lua)(151 TU;对 `absl/**` 取通配后,按上游自身的 test/benchmark 命名约定裁剪) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua)(libprotobuf 运行时,79 TU 逐条转录自上游 `src/file_lists.cmake`;因 protobuf 公开头文件 include 了 `absl/…`,故显式依赖 `compat.abseil`;`gzip` feature 定义 `HAVE_ZLIB` 并拉入 `compat.zlib`,`upb` feature 则从同一个 tarball 里再编出 protobuf 的 64 TU C 运行时) · [`compat.re2`](pkgs/c/compat.re2.lua)(22 TU,取自上游自身的 `RE2_SOURCES`) |
| C++ 源码 compat(纯客户端,零依赖) | [`compat.websocket`](pkgs/c/compat.websocket.lua)(IXWebSocket 12.0.1 —— 从上游 `IXWEBSOCKET_SOURCES` 剔掉 4 个 server TU 后直编的纯 RFC 6455 客户端TLS 关闭(不定义 `IXWEBSOCKET_USE_TLS`,OpenSSL/MbedTLS/AppleSSL 三组 TU 均不编),zlib 也关闭(`IXWEBSOCKET_USE_ZLIB` 不定义,gzip codec 编译为 no-op),于是零外部依赖。其测试自带一个基于 loopback 原始 socket 的最小 RFC 6455 echo server,握手/掩码/分片/关闭全部离线实测) |
| C++ 源码 compat(零依赖客户端 + 可选组件) | [`compat.websocket`](pkgs/c/compat.websocket.lua)(IXWebSocket 12.0.1 —— 从上游 `IXWEBSOCKET_SOURCES` 剔掉 4 个 server TU 后直编的纯 RFC 6455 客户端,**基座零外部依赖**:TLS 关闭(OpenSSL/MbedTLS/AppleSSL 三组 TU 均不编),`IXWEBSOCKET_USE_ZLIB` 不定义(gzip codec 编译为 no-op)。两个可选 feature 在基座上叠加:`server`(4 个 server TU —— `IXWebSocketServer`/`IXSocketServer`/`IXHttpServer`/`IXWebSocketProxyServer`,零新增外部依赖,且 **implies `zlib`** —— 因为上游 server 默认就宣称 permessage-deflate,而 transport 的协商不受宏门控)与 `zlib`(依赖 `compat.zlib`,把 codec 变成真正的 permessage-deflate 压缩)。默认构建的测试自带基于 loopback 原始 socket 的最小 RFC 6455 echo server(握手/掩码/分片/关闭全部离线实测);第二个成员 `websocket-features` 跑真实的 `ix::WebSocketServer`,并断言压缩在线路上可观测 —— 64 KiB 重复载荷往返,`wireSize` = 80) |
| header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) |
| 运行时 loader compat(纯源码,绕开上游 codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua)(Khronos loader:`loader/generated/` 已签入,汇编路径经 `UNKNOWN_FUNCTIONS_SUPPORTED` 降级为纯 C,故无需 CMake/Python/汇编器;windows 延后)· [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) |
| 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua)(91 TU;release tarball 已自带 `ares_build.h` 与 Windows 配置,故只需按 OS 冻结 `ares_config.h`) |
Expand Down
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ members = [
"tests/examples/tinyhttps",
"tests/examples/vulkan",
"tests/examples/websocket",
"tests/examples/websocket-features",
"tests/examples/tray",
"tests/examples/yyjson",
"tests/examples/magic_enum",
Expand Down
46 changes: 46 additions & 0 deletions pkgs/c/compat.websocket.lua
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,52 @@ package = {

targets = { ["websocket"] = { kind = "lib" } },

-- ── Optional components ──────────────────────────────────────────
-- The base build is the zero-dependency client above; two components
-- can be enabled on top. mcpp features only ADD, so a consumer naming
-- one keeps everything else:
--
-- websocket = "12.0.1" -> client only
-- websocket = { …, features = ["server"] } -> + the server (implies zlib)
-- websocket = { …, features = ["zlib"] } -> + compression
-- websocket = { …, features = ["server", "zlib"] } -> + both
--
-- `server` brings the four TUs the base build leaves out. It needs
-- nothing external: every IX* header it touches is already compiled
-- into the client, and its external includes are stdlib only (checked
-- against the four .cpp files). `IXWebSocketServer` derives from
-- `SocketServer`, so consumers reach `getPort()` etc. through it.
--
-- `server` IMPLIES `zlib`: upstream's server enables permessage-deflate
-- by default and the transport's extension negotiation is NOT gated on
-- IXWEBSOCKET_USE_ZLIB (only the gzip codec is). A server built without
-- the define would offer compression its codec cannot perform, so the
-- implication keeps every server build capable of what it advertises.
--
-- `zlib` turns the gzip codec from its no-op into real
-- permessage-deflate compression. Only
-- IXWebSocketPerMessageDeflateCodec.cpp is gated by
-- IXWEBSOCKET_USE_ZLIB; the negotiation logic in
-- IXWebSocketPerMessageDeflate.cpp is zlib-free. The define reaches
-- this package's own TUs — consumers need no define, they just call
-- enablePerMessageDeflate() on their client (and the server enables it
-- by default).
features = {
["server"] = {
implies = { "zlib" },
sources = {
"*/ixwebsocket/IXSocketServer.cpp",
"*/ixwebsocket/IXHttpServer.cpp",
"*/ixwebsocket/IXWebSocketServer.cpp",
"*/ixwebsocket/IXWebSocketProxyServer.cpp",
},
},
["zlib"] = {
defines = { "IXWEBSOCKET_USE_ZLIB=1" },
deps = { ["compat.zlib"] = "1.3.2" },
},
},

-- ── Platform-specific ──────────────────────────────────────────────
-- Upstream: Threads::Threads on UNIX, wsock32/ws2_32/shlwapi +
-- _CRT_SECURE_NO_WARNINGS on Windows. shlwapi is only reached from the
Expand Down
17 changes: 17 additions & 0 deletions tests/examples/websocket-features/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# websocket-features test project: consumes compat.websocket with the `server`
# and `zlib` features enabled and asserts them under `mcpp test`. Part of the
# mcpp-index self-referential workspace — the workspace-root `[indices]`
# redirect points at this repo, so the dependency resolves to the checked-in
# recipe (pkgs/c/compat.websocket.lua).
#
# The test is entirely OFFLINE: it runs an ix::WebSocketServer (the `server`
# feature) on loopback and drives the ix::WebSocket client against it, then
# checks that per-message-deflate (the `zlib` feature) actually compresses on
# the wire. The sibling member tests/examples/websocket covers the zero-dep
# client with no features; this member exists to prove the features.
[package]
name = "websocket-features-tests"
version = "0.1.0"

[dependencies.compat]
websocket = { version = "12.0.1", features = ["server", "zlib"] }
Loading
Loading