package main import ( "net/http" "net/http/httputil" "net/url" ) // initInvProxy builds a streaming reverse proxy to the inventory-service. // FlushInterval=-1 flushes writes immediately so SSE endpoints (the suitbuilder // search stream) work. Connection errors map to 503, mirroring the Python // service's "Inventory service unavailable". func (s *Server) initInvProxy(target string) error { u, err := url.Parse(target) if err != nil { return err } rp := httputil.NewSingleHostReverseProxy(u) rp.FlushInterval = -1 rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { s.log.Error("inventory proxy error", "err", err, "path", r.URL.Path) writeJSON(w, http.StatusServiceUnavailable, map[string]any{"detail": "Inventory service unavailable"}) } s.invProxy = rp return nil } // proxyInv returns a handler that rewrites the request path (via rewrite) and // forwards it to the inventory-service, preserving method, query, headers, and // body. The original /inv/* prefix etc. is mapped to the upstream path. func (s *Server) proxyInv(rewrite func(r *http.Request) string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if s.invProxy == nil { writeJSON(w, http.StatusServiceUnavailable, map[string]any{"detail": "Inventory service unavailable"}) return } r.URL.Path = rewrite(r) r.URL.RawPath = "" // force re-encode from the (decoded) Path s.invProxy.ServeHTTP(w, r) } } func (s *Server) registerProxyRoutes(mux *http.ServeMux) { mux.HandleFunc("GET /inventory/{character_name}", s.proxyInv(func(r *http.Request) string { return "/inventory/" + r.PathValue("character_name") })) mux.HandleFunc("GET /inventory-characters", s.proxyInv(func(r *http.Request) string { return "/characters/list" })) mux.HandleFunc("GET /search/items", s.proxyInv(func(r *http.Request) string { return "/search/items" })) mux.HandleFunc("GET /search/equipped/{character_name}", s.proxyInv(func(r *http.Request) string { return "/search/equipped/" + r.PathValue("character_name") })) mux.HandleFunc("GET /search/upgrades/{character_name}/{slot}", s.proxyInv(func(r *http.Request) string { return "/search/upgrades/" + r.PathValue("character_name") + "/" + r.PathValue("slot") })) mux.HandleFunc("GET /sets/list", s.proxyInv(func(r *http.Request) string { return "/sets/list" })) // /inv/test is a static liveness probe in the Python service. mux.HandleFunc("GET /inv/test", func(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{"message": "Inventory proxy route is working"}) }) // Generic catch-all proxy: /inv/{path...} -> {SVC}/{path}. Covers GET and // POST (incl. the SSE suitbuilder search). Registered for both methods. invAll := s.proxyInv(func(r *http.Request) string { return "/" + r.PathValue("path") }) mux.HandleFunc("GET /inv/{path...}", invAll) mux.HandleFunc("POST /inv/{path...}", invAll) }