主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批: 已推库 16 个文件 + 本批剩余文件)。 校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0); 全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
117 lines
3.5 KiB
Go
117 lines
3.5 KiB
Go
package gateway
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
apperr "git.yeij.top/AskaEth/Cyrene_v2/internal/errors"
|
|
)
|
|
|
|
// cors 为已配置允许列表中的来源设置 Access-Control-* 头,并
|
|
// 短路 OPTIONS 预检请求。空来源(同源或
|
|
// 非浏览器客户端)原样放行。
|
|
func (s *Server) cors(next http.Handler) http.Handler {
|
|
allowed, allowAll := s.allowedOrigins()
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if origin := r.Header.Get("Origin"); origin != "" && (allowAll || allowed[origin]) {
|
|
if allowAll {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
} else {
|
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
|
}
|
|
w.Header().Set("Vary", "Origin")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-API-Version")
|
|
w.Header().Set("Access-Control-Expose-Headers", voiceProviderHeader)
|
|
w.Header().Set("Access-Control-Max-Age", "600")
|
|
}
|
|
if r.Method == http.MethodOptions {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// allowedOrigins 把已配置的来源列表解析成查找集合,外加针对
|
|
// "*" 通配符的允许全部标志。
|
|
func (s *Server) allowedOrigins() (map[string]bool, bool) {
|
|
list := s.cfg.AllowedOrigins
|
|
if len(list) == 0 {
|
|
return map[string]bool{}, false
|
|
}
|
|
allowAll := false
|
|
out := make(map[string]bool, len(list))
|
|
for _, o := range list {
|
|
if o == "*" {
|
|
allowAll = true
|
|
continue
|
|
}
|
|
out[o] = true
|
|
}
|
|
return out, allowAll
|
|
}
|
|
|
|
// recover 把下游 handler 中的 panic 转成 500 {error:...}
|
|
// 响应,而不是让进程崩溃,并记录该 panic。
|
|
func (s *Server) recover(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
s.log.Error("panic recovered", "panic", rec, "method", r.Method, "path", r.URL.Path)
|
|
writeError(w, apperr.ErrInternal)
|
|
}
|
|
}()
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// logging 为每个请求记录一行结构化日志(method、path、status、dur)。
|
|
func (s *Server) logging(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
|
|
next.ServeHTTP(rec, r)
|
|
durMs := time.Since(start).Milliseconds()
|
|
s.recordMetric(r.Method, r.URL.Path, rec.status, durMs)
|
|
s.log.Info("request",
|
|
"method", r.Method,
|
|
"path", r.URL.Path,
|
|
"status", rec.status,
|
|
"duration_ms", durMs,
|
|
)
|
|
})
|
|
}
|
|
|
|
// statusRecorder 捕获写入的状态,使 logging 中间件能报告
|
|
// 它。它转发可选的 Hijacker/Flusher 接口,因此 WebSocket
|
|
// 升级器(要求 http.Hijacker)与流式 handler 仍能工作。
|
|
type statusRecorder struct {
|
|
http.ResponseWriter
|
|
status int
|
|
}
|
|
|
|
// WriteHeader 在委托之前记录状态。
|
|
func (r *statusRecorder) WriteHeader(code int) {
|
|
r.status = code
|
|
r.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
// Hijack 在底层 writer 支持连接劫持时转发给它。
|
|
func (r *statusRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
if hj, ok := r.ResponseWriter.(http.Hijacker); ok {
|
|
return hj.Hijack()
|
|
}
|
|
return nil, nil, fmt.Errorf("gateway: hijack unsupported by %T", r.ResponseWriter)
|
|
}
|
|
|
|
// Flush 在底层 writer 支持 flush 时转发给它。
|
|
func (r *statusRecorder) Flush() {
|
|
if f, ok := r.ResponseWriter.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
}
|