Stage 6: gateway edge (Connect/FlatBuffers over h2c, platform/email/guest auth, sessions, rate-limit, admin passthrough, live push bridge)
New public ingress and the first network edge. Framework + a vertical slice of operations end-to-end; remaining ops reuse the same transcode pattern in Stage 7. Contracts (new module scrabble/pkg): - push.proto (backend->gateway gRPC server-stream) + scrabble.fbs (FlatBuffers edge payloads), committed generated Go; buf/flatc Makefiles (dev-time codegen). Backend: - REST handlers on the /api/v1 groups: internal session endpoints (telegram/guest/email login -> mint, resolve, revoke) and the user slice (profile, submit_play, state, lobby enqueue/poll, chat). - internal/notify in-process Publisher hub + internal/pushgrpc gRPC server (BACKEND_GRPC_ADDR) streaming your_turn/opponent_moved/chat/nudge/match_found; emission in game.commit, social, matchmaker. - migration 00005 accounts.is_guest; guests are durable rows excluded from stats; ProvisionGuest; email-as-login (RequestLoginCode/LoginWithCode). Gateway (new module scrabble/gateway): - Connect Gateway service over h2c (Execute + Subscribe), FlatBuffers<->JSON transcode registry, Telegram initData HMAC validator (seam), session cache, token-bucket rate limiter (3 classes), push fan-out hub, backend REST + push gRPC client, admin Basic-Auth reverse proxy. go.work: use ./pkg, ./gateway + replace scrabble/pkg. CI: gateway/**, pkg/** path filters; unit build/vet/test span all three modules. Docs (PLAN, ARCHITECTURE, FUNCTIONAL+ru, TESTING, READMEs) updated; gateway/pkg unit tests + guest/email-login integration tests.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# gateway
|
||||
|
||||
The Scrabble platform's only public ingress (module `scrabble/gateway`). It
|
||||
terminates the client's **Connect-RPC + FlatBuffers** traffic over HTTP/2
|
||||
cleartext (`h2c`), authenticates the originating credential, mints/resolves a
|
||||
thin opaque session, rate-limits, injects `X-User-ID` when forwarding to the
|
||||
backend over REST/JSON, and bridges the backend's gRPC push stream to each
|
||||
client's in-app live channel. It also fronts the backend admin API behind HTTP
|
||||
Basic-Auth. See [`../docs/ARCHITECTURE.md`](../docs/ARCHITECTURE.md) §2, §3, §10,
|
||||
§12.
|
||||
|
||||
## Package layout
|
||||
|
||||
```
|
||||
cmd/gateway/ # main: config -> backend client -> session cache ->
|
||||
# push hub -> Connect h2c server (+ admin) -> serve
|
||||
proto/edge/v1/ # Connect envelope contract (committed generated Go)
|
||||
internal/config/ # GATEWAY_* env config
|
||||
internal/backendclient/ # typed REST client (+ X-User-ID) and push gRPC client
|
||||
internal/session/ # in-memory session cache (LRU/TTL, backend fallback)
|
||||
internal/ratelimit/ # token-bucket limiter (golang.org/x/time/rate)
|
||||
internal/auth/ # Telegram initData HMAC validator (seam + fixtures)
|
||||
internal/push/ # live-event fan-out hub (per-user client streams)
|
||||
internal/transcode/ # FlatBuffers<->REST bridge + message_type registry
|
||||
internal/connectsrv/ # the Connect Gateway service over h2c
|
||||
internal/admin/ # Basic-Auth reverse proxy to the backend admin API
|
||||
```
|
||||
|
||||
The FlatBuffers payloads and the backend push proto are the shared wire
|
||||
contracts in [`../pkg`](../pkg).
|
||||
|
||||
## Transport contract
|
||||
|
||||
A single `Gateway` Connect service: `Execute(message_type, payload, request_id)`
|
||||
for unary operations and `Subscribe` for the live stream. The `payload` bytes are
|
||||
FlatBuffers tables (`scrabble/pkg/fbs`); the gateway transcodes them to and from
|
||||
the backend's JSON. The session token rides in `Authorization: Bearer`; `auth.*`
|
||||
operations are unauthenticated and return the minted token. A unary domain
|
||||
outcome rides back in `ExecuteResponse.result_code` (HTTP 200); only edge
|
||||
failures become Connect error codes.
|
||||
|
||||
The Stage 6 message-type slice: `auth.telegram`, `auth.guest`,
|
||||
`auth.email.request`, `auth.email.login`, `profile.get`, `game.submit_play`,
|
||||
`game.state`, `lobby.enqueue`, `lobby.poll`, `chat.post`; live events
|
||||
`your_turn`, `opponent_moved`, `chat_message`, `nudge`, `match_found`. Further
|
||||
operations follow the same transcode pattern (added in Stage 7).
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Default | Notes |
|
||||
| --- | --- | --- |
|
||||
| `GATEWAY_HTTP_ADDR` | `:8081` | public Connect/h2c listener |
|
||||
| `GATEWAY_ADMIN_ADDR` | `:8082` | admin proxy listener (enabled only with creds) |
|
||||
| `GATEWAY_LOG_LEVEL` | `info` | zap level |
|
||||
| `GATEWAY_BACKEND_HTTP_URL` | `http://localhost:8080` | backend REST base URL |
|
||||
| `GATEWAY_BACKEND_GRPC_ADDR` | `localhost:9090` | backend push gRPC address |
|
||||
| `GATEWAY_BACKEND_TIMEOUT` | `5s` | per backend REST call |
|
||||
| `GATEWAY_ADMIN_USER` / `GATEWAY_ADMIN_PASSWORD` | unset | enable + guard the admin proxy |
|
||||
| `GATEWAY_TELEGRAM_BOT_TOKEN` | unset | enable the Telegram auth path |
|
||||
| `GATEWAY_SESSION_TTL` | `10m` | cached session lifetime |
|
||||
| `GATEWAY_SESSION_CACHE_MAX` | `50000` | cached session cap |
|
||||
| `GATEWAY_PUSH_HEARTBEAT_INTERVAL` | `15s` | live-stream keep-alive |
|
||||
|
||||
Rate-limit defaults (built-in): public 30/min·IP (burst 10), authenticated
|
||||
120/min·user (burst 40), admin 60/min·IP (burst 20), email-code 5/10 min·IP.
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
GATEWAY_BACKEND_HTTP_URL=http://localhost:8080 \
|
||||
GATEWAY_BACKEND_GRPC_ADDR=localhost:9090 \
|
||||
go run ./gateway/cmd/gateway # Connect edge on :8081
|
||||
```
|
||||
|
||||
## Generated code
|
||||
|
||||
The Connect envelope Go is committed under `proto/edge/v1`. Regenerate after
|
||||
editing the `.proto` (dev-time, like `backend/cmd/jetgen`):
|
||||
|
||||
```sh
|
||||
make -C gateway tools # go install protoc-gen-go + protoc-gen-connect-go
|
||||
make -C gateway gen # buf generate (local plugins)
|
||||
```
|
||||
|
||||
The FlatBuffers payloads are generated in [`../pkg`](../pkg) (`make -C pkg fbs`).
|
||||
|
||||
## Tests
|
||||
|
||||
```sh
|
||||
go test -count=1 ./gateway/...
|
||||
```
|
||||
|
||||
All gateway tests are hermetic: no real network, a fake backend (`httptest`) and
|
||||
credential fixtures. There is no integration (Docker) suite — the gateway holds
|
||||
no database.
|
||||
Reference in New Issue
Block a user