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() } }