From a80365adc7288c00c0d60b6afc9a219f8f4de0c4 Mon Sep 17 00:00:00 2001 From: XingfenD Date: Tue, 11 Aug 2026 14:16:15 +0800 Subject: [PATCH] update documents --- AGENTS.md | 73 ++++++++++++++ docs/README.md | 242 ++++++++++++++++++++++++++++++++++++++++++++++ docs/README_zh.md | 242 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 557 insertions(+) create mode 100644 AGENTS.md create mode 100644 docs/README.md create mode 100644 docs/README_zh.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ad17280 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,73 @@ +# AGENTS.md — collab-go + +Go 1.24 WebSocket gateway that authenticates JWT tokens and proxies collaborative editing traffic to `collab-core`. + +Part of a multi-repo project. Parent `../AGENTS.md` has full architecture context. + +## Build & Run + +```bash +go run main.go # requires generated proto stubs first +go build -o collab-go main.go +``` + +Port: `1234` (override with `ADDR` env var). + +### Prerequisites — generated proto stubs + +`pkg/gen/` is gitignored. Code **will not compile** without it. Generate from the parent directory: + +```bash +# Option A: parent repo script (recommended) +bash ../deploy/script/gen_proto.sh + +# Option B: manual protoc (requires protoc-gen-go + protoc-gen-go-grpc) +mkdir -p pkg/gen +protoc -I ../proto \ + --go_out=pkg/gen --go_opt=paths=source_relative \ + --go-grpc_out=pkg/gen --go-grpc_opt=paths=source_relative \ + ../proto/yoresee_doc/v1/yoresee_doc.proto +``` + +Install codegen tools: `go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest` + +## Environment Variables + +| Var | Default | Required | +|-----|---------|----------| +| `ADDR` | `:1234` | no | +| `JWT_SECRET` | _(empty)_ | yes — empty = accept any parseable JWT | +| `COLLAB_CORE_URL` | `ws://collab-core:1234` | no | +| `BACKEND_GRPC_ADDR` | `backend:9090` | no | +| `INTERNAL_RPC_KEY` | _(empty)_ | no — sent as `x-internal-key` gRPC metadata | + +## Verification + +No tests exist. Verify with: + +```bash +go vet ./... +go build ./... +``` + +## Architecture + +- `auth/` — JWT validation (HS256/384/512) +- `config/` — env-based config via `caarlos0/env/v11` +- `handler/` — WebSocket upgrade, auth check, doc-existence check via gRPC, proxy to collab-core +- `health/` — `/health`, `/readyz`, `/livez` probes (readiness checks backend gRPC) +- `proxy/` — bidirectional WebSocket proxy to collab-core at path `/doc-{docID}` +- `pkg/gen/` — generated protobuf stubs (gitignored) + +Connection flow: `Browser → /ws/doc/{docId}?token=... → collab-go (auth + gRPC doc check) → collab-core /doc-{docId}` + +## Docker + +- `Dockerfile` — dev image, runs `go run main.go` +- `Dockerfile.prod` — production build; **expects build context at parent directory** (`COPY collab-go/...`). Proto codegen runs inside the build. + +## Conventions + +- Logs use prefix `collab-gateway` +- `CheckOrigin` allows all origins (WebSocket upgrader) +- When `JWT_SECRET` is empty, JWTs are parsed but not signature-verified (dev mode) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..a82c289 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,242 @@ +# collab-go + +WebSocket gateway for real-time collaborative document editing. Authenticates JWT tokens and proxies collaboration traffic to `collab-core`. + +## Overview + +`collab-go` is a Go service that acts as the entry point for browser WebSocket connections in the Yoresee Doc collaborative editing platform. It handles: + +- JWT token validation (HS256/384/512) +- Document existence verification via gRPC to backend +- Bidirectional WebSocket proxy to `collab-core` (Node.js Yjs service) +- Health checks and graceful shutdown + +## Connection Flow + +``` +Browser --ws://host/ws/doc/{docId}?token={jwt}--> collab-go --> collab-core +``` + +1. Browser connects to `/ws/doc/{docId}` with JWT in query parameter +2. `collab-go` validates the JWT token +3. `collab-go` calls backend gRPC `DocumentService.GetDocumentSettings()` to verify document exists +4. On success, upgrades HTTP to WebSocket +5. Dials `collab-core` at `{COLLAB_CORE_URL}/doc-{docId}` +6. Proxies messages bidirectionally between browser and `collab-core` + +## Prerequisites + +### Proto Stubs + +`pkg/gen/` contains generated protobuf code and is gitignored. You must generate it before building: + +```bash +# Recommended: use parent repo script +bash ../deploy/script/gen_proto.sh + +# Or manual protoc +mkdir -p pkg/gen +protoc -I ../proto \ + --go_out=pkg/gen --go_opt=paths=source_relative \ + --go-grpc_out=pkg/gen --go-grpc_opt=paths=source_relative \ + ../proto/yoresee_doc/v1/yoresee_doc.proto +``` + +Required tools: +- `protoc-gen-go`: `go install google.golang.org/protobuf/cmd/protoc-gen-go@latest` +- `protoc-gen-go-grpc`: `go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest` + +### Go + +Go 1.24 or later. + +## Build & Run + +```bash +# Build +go build -o collab-go main.go + +# Run +./collab-go +``` + +Or directly: + +```bash +go run main.go +``` + +Default port: `1234` + +## Configuration + +Environment variables: + +| Variable | Default | Required | Description | +|----------|---------|----------|-------------| +| `ADDR` | `:1234` | No | Listen address | +| `JWT_SECRET` | _(empty)_ | Yes | JWT signing secret. Empty = accept any parseable JWT (dev mode) | +| `COLLAB_CORE_URL` | `ws://collab-core:1234` | No | WebSocket URL for `collab-core` | +| `BACKEND_GRPC_ADDR` | `backend:9090` | No | gRPC address for backend service | +| `INTERNAL_RPC_KEY` | _(empty)_ | No | Sent as `x-internal-key` gRPC metadata for internal auth | + +### JWT Modes + +**Production mode** (with `JWT_SECRET`): +- Validates JWT signature using HS256/384/512 +- Token must be valid and properly signed + +**Development mode** (empty `JWT_SECRET`): +- Parses JWT but does not verify signature +- Accepts any structurally valid JWT +- Useful for local development without shared secrets + +## API Endpoints + +### WebSocket + +``` +GET /ws/doc/{docId}?token={jwt} +``` + +- Upgrades to WebSocket on success +- Returns `401 Unauthorized` if JWT is invalid +- Returns `404 Not Found` if document doesn't exist +- Returns `503 Service Unavailable` if backend gRPC is unreachable + +### Health Probes + +``` +GET /health +GET /readyz +GET /livez +``` + +All return JSON: + +```json +{ + "status": "ok", + "detail": "", + "backend": "ok" +} +``` + +- `/health`: Always returns `200 OK`. Includes backend status if available. +- `/readyz`: Returns `503 Service Unavailable` during shutdown draining or if backend is unhealthy. +- `/livez`: Always returns `200 OK`. Used for liveness checks. + +## Architecture + +``` +collab-go/ +├── auth/ JWT validation +├── config/ Environment-based configuration +├── handler/ WebSocket handler, auth + doc check + proxy +├── health/ Health probe handlers +├── proxy/ Bidirectional WebSocket proxy +├── pkg/gen/ Generated protobuf stubs (gitignored) +└── main.go Entry point +``` + +### Key Components + +**auth/** +- `Authenticator` validates JWT tokens +- Supports HS256, HS384, HS512 signing methods +- Falls back to unverified parsing when secret is empty + +**config/** +- Uses `caarlos0/env/v11` for environment variable parsing +- All configuration via environment variables + +**handler/** +- `WSHandler` implements `http.Handler` for WebSocket connections +- Validates JWT from `?token=` query parameter +- Calls backend gRPC to verify document exists +- Upgrades connection and delegates to proxy + +**health/** +- Implements Kubernetes-style health probes +- Readiness checks backend gRPC health +- Supports graceful shutdown draining + +**proxy/** +- `Proxy` manages WebSocket connection to `collab-core` +- `ProxyWS` runs two goroutines for bidirectional message forwarding +- Connection path: `/doc-{docId}` on `collab-core` + +## Graceful Shutdown + +On receiving `SIGINT` or `SIGTERM`: + +1. Sets health checker to draining mode (readiness returns `not_ready`) +2. Stops accepting new connections +3. Waits up to 15 seconds for existing connections to close +4. Exits + +Load balancers should stop routing traffic when `/readyz` returns non-200. + +## Docker + +### Development + +```bash +docker build -t collab-go-dev -f Dockerfile . +docker run -p 1234:1234 --env-file .env collab-go-dev +``` + +Runs `go run main.go` (no build cache). + +### Production + +```bash +# Build from parent directory +docker build -t collab-go -f Dockerfile.prod .. +``` + +Note: `Dockerfile.prod` expects build context at parent directory (`..`) because it copies: +- `collab-go/` source +- `proto/` for protobuf generation +- Generates proto stubs inside the container + +Multi-stage build: +1. Builder stage: generates proto stubs, compiles binary +2. Runner stage: copies binary to minimal Alpine image + +## Integration with Collab Stack + +`collab-go` is part of a two-service collaboration stack: + +1. **collab-core** (Node.js): Maintains Yjs document state in memory + Redis +2. **collab-go** (Go): WebSocket gateway, auth, proxy + +The gateway pattern separates: +- Authentication and authorization (Go, fast) +- Document state management (Node.js, Yjs runtime) +- Horizontal scaling (multiple gateway instances, single core per doc) + +## Logging + +All logs use prefix `collab-gateway`: + +``` +collab-gateway listening on :1234 +collab-gateway unauthorized path=/ws/doc/abc remote=127.0.0.1:12345 err=... +collab-gateway doc check failed docID=abc err=... +``` + +## Testing + +No tests currently exist. Verify with: + +```bash +go vet ./... +go build ./... +``` + +## Related Documentation + +- Parent project: `../AGENTS.md` +- Full system docs: `../../docs/README.md` +- Chinese version: `../../docs/README_zh.md` diff --git a/docs/README_zh.md b/docs/README_zh.md new file mode 100644 index 0000000..2f3f9df --- /dev/null +++ b/docs/README_zh.md @@ -0,0 +1,242 @@ +# collab-go + +实时协作文档编辑的 WebSocket 网关。负责 JWT 鉴权并将协作流量代理到 `collab-core`。 + +## 概述 + +`collab-go` 是一个 Go 服务,作为 Yoresee Doc 协作编辑平台中浏览器 WebSocket 连接的入口。主要职责: + +- JWT 令牌校验(HS256/384/512) +- 通过 gRPC 调用后端校验文档是否存在 +- 与 `collab-core`(Node.js Yjs 服务)之间的双向 WebSocket 代理 +- 健康检查与优雅停机 + +## 连接流程 + +``` +浏览器 --ws://host/ws/doc/{docId}?token={jwt}--> collab-go --> collab-core +``` + +1. 浏览器连接 `/ws/doc/{docId}`,JWT 通过 query 参数传递 +2. `collab-go` 校验 JWT 令牌 +3. `collab-go` 调用后端 gRPC `DocumentService.GetDocumentSettings()` 验证文档存在 +4. 成功后将 HTTP 升级为 WebSocket +5. 拨号连接 `collab-core`:`{COLLAB_CORE_URL}/doc-{docId}` +6. 在浏览器与 `collab-core` 之间双向代理消息 + +## 前置依赖 + +### Proto 桩代码 + +`pkg/gen/` 包含生成的 protobuf 代码,已被 gitignore。构建前必须先生成: + +```bash +# 推荐:使用父仓库脚本 +bash ../deploy/script/gen_proto.sh + +# 或手动 protoc +mkdir -p pkg/gen +protoc -I ../proto \ + --go_out=pkg/gen --go_opt=paths=source_relative \ + --go-grpc_out=pkg/gen --go-grpc_opt=paths=source_relative \ + ../proto/yoresee_doc/v1/yoresee_doc.proto +``` + +所需工具: +- `protoc-gen-go`:`go install google.golang.org/protobuf/cmd/protoc-gen-go@latest` +- `protoc-gen-go-grpc`:`go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest` + +### Go + +Go 1.24 或更高版本。 + +## 构建与运行 + +```bash +# 构建 +go build -o collab-go main.go + +# 运行 +./collab-go +``` + +或直接运行: + +```bash +go run main.go +``` + +默认端口:`1234` + +## 配置 + +环境变量: + +| 变量 | 默认值 | 必填 | 说明 | +|------|--------|------|------| +| `ADDR` | `:1234` | 否 | 监听地址 | +| `JWT_SECRET` | _(空)_ | 是 | JWT 签名密钥。为空 = 接受任何可解析的 JWT(开发模式) | +| `COLLAB_CORE_URL` | `ws://collab-core:1234` | 否 | `collab-core` 的 WebSocket 地址 | +| `BACKEND_GRPC_ADDR` | `backend:9090` | 否 | 后端 gRPC 地址 | +| `INTERNAL_RPC_KEY` | _(空)_ | 否 | 作为 `x-internal-key` gRPC 元数据发送,用于内部鉴权 | + +### JWT 模式 + +**生产模式**(设置了 `JWT_SECRET`): +- 使用 HS256/384/512 校验 JWT 签名 +- 令牌必须有效且签名正确 + +**开发模式**(`JWT_SECRET` 为空): +- 解析 JWT 但不校验签名 +- 接受任何结构有效的 JWT +- 适用于本地开发时无共享密钥的场景 + +## API 端点 + +### WebSocket + +``` +GET /ws/doc/{docId}?token={jwt} +``` + +- 成功后升级为 WebSocket +- JWT 无效返回 `401 Unauthorized` +- 文档不存在返回 `404 Not Found` +- 后端 gRPC 不可达返回 `503 Service Unavailable` + +### 健康探针 + +``` +GET /health +GET /readyz +GET /livez +``` + +均返回 JSON: + +```json +{ + "status": "ok", + "detail": "", + "backend": "ok" +} +``` + +- `/health`:始终返回 `200 OK`。包含后端状态(如可用)。 +- `/readyz`:停机 draining 阶段或后端不健康时返回 `503 Service Unavailable`。 +- `/livez`:始终返回 `200 OK`。用于存活检查。 + +## 架构 + +``` +collab-go/ +├── auth/ JWT 校验 +├── config/ 基于环境变量的配置 +├── handler/ WebSocket 处理器:鉴权 + 文档校验 + 代理 +├── health/ 健康探针处理器 +├── proxy/ 双向 WebSocket 代理 +├── pkg/gen/ 生成的 protobuf 桩代码(gitignored) +└── main.go 入口文件 +``` + +### 核心组件 + +**auth/** +- `Authenticator` 校验 JWT 令牌 +- 支持 HS256、HS384、HS512 签名方式 +- 密钥为空时降级为不校验签名的解析 + +**config/** +- 使用 `caarlos0/env/v11` 解析环境变量 +- 所有配置通过环境变量传入 + +**handler/** +- `WSHandler` 实现 WebSocket 连接的 `http.Handler` +- 从 `?token=` query 参数获取 JWT +- 调用后端 gRPC 校验文档是否存在 +- 升级连接后委托给代理 + +**health/** +- 实现 Kubernetes 风格的健康探针 +- readiness 检查后端 gRPC 健康状态 +- 支持优雅停机 draining + +**proxy/** +- `Proxy` 管理与 `collab-core` 的 WebSocket 连接 +- `ProxyWS` 启动两个 goroutine 进行双向消息转发 +- 连接路径:`collab-core` 上的 `/doc-{docId}` + +## 优雅停机 + +收到 `SIGINT` 或 `SIGTERM` 信号后: + +1. 健康检查器进入 draining 模式(readiness 返回 `not_ready`) +2. 停止接受新连接 +3. 等待已有连接关闭,最长 15 秒 +4. 退出 + +负载均衡器应在 `/readyz` 返回非 200 时停止路由流量。 + +## Docker + +### 开发环境 + +```bash +docker build -t collab-go-dev -f Dockerfile . +docker run -p 1234:1234 --env-file .env collab-go-dev +``` + +运行 `go run main.go`(无构建缓存)。 + +### 生产环境 + +```bash +# 从父目录构建 +docker build -t collab-go -f Dockerfile.prod .. +``` + +注意:`Dockerfile.prod` 期望构建上下文在父目录(`..`),因为它需要复制: +- `collab-go/` 源码 +- `proto/` 用于 protobuf 生成 +- 在容器内部生成 proto 桩代码 + +多阶段构建: +1. Builder 阶段:生成 proto 桩代码,编译二进制 +2. Runner 阶段:将二进制复制到最小化 Alpine 镜像 + +## 协作栈集成 + +`collab-go` 是双服务协作栈的一部分: + +1. **collab-core**(Node.js):在内存 + Redis 中维护 Yjs 文档状态 +2. **collab-go**(Go):WebSocket 网关,鉴权,代理 + +网关模式实现了职责分离: +- 鉴权与授权(Go,快速) +- 文档状态管理(Node.js,Yjs 运行时) +- 水平扩展(多个网关实例,每个文档一个 core) + +## 日志 + +所有日志使用 `collab-gateway` 前缀: + +``` +collab-gateway listening on :1234 +collab-gateway unauthorized path=/ws/doc/abc remote=127.0.0.1:12345 err=... +collab-gateway doc check failed docID=abc err=... +``` + +## 测试 + +当前无测试。可通过以下命令验证: + +```bash +go vet ./... +go build ./... +``` + +## 相关文档 + +- 父项目:`../AGENTS.md` +- 完整系统文档:`../../docs/README.md` +- 中文版本:`../../docs/README_zh.md`