diff --git a/gateway/README.md b/gateway/README.md index 0993710..844dafa 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -189,12 +189,11 @@ gateway 不直连数据库(早期直连本地 sqlite 的代码已注释保留 gateway 配置采用**两层加载模式**,与其他 Python 服务(dnfs/db/static)保持一致: -1. **confs 统一配置服务**(默认模式):启动时从 confs(`127.0.0.1:8010`,可通过 `CONFS_HOST`/`CONFS_PORT` 环境变量覆盖)通过 pymsg `get` 路由拉取 `gateway.ini`,`retry_on_refused=true` 阻塞等待 confs 就绪。 -2. **本地文件覆盖**(开发/调试模式):启动时传 `-i ` 则跳过 confs,直接读取本地 ini 文件。 -3. **环境变量覆盖**(最高优先级,Docker 兼容):`LISTEN_ADDR`/`LISTEN_PORT`、`DB_HOST`/`DB_PORT`、`CAPTCHA_HOST`/`CAPTCHA_PORT`、`SMTP_HOST`/`SMTP_PORT` 覆盖 ini 中对应值。 -4. **完整性校验**:必需配置项缺失则启动失败。 +1. **confs 统一配置服务**:启动时从 confs(`127.0.0.1:8010`,可通过 `CONFS_HOST`/`CONFS_PORT` 环境变量覆盖)通过 pymsg `get` 路由拉取 `gateway.ini`,`retry_on_refused=true` 阻塞等待 confs 就绪。 +2. **环境变量覆盖**(最高优先级,Docker 兼容):`LISTEN_ADDR`/`LISTEN_PORT`、`DB_HOST`/`DB_PORT`、`CAPTCHA_HOST`/`CAPTCHA_PORT`、`SMTP_HOST`/`SMTP_PORT` 覆盖 ini 中对应值。 +3. **完整性校验**:必需配置项缺失则启动失败。 -配置文件存放于 [confs/configs/gateway.ini](../confs/configs/gateway.ini)([example.ini](example.ini) 为本地开发参考副本,内容一致,新增 `[Downstream]` 段)。 +配置文件统一存放于 [confs/configs/gateway.ini](../confs/configs/gateway.ini),gateway 目录下不再保留本地配置文件。 配置项说明: - `Server.address`/`port`/`tls`:监听地址、端口、TLS 开关;`address`/`port` 可被 `LISTEN_ADDR`/`LISTEN_PORT` 环境变量覆盖。 @@ -249,17 +248,17 @@ tls = on ```bash bash _set.sh # make clean && make -bash _run.sh # 默认从 confs 拉取配置;开发模式可用 _run.sh -i example.ini +bash _run.sh # 从 confs 拉取配置启动 ``` -默认无需传参(从 confs 拉取配置);`-i ` 为本地文件模式(开发/调试用)。其他参数:`-h/--help`、`-v/--version`。 +无需传参,配置统一由 confs 管理。命令行参数:`-h/--help`、`-v/--version`。 ## 目录结构 ``` gateway/ ├── src/ -│ └── main.cpp # 全部实现:配置加载(confs/本地文件/环境变量)、PBKDF2、RateLimiter、路由/handler +│ └── main.cpp # 全部实现:配置加载(confs → 环境变量覆盖)、PBKDF2、RateLimiter、路由/handler ├── inc/ │ ├── Msg.hpp # pymsg C++ 实现:长度前缀帧协议、MsgClient(含 retry_on_refused)、MsgException 族 │ ├── DbC.hpp # db 服务客户端:通过 pymsg 调 dbv2(地址可配置,retry_on_refused=true) @@ -267,12 +266,13 @@ gateway/ │ ├── Conf.hpp # ConfIni:boost::property_tree ini 解析,支持 fromString/env/env_int │ ├── ExpBase.hpp # 异常基类(带 source_location) │ └── LocalTime.hpp # 本地时间工具(getCurrent / parse) -├── example.ini # 本地开发配置参考副本(与 confs/configs/gateway.ini 一致) ├── Makefile ├── _set.sh / _run.sh └── README.md ``` +配置文件统一在 [confs/configs/gateway.ini](../confs/configs/gateway.ini),gateway 目录无本地配置文件。 + ## 版本 `0.7.0`(见 [src/main.cpp:31](src/main.cpp#L31))。启动时打印: @@ -283,7 +283,7 @@ Gateway 0.7.0 started 近期变更: -- `0.7.0`:配置体系迁移到 confs 统一配置服务——新增 `confs/configs/gateway.ini`(原 example.ini 内容 + `[Downstream]` 段配置下游服务地址);启动时默认从 confs(`CONFS_HOST`/`CONFS_PORT`,默认 `127.0.0.1:8010`)通过 pymsg `get` 路由拉取配置(`retry_on_refused=true`),`-i ` 保留为本地文件开发模式;`ConfIni` 增加 `fromString()`(从 ini 文本解析)、`env()`/`env_int()`(环境变量读取)、公开默认构造函数;环境变量覆盖支持 `LISTEN_ADDR`/`LISTEN_PORT`/`DB_HOST`/`DB_PORT`/`CAPTCHA_HOST`/`CAPTCHA_PORT`/`SMTP_HOST`/`SMTP_PORT`;配置完整性校验(`Server.address`/`port`、`Proxy.default`、`Downstream.db_host`/`db_port` 缺失则启动失败);`DbC` 增加 `init(host, port)` 支持可配置 db 地址(默认 127.0.0.1:8005),main.cpp 中所有硬编码 captcha/smtp 地址改为配置变量,`logs().set_endpoint()` 在启动时初始化;`_run.sh` 默认不传 `-i`,`--version/-v` 选项补全实现。 +- `0.7.0`:配置体系完全迁移到 confs 统一配置服务——配置文件统一存放于 `confs/configs/gateway.ini`(原 example.ini 内容 + `[Downstream]` 段),删除本地 example.ini,移除 `-i/--input` 命令行参数(不再支持本地 ini 文件回退,与 static 模块一致);启动时从 confs(`CONFS_HOST`/`CONFS_PORT`,默认 `127.0.0.1:8010`)通过 pymsg `get` 路由拉取配置(`retry_on_refused=true`);`ConfIni` 增加 `fromString()`/`env()`/`env_int()`;环境变量覆盖支持 `LISTEN_ADDR`/`LISTEN_PORT`/`DB_HOST`/`DB_PORT`/`CAPTCHA_HOST`/`CAPTCHA_PORT`/`SMTP_HOST`/`SMTP_PORT`;配置完整性校验;`DbC` 增加 `init(host, port)`,main.cpp 所有硬编码 captcha/smtp/db 地址改为配置变量,`logs().set_endpoint()` 启动时初始化;`--version/-v` 选项补全实现;版本升至 0.7.0。 - `0.6.1`:pymsg 客户端增加 `retry_on_refused` 重试机制——新增 `MsgConnectionRefusedException`;`MsgClient::call()` 新增 `retry_on_refused`/`retry_interval` 参数;`DbC::call_msg()` 启用重试等待 db 就绪;`LogC::_send()` 显式不重试(fail-safe)。 - `0.6.0`:WebSocket 反向代理(仅管理员)——新增 `[WsProxy]` 配置段,支持 `routes = /path=ws://host:port/target;...` 精确路径映射;握手阶段校验 Token 中 `is_admin=true`(基于 `Admin.users` 白名单),通过后双向透明桥接文本/二进制帧到后端,支持 `ws://` 和 `wss://`;后端连接错误/鉴权失败均优雅关闭。依赖 libhttp v3.1+ 的 `ws_proxy()` 接口。顺带修复 `LocalTime.hpp` 在 Apple clang 下 C++20 tzdb 不可用的编译问题(改用 `strftime`/`get_time`/`mktime`)。 - `0.5.4`:修复邮件轰炸漏洞(审计报告 P0-1)——`/register/send_code` 强制 CAPTCHA 校验(经 captcha:8009,Token 一次性消费),新增邮箱级双窗口限流(3 次/小时 + 10 次/天);smtp 层同步增加邮箱级长期限流兜底 + 全局熔断(`SMTP_HOURLY_LIMIT` 环境变量,默认 100 封/小时);前端 `register.html`/`register.js` 同步接入图形验证码(复用 `/captcha/image` 代理路由,方案 A:仅在「获取验证码」时要求 CAPTCHA,注册提交不加)。 diff --git a/gateway/_run.sh b/gateway/_run.sh index e5363c1..9c34359 100644 --- a/gateway/_run.sh +++ b/gateway/_run.sh @@ -1,7 +1,7 @@ #!/bin/bash # gateway 启动脚本 -# 默认从 confs 统一配置服务拉取配置(confs 需已启动)。 -# 若需使用本地 ini 文件(开发/调试),传入 -i ,例如:./_run.sh -i example.ini +# 配置统一由 confs 服务管理(confs/configs/gateway.ini), +# 启动时从 confs(CONFS_HOST:CONFS_PORT,默认 127.0.0.1:8010)拉取配置。 cd "$(dirname "$0")" || exit # 等待依赖服务启动(confs/db) diff --git a/gateway/example.ini b/gateway/example.ini deleted file mode 100644 index bdb0dc2..0000000 --- a/gateway/example.ini +++ /dev/null @@ -1,43 +0,0 @@ -[Server] -# 当 WAF 在前面终止 TLS 时,gateway 退化为内部 HTTP 服务: -# - tls = off 启用 HTTP 模式(配合 waf 模块使用) -# - tls = on 启用 HTTPS 模式(独立部署,证书为 full_chain.crt / server.key) -address = 127.0.0.1 -port = 8443 -tls = off - -[Proxy] -default = http://127.0.0.1:9200 -closed = http://127.0.0.1:8005;http://127.0.0.1:8007; -opened = http://127.0.0.1:9200;http://127.0.0.1:8006; - -[Token] -# Token 有效期(秒),默认 3600 = 1 小时 -keep_time = 3600 -# Token 刷新宽限期倍数:过期后 iat + keep_time * 倍数 窗口内仍可刷新,默认 2 -refresh_grace_multiplier = 2 -# Token 生命周期内最大刷新次数,超过后需重新登录,默认 10 -max_refresh = 10 - -[Admin] -# 管理员账号白名单(分号分隔)。登录时命中即在 Token payload 写入 is_admin=true, -# 下游服务(dnfs 等)凭该字段豁免所有权校验,无需各自维护名单。 -# 修改后需重启 gateway 生效;旧 Token 无此字段,按非管理员处理。 -users = qing@qsont.me;shufeng@qsont.me - -[CORS] -# 允许跨域的 Origin 白名单(分号分隔) -# 仅命中的 Origin 才会回填 Access-Control-Allow-Origin;缺省为空(不回填) -allowed_origins = https://qsont.me;https://www.qsont.me;http://127.0.0.1:8443;http://localhost:8443 - -[Database] -path = sqlite3.db - -[WsProxy] -# WebSocket 反向代理路由(仅管理员可访问) -# 格式:routes = /path=ws://host:port/target;/path2=ws://host2:port2/target2 -# 握手阶段会校验 HTTP Upgrade 请求中的 Token(Authorization: Bearer xxx 或 Cookie: q4_token=xxx), -# 仅 Token 中 is_admin=true 的用户才能升级连接;非管理员或无 Token 直接拒绝。 -# 支持 ws:// 和 wss:// 后端 -# 示例:routes = /ws/aibox=ws://127.0.0.1:8765;/ws/other=wss://example.com/ws -routes = /ws/aibox=ws://127.0.0.1:8765; diff --git a/gateway/src/main.cpp b/gateway/src/main.cpp index 0acaabe..0ad53da 100644 --- a/gateway/src/main.cpp +++ b/gateway/src/main.cpp @@ -486,7 +486,6 @@ int main(int argc, char **argv) { desc.add_options() ("help,h", "output help message") ("version,v", "output version message") - ("input,i", po::value(), "(可选) 指定本地 ini 配置文件路径,跳过 confs 拉取(开发/调试用)") ; @@ -522,50 +521,42 @@ int main(int argc, char **argv) { * 与 Python 服务(dnfs/db/static)保持一致的加载模式: * 1. 引导配置:confs 地址从 CONFS_HOST/CONFS_PORT 环境变量读取,缺省 127.0.0.1:8010 * 2. 从 confs 拉取 gateway.ini 全文(retry_on_refused=true 等待 confs 就绪) - * - 若指定了 -i 参数,则跳过 confs,直接读取本地 ini 文件(开发/调试模式) * 3. 用 boost::property_tree 解析 ini * 4. 环境变量覆盖(最高优先级,Docker 环境需要服务名而非 127.0.0.1) * 5. 配置完整性校验:必需字段缺失则启动失败 + * 不再支持本地 ini 文件回退(与 static 模块一致,配置统一由 confs 管理)。 * ==================================================================================== */ { - ConfIni cf; // 默认空构造,后续根据模式赋值 - /* 1. 引导配置:confs 连接地址(不依赖 confs 本身,避免循环依赖) */ _CONFS_HOST = ConfIni::env("CONFS_HOST", "127.0.0.1"); _CONFS_PORT = ConfIni::env_int("CONFS_PORT", 8010); + ConfIni cf; try { - if (vm.count("input")) { - /* 开发模式:-i 指定本地文件,跳过 confs */ - std::string ini_path = vm["input"].as(); - cf = ConfIni(ini_path); - std::cout << "[gateway] 配置来源: 本地文件 " << ini_path << std::endl; - } else { - /* 生产模式:从 confs 拉取 gateway.ini */ - std::cout << "[gateway] 从 confs (" << _CONFS_HOST << ":" << _CONFS_PORT - << ") 拉取 gateway.ini ..." << std::endl; - pmc::net::MsgClient confs_client(_CONFS_HOST, _CONFS_PORT, "gateway"); - json::object req; - req["_route"] = "get"; - req["name"] = "gateway.ini"; - std::string resp_str = confs_client.call( - json::serialize(req), /*timeout=*/5.0, - /*retry_on_refused=*/true); - auto resp = json::parse(resp_str).as_object(); - if (!resp.at("success").as_bool()) { - std::string err = "unknown"; - if (auto* ev = resp.if_contains("error")) { - if (ev->is_string()) err = ev->as_string().c_str(); - } - std::cerr << "[gateway] 致命错误: 从 confs 获取 gateway.ini 失败: " << err << std::endl; - return 1; + /* 从 confs 拉取 gateway.ini */ + std::cout << "[gateway] 从 confs (" << _CONFS_HOST << ":" << _CONFS_PORT + << ") 拉取 gateway.ini ..." << std::endl; + pmc::net::MsgClient confs_client(_CONFS_HOST, _CONFS_PORT, "gateway"); + json::object req; + req["_route"] = "get"; + req["name"] = "gateway.ini"; + std::string resp_str = confs_client.call( + json::serialize(req), /*timeout=*/5.0, + /*retry_on_refused=*/true); + auto resp = json::parse(resp_str).as_object(); + if (!resp.at("success").as_bool()) { + std::string err = "unknown"; + if (auto* ev = resp.if_contains("error")) { + if (ev->is_string()) err = ev->as_string().c_str(); } - std::string ini_content = resp.at("data").as_object().at("content").as_string().c_str(); - cf = ConfIni::fromString(ini_content); - std::cout << "[gateway] 配置来源: confs (gateway.ini @ " - << _CONFS_HOST << ":" << _CONFS_PORT << ")" << std::endl; + std::cerr << "[gateway] 致命错误: 从 confs 获取 gateway.ini 失败: " << err << std::endl; + return 1; } + std::string ini_content = resp.at("data").as_object().at("content").as_string().c_str(); + cf = ConfIni::fromString(ini_content); + std::cout << "[gateway] 配置来源: confs (gateway.ini @ " + << _CONFS_HOST << ":" << _CONFS_PORT << ")" << std::endl; } catch (ConfIni::IniConfigureFileParseException &e) { std::cerr << "ERROR: " << e.what() << std::endl;