feat(log): 前后端共用 request_id 与四时间点时钟校准
- X-Request-Id 请求头带合法 UUID 则沿用为 request_id(严格格式白名单防注入),否则自生成 12 位 hex - 响应头新增 X-Server-Received-Ms / X-Server-Sent-Ms 供前端时钟校准 - CORS Allow-Headers 增加 X-Request-Id,Expose-Headers 显式列出(credentials 下不认 *) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+39
-6
@@ -208,6 +208,30 @@ func truncateUA(s string, max int) string {
|
||||
return string(r[:max])
|
||||
}
|
||||
|
||||
// normalizeRequestId 校验前端传入的 X-Request-Id。
|
||||
// 仅接受标准 UUID 形态(36 位,8-4-4-4-12,hex+连字符),统一转小写;
|
||||
// 其余(超长、控制符、任意注入串)一律丢弃返回空,保证日志字段安全且低基数可控。
|
||||
func normalizeRequestId(v string) string {
|
||||
if len(v) != 36 {
|
||||
return ""
|
||||
}
|
||||
for i := 0; i < 36; i++ {
|
||||
c := v[i]
|
||||
switch i {
|
||||
case 8, 13, 18, 23:
|
||||
if c != '-' {
|
||||
return ""
|
||||
}
|
||||
default:
|
||||
isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
|
||||
if !isHex {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.ToLower(v)
|
||||
}
|
||||
|
||||
// shortID 生成 n 字节随机 hex(n=6 → 12 位)
|
||||
func shortID(n int) string {
|
||||
b := make([]byte, n)
|
||||
@@ -557,13 +581,20 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
unescapeUrl = req.RequestURI
|
||||
}
|
||||
|
||||
// 请求级追踪:sid=sessionId 前 12 位(脱敏),request_id 回写响应头
|
||||
requestId := shortID(6)
|
||||
// 请求级追踪:sid=sessionId 前 12 位(脱敏),request_id 回写响应头。
|
||||
// 前后端共用一个 request_id:前端带合法 X-Request-Id(UUID)则沿用,非法/缺失才服务端自生成,
|
||||
// 严格格式校验杜绝日志注入。
|
||||
requestId := normalizeRequestId(req.Header.Get("X-Request-Id"))
|
||||
if requestId == "" {
|
||||
requestId = shortID(6)
|
||||
}
|
||||
sid := sessionId
|
||||
if len(sid) > 12 {
|
||||
sid = sid[:12]
|
||||
}
|
||||
w.Header().Set("X-Request-Id", requestId)
|
||||
// 服务端接收时间(ms),与 View() 写出的 X-Server-Sent-Ms 组成四时间点时钟校准
|
||||
w.Header().Set("X-Server-Received-Ms", strconv.FormatInt(nowUnixTime.UnixMilli(), 10))
|
||||
reqLog := that.Log.WithFields("sid", sid, "request_id", requestId)
|
||||
dbCopy := that.Db
|
||||
dbCopy.Log = reqLog
|
||||
@@ -716,8 +747,9 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
||||
//header.Set("Access-Control-Allow-Origin", "*")
|
||||
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
|
||||
header.Set("Access-Control-Allow-Credentials", "true")
|
||||
header.Set("Access-Control-Expose-Headers", "*")
|
||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie")
|
||||
// credentials 模式下浏览器不认 "*",必须显式列出可读响应头
|
||||
header.Set("Access-Control-Expose-Headers", "X-Request-Id,X-Server-Received-Ms,X-Server-Sent-Ms")
|
||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie,X-Request-Id")
|
||||
|
||||
if sessionId != "" {
|
||||
//跨域允许需要设置cookie的允许跨域https才有效果
|
||||
@@ -763,8 +795,9 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
||||
|
||||
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
|
||||
header.Set("Access-Control-Allow-Credentials", "true")
|
||||
header.Set("Access-Control-Expose-Headers", "*")
|
||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie")
|
||||
// credentials 模式下浏览器不认 "*",必须显式列出可读响应头
|
||||
header.Set("Access-Control-Expose-Headers", "X-Request-Id,X-Server-Received-Ms,X-Server-Sent-Ms")
|
||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie,X-Request-Id")
|
||||
|
||||
if sessionId != "" {
|
||||
//跨域允许需要设置cookie的允许跨域https才有效果
|
||||
|
||||
Reference in New Issue
Block a user