Kiến trúc
Luồng request
Phần tiêu đề “Luồng request”AI CLI (claude-code, claude-cli, ...) │ │ HTTP to 127.0.0.1:3817 ▼┌──────────────────────────────────────────────┐│ sotatek-proxy (Gin engine, loopback bind) ││ ││ recoveryLogger panic recovery ││ requestLogger scrub Authorization, ││ x-api-key, drop query ││ MiddlewareGitRemote ││ │ ││ ├─ extract peer port from RemoteAddr ││ ├─ inspect.GetPidByPeerPort → PID ││ ├─ inspect.GetCwdByPid → CWD ││ ├─ walk parents (≤3) for .git ││ ├─ cache.Get(pid) [verify CWD] ││ ├─ git.GetRemoteRaw(cwd) (2s timeout) ││ └─ inject X-Git-Remote (base64) ││ ││ ReverseProxy (NoRoute) ││ ├─ stamp User-Agent: sotatek-proxy/X ││ ├─ preserve original in ││ │ X-Forwarded-User-Agent ││ └─ FlushInterval=-1 (SSE-safe) │└────────────────────┬─────────────────────────┘ │ HTTP/2 (ForceAttemptHTTP2) ▼ https://bifrost.sotatek.works/anthropic/v1/*Chuỗi middleware
Phần tiêu đề “Chuỗi middleware”Thứ tự rất quan trọng. Mỗi lớp chạy theo thứ tự này khi đi vào, đảo ngược khi đi ra.
| # | Middleware | Mục đích |
|---|---|---|
| 1 | recoveryLogger | Phục hồi sau panic; không bao giờ hiển thị request header. |
| 2 | requestLogger | Log cấu trúc gọn gàng mỗi request: method, path, status, latency, client_ip, boolean has_auth/has_api_key, cờ has_query. Giá trị Authorization không bao giờ được ghi log. |
| 3 | MiddlewareGitRemote | Phân giải PID và CWD của client đang gọi, chạy git remote -v, mã hóa base64 kết quả, đặt X-Git-Remote. Xóa bất kỳ X-Git-Remote nào do client cung cấp trước. |
| 4 | ReverseProxy (NoRoute) | Chuyển tiếp đến upstream 1:1, đóng dấu User-Agent, stream SSE không có buffering. |
Kiểm tra tiến trình (PID → CWD → git)
Phần tiêu đề “Kiểm tra tiến trình (PID → CWD → git)”Proxy phân giải TCP peer port của client (cổng CLI của bạn dùng để kết nối) thành PID, rồi thành CWD, sau đó đi lên tối đa 3 thư mục cha để tìm .git.
| OS | Cài đặt |
|---|---|
| macOS | lsof -iTCP:<port> -sTCP:ESTABLISHED -F pn (PID), lsof -p <pid> -d cwd -F n (CWD). Timeout thực thi 2 giây. |
| Linux | Đọc /proc/net/tcp[6] để map port → inode, quét /proc/*/fd/ tìm socket khớp, rồi đọc /proc/<pid>/cwd. Chỉ cùng người dùng. |
| Windows | GetExtendedTcpTable cho bảng kết nối IPv4/IPv6; PEB walk qua NtQueryInformationProcess cho CWD. Chỉ 64-bit. |
Việc đi lên thư mục cha dừng lại tại các shell tương tác phổ biến (bash, zsh, fish, sh, dash, ksh, tcsh, csh) để proxy không bao giờ chiếm CWD của shell.
Cache
Phần tiêu đề “Cache”Cache PID → {cwd, git-remote} dùng sync.Map với TTL (mặc định 60 giây). Mỗi lần Get, caller đọc lại CWD hiện tại và mục cache bị xóa khi không khớp — điều này bảo vệ chống tái sử dụng PID.
Một sweep nền mỗi 5 phút sẽ xóa các mục đã hết hạn và các mục có PID không còn tồn tại (kill -0).
Shell-out git remote -v có timeout cứng 2 giây. Khi timeout hoặc không có remote, request được chuyển tiếp không có header X-Git-Remote thay vì bị chặn.
Streaming
Phần tiêu đề “Streaming”Proxy dùng httputil.ReverseProxy với FlushInterval=-1, buộc flush mỗi lần ghi. Các phản hồi SSE / chunked đến client khi upstream phát ra — không có buffering body. Khi client ngắt kết nối, upstream context bị hủy.
Health endpoint
Phần tiêu đề “Health endpoint”GET /_proxy/healthzTrả về version, commit, uptime, và (khi được kết nối) thống kê cache.
{ "status": "ok", "version": "v0.1.0", "commit": "abc1234", "uptime": "1h23m", "cache": { "entries": 12, "hits": 480, "misses": 64, "hit_rate": 0.882 }}