// v1handlers.go:HTTP 兜底面 /v1/search /v1/read(与 MCP tools/call 共用同一内核)。 package gateway import ( "net/http" "onesvm.com/onesvm/browser-server/internal/contract" ) // handleV1Search POST /v1/search。 func (s *Server) handleV1Search(w http.ResponseWriter, r *http.Request, a *consumerAuth) { var in contract.SearchInput if err := jsonDecode(r, &in); err != nil { s.writeErr(w, r, contract.CodeUnavailable, "请求体非法 JSON: "+err.Error(), http.StatusBadRequest, "", nil) return } s.runAndRespond(w, r, a, runCtx{Intent: "search", Search: &in}) } // handleV1Read POST /v1/read。 func (s *Server) handleV1Read(w http.ResponseWriter, r *http.Request, a *consumerAuth) { var in contract.ReadInput if err := jsonDecode(r, &in); err != nil { s.writeErr(w, r, contract.CodeUnavailable, "请求体非法 JSON: "+err.Error(), http.StatusBadRequest, "", nil) return } s.runAndRespond(w, r, a, runCtx{Intent: "read", Read: &in}) } // runAndRespond 共用响应路径:管线 → 信封/错误输出。 func (s *Server) runAndRespond(w http.ResponseWriter, r *http.Request, a *consumerAuth, rc runCtx) { rc.Auth = a rc.W = w rc.R = r res := s.pipeline(rc) switch { case res.HTTPStatus != http.StatusOK && res.Body == nil: // 前置错误(401/402/403/429/503) w.Header().Set("Content-Type", "application/json") if res.RetryAfterS != nil { setRetryAfter(w, *res.RetryAfterS) } for k, v := range res.Headers { w.Header().Set(k, v) } w.WriteHeader(res.HTTPStatus) _, _ = w.Write(res.Body2) case res.Body != nil: // 完整信封(成功或 200 信封错误) for k, v := range res.Headers { w.Header().Set(k, v) } s.writeEnvelope(w, r, a, res.Body) default: s.writeErr(w, r, contract.CodeUpstream, "空管线结果", http.StatusInternalServerError, "", nil) } } // setRetryAfter Retry-After 头。 func setRetryAfter(w http.ResponseWriter, secs int) { w.Header().Set("Retry-After", itoa(secs)) }