Documentation
¶
Overview ¶
Package server is the proxy core: a data-plane listener that runs the negotiate → decode → one upstream call → encode pipeline, and an ops listener for /metrics, /health, /ready. The bundle is held behind an atomic.Pointer (set once at boot; reserved for future hot-swap).
Index ¶
- type Server
- func (s *Server) Bundle() *bundle.Bundle
- func (s *Server) DataHandler() http.Handler
- func (s *Server) Message(fullName string) (protoreflect.MessageDescriptor, error)
- func (s *Server) OpsHandler() http.Handler
- func (s *Server) Recover(next http.Handler) http.Handler
- func (s *Server) ReloadBundle() error
- func (s *Server) Run(ctx context.Context) error
- func (s *Server) SetBundle(b *bundle.Bundle)
- func (s *Server) SetLogger(l *slog.Logger)
- func (s *Server) WireDataServer(srv *http.Server)
- func (s *Server) WrapDataListener(l net.Listener) net.Listener
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
func (*Server) DataHandler ¶
DataHandler returns the data-plane handler used by Run. The chain is `markHandlerSeen → Recover → proxy`:
- markHandlerSeen records that the wavefront handler entered, so the ConnState callback knows a later StateClosed is NOT a stdlib-boundary failure. It wraps Recover (not the other way around) so a recovered panic still counts as a handler run — the resulting wavefront-shaped 500 envelope is not a stdlib boundary.
- Recover converts any panic in the request path into a typed `wavefront.v0.Error` envelope rather than crashing the process or dropping the connection.
- proxy is the negotiate → decode → upstream → encode pipeline.
Ops/metrics endpoints (OpsHandler) keep Go's default behavior — a fault in pprof or /metrics surfaces normally and the stdlib-boundary detector is not wired on that listener.
func (*Server) Message ¶
func (s *Server) Message(fullName string) (protoreflect.MessageDescriptor, error)
Message implements adapter.MessageResolver against the current bundle.
func (*Server) OpsHandler ¶
OpsHandler serves the ops surface: Prometheus /metrics, liveness/readiness probes (/health, /ready), and pprof. It is intentionally bound to the ops listener (WAVEFRONT_METRICS_ADDR) only — pprof must never be reachable on the data plane, and is registered explicitly on this mux, not the global DefaultServeMux.
func (*Server) Recover ¶ added in v0.5.0
Recover wraps an http.Handler so that any panic inside it is converted to a typed wavefront wire-error response — HTTP 500 with the `X-Wavefront-Error: internal_error` header and a `wavefront.v0.Error` protobuf body — instead of crashing the process or returning a connection reset to the client.
The recovered panic value is recorded on the proxy's `errors` metric (code=internal_error, contract_version=unknown, transform_outcome="") and logged at Error level with the captured stack so the operator has the full forensic trail. The client only ever sees the fixed envelope — the panic value and the stack trace never reach the wire.
The envelope's X-Wavefront-Contract-Version echoes the raw client-sent header (or `unknown` when the client sent none); the metric label stays at `unknown` to keep Prometheus cardinality bounded.
Two cases are deliberately excluded from envelope emission:
- http.ErrAbortHandler — the stdlib's sentinel for "abort the connection without writing a response or logging." Used by http.MaxBytesReader, timeout handlers, etc. The contract requires re-panicking so the http.Server's own handling takes over; we must not log or write.
- the handler already wrote a status or body before panicking — emitting a wire-error envelope on top would corrupt the response. In that case we still log and count, but skip the envelope.
This middleware is wired onto the data-plane handler only; ops/metrics (handled by OpsHandler) keep Go's default behavior so a fault in pprof or /metrics surfaces normally.
func (*Server) ReloadBundle ¶ added in v0.4.0
ReloadBundle re-reads the bundle directory configured on the server and, on success, atomically swaps the live bundle. On failure, the previous bundle is preserved and the error is both logged and returned so the caller (typically a SIGHUP handler) can decide whether to react further. In-flight requests continue against the bundle they observed at the start of the request — the proxy reads the bundle pointer once per request.
func (*Server) SetLogger ¶ added in v0.4.0
SetLogger overrides the structured logger used by the request path. The default is slog.Default(). Tests use this to capture per-request log lines.
func (*Server) WireDataServer ¶ added in v0.5.0
WireDataServer attaches the data-plane connection-state callbacks (ConnContext + ConnState) to srv so the stdlib-boundary observability is in effect. Run() wires this on the production listener; tests that exercise the boundary detection wire it on an httptest.NewUnstartedServer's Config before Start(). The ops listener must NOT receive this wiring — only the data plane.
WireDataServer covers the half of the observability that lives on the *http.Server. The other half — wrapping the listener so every accepted conn is a *sniffingConn that captures the first response status line — is WrapDataListener. Run wires both; boundary tests wrap the httptest.Server.Listener before Start() so the same path is exercised.
func (*Server) WrapDataListener ¶ added in v0.5.0
WrapDataListener returns l wrapped so every Accept produces a *sniffingConn. The wrap lets the StateClosed branch of connState read the status code net/http wrote on the wire (e.g. 431, 417) and label the `wavefront_stdlib_boundary_total` counter with it. The wrap MUST be in place before the conn is passed to ConnContext / ConnState: net/http accepts a conn, calls ConnContext with it, then setState(c.rwc, StateNew) — so the `net.Conn` that lands in the ConnState callback is whatever Accept returned.