HTTP Server and Middleware#
Flipt's HTTP layer is constructed in internal/cmd/http.go via NewHTTPServer. It wraps Go's net/http.Server with ReadTimeout: 10s, WriteTimeout: 30s, and MaxHeaderBytes: 1MB , using Chi v5 as the router. Each logical API surface gets its own gRPC-gateway runtime.ServeMux that proxies requests to Flipt's in-process gRPC services.
Route Structure#
Each API surface gets its own runtime.ServeMux mounted at a distinct path prefix :
| Mount path | Handler | Notes |
|---|---|---|
/api/v1 | gRPC-gateway (api) | Core Flipt API |
/evaluate/v1 | gRPC-gateway (evaluateAPI) | Evaluation service |
/internal/v1 | gRPC-gateway (evaluateDataAPI) | Data service; forwards Accept-Server-Version; uses HttpResponseModifier |
/internal/v1/analytics | gRPC-gateway (analyticsAPI) | Analytics service |
/ofrep | gRPC-gateway (ofrepAPI) | OFREP; custom error handler |
/auth/v1 | gRPC-gateway (via authenticationHTTPMount) | All auth methods; custom error handler |
/meta | runtime.NewServeMux | Metadata service |
/health | runtime.NewServeMux | gRPC health check |
/metrics | promhttp.Handler() | Prometheus metrics |
/debug | middleware.Profiler() | pprof; conditional on diagnostics config |
/ | http.FileServer | Embedded UI (when enabled) |
All API routes (except /metrics, /health, and the UI) are grouped under a sub-router that applies removeTrailingSlash and optional OTel tracing/audit middleware before mounting . CSRF protection (http.NewCrossOriginProtection) is conditionally applied to this same group when a session CSRF key is configured .
Global Middleware Chain#
Applied in registration order to the root Chi router :
- CORS (
go-chi/cors) β conditional oncfg.Cors.Enabled middleware.RequestIDβ injects a request ID headermiddleware.RealIPβ rewritesr.RemoteAddrfromX-Real-IP/X-Forwarded-For- Pretty-print handler β sets
Accept: application/json+prettywhen?prettyis present in the query string middleware.Compressβ gzip compression at default levelhttp_middleware.HandleNoBodyResponseβ suppresses response body for 204/304middleware.Recovererβ panic recovery
β οΈ No UTF-8 path validation exists in this chain. See Known Issue: UTF-8 Path Validation Gap below.
Gateway Construction (internal/gateway/gateway.go)#
NewGatewayServeMux wraps runtime.NewServeMux and applies shared options once (via sync.Once):
V1toV2MarshallerAdapterβ backwards-compat marshaller that handlesnullmap values rejected by the proto v2 marshaller (see issue #664)application/json+prettymarshaller β indented JSON output, activated by the?prettyquery param- Trace header forwarding β propagates
Traceparent,Tracestate, andBaggageinto gRPC metadata via a customIncomingHeaderMatcher
Per-mux options are merged on top of these shared options when creating specific gateway instances.
Custom Error Handlers#
OFREP (/ofrep)#
The OFREP ServeMux is initialized with ofrep_middleware.ErrorHandler, which translates gRPC status codes into the OpenFeature Remote Evaluation Protocol error schema :
codes.NotFoundβFLAG_NOT_FOUNDcodes.InvalidArgumentβINVALID_CONTEXT- all others β
GENERAL
The error body includes a key field populated from gRPC status details when present, and an errorDetails string from the status message .
Auth (/auth/v1)#
authenticationHTTPMount configures a dedicated ServeMux with authmiddleware.ErrorHandler. On a codes.Unauthenticated response, if a token cookie was present in the request, the error handler clears all session cookies (flipt_client_state and the token cookie) before delegating to the default gRPC-gateway error handler. This prevents user agents from re-sending an invalid session token.
The auth sub-router also wraps in authmiddleware.Handler, which intercepts PUT /auth/v1/self/expire to clear cookies on explicit logout.
Known Issue: UTF-8 Path Validation Gap#
Affects: Confirmed v2.10.0; likely all v2 releases
Request paths containing percent-encoded bytes that are invalid UTF-8 (e.g., /%c0, /%ff, /%ed%a0%80) return 500 Internal Server Error with no log output at any log level .
Valid-UTF-8 paths like /caf%c3%a9 correctly return 404 for unknown routes. The divergence occurs because :
- Go's
net/httppercent-decodes the raw path, producing a non-UTF-8 byte sequence inr.URL.Path. - The middleware chain passes the request through untouched β no middleware validates path encoding.
- grpc-gateway's
ServeMuxor Go's internal routing panics/errors on the invalid string, producing a 500 viamiddleware.Recovererwithout logging.
Proposed fix: Add a middleware after RealIP that calls utf8.ValidString(r.URL.Path) and returns 400 Bad Request for invalid paths . There is no current workaround short of a WAF or reverse proxy that rejects malformed paths upstream.
Tracked in: GitHub Issue #6337