# webchannel **Repository Path**: duzc2/webchannel ## Basic Information - **Project Name**: webchannel - **Description**: webchannel 是一个 **反向代理网关系统 **,它让内网服务**无需公网 IP、无需开放端口**即可被公网访问。 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-14 - **Last Updated**: 2026-05-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # webchannel 基于 WebSocket 的多租户 HTTP 代理网关 --- ## 这是什么 webchannel 是一个 **反向代理网关系统 (v2)**,它让内网服务**无需公网 IP、无需开放端口**即可被公网访问。 典型场景:你的服务部署在公司内网或本地开发机上,外部想调用你的 API。webchannel 在公网服务器上运行 **Server**,你的机器上运行 **Client**。Client 与 Server 保持一条 WebSocket 长连接,Server 收到的 HTTP 请求通过这条连接转发给 Client,Client 再转发到你本地的 HTTP 服务,响应原路返回。 ``` Internet 公网服务器 内网/本地 ┌──────────┐ HTTP ┌──────────────┐ WebSocket ┌──────────┐ HTTP ┌──────────┐ │ 外部调用者 │────────>│ Server │<=============>│ Client │────────>│ 本地服务 │ │ │<────────│ (Gateway) │ 长连接 │ (Gateway) │<────────│ (你的API) │ └──────────┘ └──────────────┘ └──────────┘ └──────────┘ ``` **多租户设计**:一台 Server 可以同时服务多个 Client,每个 Client 分配一个独立的主机名(如 `app-a.example.com`、`app-b.example.com`),通过 `Host` 请求头自动路由到对应的 Client。 ## 核心特性 - **零配置穿透** — 内网服务无需开放端口,无需配置防火墙 - **多租户隔离** — 每个客户端绑定独立主机名,API Key 鉴权互不影响 - **并发复用** — 单个 WebSocket 连接承载多个并发 HTTP 请求 - **自动重连** — 网络断开后指数退避重连,无需人工干预 - **流式传输** — v2 全链路 chunked streaming,支持大文件上传下载,带背压控制 - **WebSocket 代理** — 浏览器 WebSocket 连接可代理到内网 WebSocket 服务 - **按主机名会话限制** — 每个主机名独立控制最大浏览器会话数,防止滥用 - **带宽限制** — 按主机名令牌桶限速,精确控制每个客户端的出站流量 - **permessage-deflate** — WebSocket 压缩,减少传输带宽消耗 - **跨运行时** — 纯 ES 模块(.mjs),Node.js 和 Bun 均可运行 - **TypeScript 支持** — 提供类型声明文件(.d.mts),编辑器智能提示 - **SQLite 持久化** — 客户端和路由映射存储在 SQLite 中,重启不丢失 - **应用层心跳** — 15 秒心跳 + 30 秒超时检测死连接 ## 使用方式 webchannel 支持两种使用方式: ### 方式一:作为 npm 依赖引入(推荐) 在你的项目中通过 git 直接引入,可以按需只引入 Server 或 Client: ```bash # 在你的项目中 npm install github:user/webchannel ``` 安装时自动执行构建脚本,生成可 tree-shaking 的 bundle。之后按需导入: ```js // 只需要 Client?只引入 Client,最终打包**零**Server 代码 import { createClient } from 'webchannel/client'; // 只需要 Server?只引入 Server,最终打包**零**Client 代码 import { createServer } from 'webchannel/server'; // 只需要协议定义? import { MESSAGE_TYPES, encodeMessage } from 'webchannel/protocol'; ``` **Tree-shaking 保证**:`webchannel/client` 和 `webchannel/server` 的内部依赖(`webchannel-common`、`webchannel-protocol`)已被内联打包。`webchannel/client` 中不包含任何 Server 代码——下游打包工具(webpack、esbuild、rollup 等)的 tree-shaking 不会将 `better-sqlite3`、`createServer` 或 `process.on('uncaughtException')` 引入你的 Client 产物。 详细用法见 [快速上手](./docs/GETTING_STARTED.md)。 ### 方式二:直接运行(独立部署) 克隆仓库后直接运行,适合独立部署 Server 或 Client(不嵌入其他项目): ```bash git clone https://github.com/user/webchannel.git cd webchannel npm install ``` **启动 Server(公网服务器):** ```bash node server/bin/server.mjs --admin-token "your-token" --http-port 8080 --ws-port 9090 ``` **注册客户端并分配主机名:** ```bash curl -X POST http://localhost:8080/__admin/clients \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-token" \ -d '{"clientId":"my-app", "name":"我的应用"}' curl -X POST http://localhost:8080/__admin/clients/my-app/hostnames \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-token" \ -d '{"hostname":"my-app.localhost"}' ``` **启动 Client(内网机器):** ```bash node client/bin/client.mjs \ --client-id my-app \ --api-key "<上一步获取的API Key>" \ --local-base-url http://localhost:3000 \ --server-url ws://your-server.com:9090 ``` **验证:** ```bash curl http://my-app.localhost:8080/hello ``` 详细部署指南见 [快速上手](./docs/GETTING_STARTED.md)。 ## 文档 | 文档 | 内容 | | ------------------------------------- | ------------------------------ | | [系统概览](./docs/OVERVIEW.md) | 项目目标、适用场景、解决的问题 | | [架构设计](./docs/ARCHITECTURE.md) | 内部架构、数据流、组件关系 | | [快速上手](./docs/GETTING_STARTED.md) | 详细安装与使用教程 | | [通信协议](./docs/PROTOCOL.md) | WebSocket 消息格式规范 | | [Server 手册](./docs/SERVER.md) | Server 配置、运行、管理 | | [Client 手册](./docs/CLIENT.md) | Client 配置、运行、故障排查 | | [管理 API](./docs/API.md) | Admin API 完整参考 | | [常见问题](./docs/FAQ.md) | FAQ | ## 项目结构 ``` webchannel/ ├── common/ # 共享工具(日志工厂、CLI 配置解析) ├── protocol/ # 共享协议定义(零外部依赖) ├── server/ # 网关服务器 ├── client/ # 网关客户端 ├── docs/ # 文档 ├── build.mjs # 构建脚本(esbuild) └── dist/ # 构建产物(git ignored,npm install 时自动生成) ``` 四个子包为独立的 npm workspace,可分别开发。通过构建脚本打包为可独立分发的 ES 模块。 ## 技术栈 - **运行时**: Node.js >= 18 或 Bun >= 1.0 - **传输层**: WebSocket(ws 库) - **持久化**: SQLite(better-sqlite3,原生 C 绑定,WAL 模式) - **协议**: JSON 文本帧 + 原始二进制帧 - **无框架**: 使用 Node.js 内置 `http` 模块,零框架依赖 - **构建**: esbuild,将工作区包打包为可独立分发的 ES 模块 ## 反向代理配置 webchannel 通过 HTTP `Host` 请求头将请求路由到对应的客户端。如果你在 webchannel Server 前面加了反向代理(Nginx、Caddy、HAProxy 等),**必须确保反向代理保留原始的 `Host` 头**,否则 Server 会收到被改写的 `Host`(如 `127.0.0.1` 或 `localhost`)而找不到对应的客户端映射,返回 `{"error":"no_mapping","message":"no client mapped for hostname: "127.0.0.1""}`。 ### Nginx ```nginx server { listen 80; server_name ~^(?.+)\.example\.com$; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; # 关键:保留原始 Host 头 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket 支持(浏览器 WebSocket 代理需要) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ``` ### Caddy Caddy 默认保留原始 `Host` 头,一般无需额外配置: ```caddyfile app.example.com { reverse_proxy 127.0.0.1:8080 } ``` 如果你的 Caddy 配置中写了 `header_up` 覆盖了 Host,去掉即可。 ### HAProxy ```haproxy frontend web bind :80 mode http default_backend webchannel backend webchannel mode http server webchannel 127.0.0.1:8080 # 默认保留原始 Host 头,无需额外配置 ``` ### Apache (mod_proxy) ```apache ServerName *.example.com ProxyPreserveHost On # 关键:保留原始 Host 头 ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/ ``` ### 验证 用 curl 测试 Host 头是否正确传递: ```bash # 如果 Host 头被正确保留,应该返回正常响应或 502(Client 离线) # 如果 Host 头被改写,会返回 no_mapping 错误 curl -v -H "Host: my-app.localhost" http://127.0.0.1:8080/ ``` ### 常见错误排查 | 错误信息 | 原因 | 解决 | |---------|------|------| | `no client mapped for hostname: "127.0.0.1"` | 反向代理将 Host 头改写为代理目标的 IP | 按上方配置保留原始 Host 头 | | `no client mapped for hostname: "localhost"` | 反向代理将 Host 头改写为 localhost | 同上 | | `no client mapped for hostname: "(invalid)"` | Host 头为空或格式不正确 | 检查反向代理是否正确传递了 Host 头 | ## License Apache-2.0