feat(gomsg): pymsg/libmsg 协议的 Go 实现,waf logclient 抽取接入

- 新增 gomsg/:帧协议(4B 大端长度 + UTF-8,64MiB 上限)、MsgClient/MsgServer、
  RpcClient/RpcServer(_route 分发、错误脱敏、panic 恢复、json.Number 保精度)、
  AsyncQueue[T]、LogClient(集中式日志,级别过滤 + fail-safe),线协议与 Python/C++ 字节级兼容
- waf/internal/logclient 改为 gomsg 薄适配层(336→20 行),main/engine/proxy 零改动
- gomsg 26 项单测全绿(-race);waf 编译通过
This commit is contained in:
qing committed 2026-08-23 11:21:40 +00:00
1 parent 0ed03ac095
commit c4154d4db9
16 files changed
+1624 -317

No files matched your search

+224
View File
@@ -0,0 +1,224 @@
package gomsg
import (
"bytes"
"encoding/json"
"fmt"
"regexp"
"strings"
"sync"
"time"
)
// JSON RPC 约定层(与 Python pymsg.rpc / C++ libmsg RpcClient/RpcServer 兼容):
//
// 请求: {"_route": "<路由名>", ...业务字段}
// 响应: {"success": true, "data": ...}
// {"success": false, "error": "..."}
//
// 数字一律以 json.Number 保留原始精度(避免 int64 经 float64 丢失)。
// RpcError 远端返回 success=false 的业务错误,或响应不符合 RPC 约定。
type RpcError struct {
msg string
}
func (e *RpcError) Error() string { return e.msg }
// RpcHandler 处理一条 RPC 请求,返回值放入 data,错误包装为 success=false。
type RpcHandler func(req map[string]any) (any, error)
// RpcClient JSON RPC 客户端(复用 MsgClient 长连接)。
type RpcClient struct {
client *MsgClient
}
// NewRpcClient 创建客户端。timeout 为 0 时使用 DefaultTimeout。
func NewRpcClient(addr string, timeout time.Duration) *RpcClient {
return &RpcClient{client: NewMsgClient(addr, timeout)}
}
// Call 调用指定路由,返回响应中的 data 字段(可能为 nil)。
// success=false 或非法响应时返回 *RpcError。
func (c *RpcClient) Call(route string, payload map[string]any) (any, error) {
req := make(map[string]any, len(payload)+1)
for k, v := range payload {
req[k] = v
}
req["_route"] = route
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
resp, err := c.client.Call(body)
if err != nil {
return nil, err
}
var obj map[string]any
dec := json.NewDecoder(bytes.NewReader(resp))
dec.UseNumber()
if err := dec.Decode(&obj); err != nil || obj == nil {
return nil, &RpcError{msg: fmt.Sprintf("响应不是合法 JSON: %v", err)}
}
success, ok := obj["success"].(bool)
if !ok {
return nil, &RpcError{msg: fmt.Sprintf("非法 RPC 响应: %s", truncate(resp))}
}
if !success {
errMsg, _ := obj["error"].(string)
return nil, &RpcError{msg: errMsg}
}
return obj["data"], nil
}
// Close 关闭底层连接。
func (c *RpcClient) Close() error { return c.client.Close() }
// RpcServer JSON RPC 服务端:基于 MsgServer 的路由分发与响应包装。
type RpcServer struct {
*MsgServer
routes map[string]RpcHandler
mu sync.RWMutex
// OnError 可选钩子:handler 抛错时调用(用于记日志)。
OnError func(route string, err error)
// JSONMarshal 响应序列化函数,默认 json.Marshal(对应 Python 的 json_default)。
JSONMarshal func(v any) ([]byte, error)
}
// NewRpcServer 创建 RPC 服务端(不监听,需调用 Start)。
func NewRpcServer(addr string) *RpcServer {
s := &RpcServer{routes: make(map[string]RpcHandler)}
s.MsgServer = NewMsgServer(addr, s.dispatch)
return s
}
// AddRoute 注册路由处理函数。
func (s *RpcServer) AddRoute(name string, fn RpcHandler) {
s.mu.Lock()
defer s.mu.Unlock()
s.routes[name] = fn
}
// dispatch 解析请求、查找路由、包装响应;任何输入错误都不向客户端回显内部细节。
// handler 内 panic 会被恢复并包装为 success=false(对应 Python 的 except Exception)。
func (s *RpcServer) dispatch(payload []byte) (reply []byte) {
defer func() {
if r := recover(); r != nil {
err := fmt.Errorf("handler panic: %v", r)
if s.OnError != nil {
s.OnError("", err)
}
reply = s.reply(false, nil, sanitizeError(err.Error()))
}
}()
req, err := decodeRequest(payload)
if err != nil {
return s.reply(false, nil, "invalid JSON request")
}
routeVal, has := req["_route"]
delete(req, "_route")
route := ""
switch r := routeVal.(type) {
case string:
route = r
case nil:
// 保持 Python 行为:缺省 _route 时按空路由查找(未知路由)
default:
if has {
route = fmt.Sprintf("%v", r)
}
}
s.mu.RLock()
handler := s.routes[route]
s.mu.RUnlock()
if handler == nil {
return s.reply(false, nil, "未知路由: "+route)
}
data, err := handler(req)
if err != nil {
if s.OnError != nil {
s.OnError(route, err)
}
return s.reply(false, nil, sanitizeError(err.Error()))
}
return s.reply(true, data, "")
}
func (s *RpcServer) reply(success bool, data any, errMsg string) []byte {
resp := map[string]any{"success": success}
if data != nil {
resp["data"] = data
}
if errMsg != "" {
resp["error"] = errMsg
}
marshal := s.JSONMarshal
if marshal == nil {
marshal = json.Marshal
}
out, err := marshal(resp)
if err != nil {
out, _ = json.Marshal(map[string]any{
"success": false,
"error": sanitizeError(err.Error()),
})
}
return out
}
// decodeRequest 解析请求 JSON:UseNumber 保留整数精度;
// Go 的 encoding/json 内置 10000 层嵌套上限,数字位数字符串化保存,
// 输入大小又受帧协议 64 MiB 上限约束,天然规避深度/超大数字 DoS。
func decodeRequest(payload []byte) (map[string]any, error) {
dec := json.NewDecoder(bytes.NewReader(payload))
dec.UseNumber()
var req map[string]any
if err := dec.Decode(&req); err != nil {
return nil, err
}
if req == nil {
return nil, fmt.Errorf("empty request")
}
return req, nil
}
// pathRe 匹配绝对路径(如 /opt/micro_servers/db/xxx.py),用于脱敏异常信息。
var pathRe = regexp.MustCompile(`/[A-Za-z0-9_.\-]+(?:/[A-Za-z0-9_.\-]+)*`)
// sanitizeError 脱敏异常信息中的绝对路径等内部细节,保留业务错误语义
// (对齐 Python pymsg._sanitize_error:前导字符为单词字符时不匹配)。
func sanitizeError(msg string) string {
if msg == "" {
return msg
}
var b strings.Builder
last := 0
for _, idx := range pathRe.FindAllStringIndex(msg, -1) {
start, end := idx[0], idx[1]
if start > 0 && isWordChar(msg[start-1]) {
continue
}
b.WriteString(msg[last:start])
b.WriteString("<redacted>")
last = end
}
b.WriteString(msg[last:])
return b.String()
}
func isWordChar(c byte) bool {
return c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
}
func truncate(b []byte) string {
const limit = 200
if len(b) <= limit {
return string(b)
}
return string(b[:limit]) + "..."
}