Files
blackbird/libmsg/inc/us/RpcServer.hpp
T
qing e2f05f9a43 fix(libmsg): security audit round 4 - eliminate residual race + atomic server_fd
- P1-3: introduce ConnectionLock (mutex + atomic active) to close the residual
  CWE-362 race where _remove_client closes fd before reply/broadcast acquires
  the lock; senders now check active after locking and abort if fd was closed
- P2-7: make _server_fd std::atomic<int> to remove the data-race UB between
  stop() and _event_loop
- P2-8: validate on_message is non-null in MsgServer::start()
- P3-2: distinguish missing _route vs wrong-typed _route in RpcServer
- P3-3: set TCP_NODELAY/SO_SNDTIMEO before registering fd in _clients
- doc: add SO_SNDTIMEO precondition note to pmsg_send
2026-08-07 21:27:05 +08:00

208 lines
7.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
/** RpcServer — pymsg 线协议之上的 JSON RPC 服务端(C++ 版,对应 Python 版 pymsg.rpc.RpcServer)
*
* 约定(与 Python/Go 客户端字节级兼容):
* 请求: {"_route": "<路由名>", ...业务字段}
* 响应: {"success": true, "data": ...}
* {"success": false, "error": "..."}
*
* 基于 MsgServer 提供路由分发、JSON 解析、响应自动包装、异常脱敏等功能。
* handler 签名为 ``json::value(const json::object& req)``:
* - 返回值自动包装为 {"success": true, "data": ...}
* - 抛出的异常自动包装为 {"success": false, "error": "<脱敏后错误信息>"}
* - 服务不会因 handler 异常而崩溃
*
* 注意:此头文件依赖 boost::json,核心 libmsg 库保持零外部依赖不变。
* 使用 RpcServer 时需要链接 -lboost_json。
*
* Usage:
* #include <us/RpcServer.hpp>
*
* pmc::net::RpcServer rpc("127.0.0.1", 8005, {.workers=4});
*
* rpc.add_route("ping", [](const json::object&) -> json::value {
* return "pong";
* });
*
* rpc.add_route("execute", [](const json::object& req) -> json::value {
* // 处理业务逻辑...
* return json::object{{"rows", json::array{}}};
* });
*
* rpc.start(); // 阻塞当前线程
*/
#include <us/MsgServer.hpp>
#include <boost/system/error_code.hpp>
#include <cctype>
#include <cstddef>
#include <functional>
#include <regex>
#include <string>
#include <unordered_map>
#include <boost/json.hpp>
namespace pmc {
namespace net {
namespace json = boost::json;
// ============================================================================
// RpcServer
// ============================================================================
/// JSON RPC 服务端:基于 MsgServer,自带路由分发与响应包装。
class RpcServer {
public:
/// 路由处理函数签名:接收请求参数 object,返回 data 值(任意 JSON 类型)。
using Handler = std::function<json::value(const json::object& req)>;
/// 错误回调:void(route_name, exception),handler 抛异常时调用(用于记日志)。
using ErrorCallback = std::function<void(const std::string& route, const std::exception& e)>;
/** 构造 RPC 服务端。
*
* @param addr 监听地址
* @param port 监听端口
* @param config MsgServer 配置(线程池/超时/连接数限制等)
* @param on_error 可选错误回调,handler 抛异常时被调用
*/
explicit RpcServer(const std::string& addr, int port,
MsgServerConfig config = {},
ErrorCallback on_error = {})
: _server(addr, port, config), _on_error(std::move(on_error)) {}
// ---- 路由注册 ----------------------------------------------------------
/** 注册路由处理函数。
*
* @param name 路由名(对应请求中的 _route 字段),不能为空
* @param handler 处理函数,签名 json::value(const json::object& req)
*/
void add_route(const std::string& name, Handler handler) {
if (name.empty()) {
throw MsgException("route name cannot be empty");
}
if (!handler) {
throw MsgException("route handler cannot be null");
}
_routes[name] = std::move(handler);
}
// ---- 启动 / 停止 -------------------------------------------------------
/// 启动服务(阻塞当前线程)。
void start() {
_server.start([this](int fd, const std::string& addr, const std::string& msg) {
this->_dispatch(fd, addr, msg);
});
}
/// 从另一线程停止服务。
void stop() {
_server.stop();
}
/// 获取底层 MsgServer 引用(可用于 broadcast 等高级操作)。
MsgServer& server() noexcept { return _server; }
private:
// ---- 分发与响应包装 ----------------------------------------------------
void _dispatch(int fd, const std::string& /*addr*/, const std::string& text) {
// 解析 JSON(限制嵌套深度防止栈溢出)
json::parse_options opts;
opts.max_depth = 32; // 与 Python 版一致
boost::system::error_code ec;
json::value req_val = json::parse(text, ec, {}, opts);
if (ec || !req_val.is_object()) {
_reply_error(fd, "invalid JSON request");
return;
}
json::object req = req_val.as_object();
// 提取 _route
std::string route;
if (auto it = req.find("_route"); it != req.end() && it->value().is_string()) {
route = it->value().as_string().c_str();
req.erase(it); // 从业务参数中移除 _route
} else if (it == req.end()) {
_reply_error(fd, "missing _route");
return;
} else {
_reply_error(fd, "_route must be a string");
return;
}
// 查找 handler
auto handler_it = _routes.find(route);
if (handler_it == _routes.end()) {
_reply_error(fd, "unknown route: " + route);
return;
}
// 执行 handler
try {
json::value result = handler_it->second(req);
_reply_success(fd, result);
} catch (const std::exception& e) {
if (_on_error) {
try {
_on_error(route, e);
} catch (...) {}
}
// 脱敏后返回:保留业务错误语义,过滤绝对路径等内部细节
try {
_reply_error(fd, _sanitize_error(e.what()));
} catch (...) {
// _sanitize_error 或序列化失败时,使用安全的固定文案
try { _reply_error(fd, "internal error"); } catch (...) {}
}
}
}
void _reply_success(int fd, const json::value& data) {
json::object resp;
resp["success"] = true;
resp["data"] = data;
_server.reply(fd, json::serialize(resp));
}
void _reply_error(int fd, const std::string& error) {
json::object resp;
resp["success"] = false;
resp["error"] = error;
_server.reply(fd, json::serialize(resp));
}
// ---- 错误信息脱敏 ------------------------------------------------------
// 匹配绝对路径(如 /opt/micro_servers/db/xxx.py 或 /usr/lib/...),替换为 <redacted>
// 不返回函数堆栈、SQL、文件路径等内部细节给客户端;完整异常由 on_error 钩子记录到服务端日志。
static std::string _sanitize_error(const std::string& msg) {
if (msg.empty()) return msg;
try {
// 匹配以 / 开头的绝对路径(含特殊字符如空格、括号、@、~ 等),替换为 <redacted>
static const std::regex path_re(R"(/\S+(?:/\S*)*)");
return std::regex_replace(msg, path_re, "<redacted>");
} catch (...) {
return msg; // 正则异常时返回原文
}
}
// ---- 成员 --------------------------------------------------------------
MsgServer _server;
std::unordered_map<std::string, Handler> _routes;
ErrorCallback _on_error;
};
} // namespace net
} // namespace pmc