Documentation
¶
Overview ¶
Package middleware defines a protocol-agnostic middleware abstraction that serves both HTTP (gin) and gRPC transports. A single Middleware can be applied to both through the GinHandler and UnaryServerInterceptor adapters, so cross-cutting concerns (logging, tracing, metrics, recovery, timeout) are written once and reused across protocols.
Index ¶
- Constants
- func BodyLimit(maxBytes int64) gin.HandlerFunc
- func CORS(cfg CORSConfig) gin.HandlerFunc
- func GinHandler(m Middleware) gin.HandlerFunc
- func IdentityFromContext(ctx context.Context) (string, bool)
- func NewClientTracingInterceptor() grpc.UnaryClientInterceptor
- func Register(name string, f Factory)
- func Registered(name string) bool
- func StreamServerInterceptor(m Middleware) grpc.StreamServerInterceptor
- func UnaryClientInterceptor(m Middleware) grpc.UnaryClientInterceptor
- func UnaryServerInterceptor(m Middleware) grpc.UnaryServerInterceptor
- type CORSConfig
- type Factory
- type Handler
- type Middleware
- func Auth(key, identityKey string) Middleware
- func Build(ctx context.Context, builders ...MiddlewareBuilder) Middleware
- func Chain(outer Middleware, others ...Middleware) Middleware
- func ChainSuites(suites ...Suite) Middleware
- func Get(name string) (Middleware, error)
- func Logging(logger *slog.Logger) Middleware
- func Metrics(meter metric.Meter) Middleware
- func RateLimit(l ratelimit.Limiter) Middleware
- func Recovery() Middleware
- func Timeout(d time.Duration) Middleware
- func Tracing(tracer trace.Tracer) Middleware
- type MiddlewareBuilder
- type Suite
Constants ¶
const OnexErrorReasonKey = attribute.Key("onex.error.reason")
OnexErrorReasonKey carries the platform's own failure classification next to the semantic convention's error.type.
error.type is the standard slot for "what went wrong", but for HTTP its specified values are the status codes and for gRPC the status code names — both of which say a request failed with 403, not that it failed because the caller's membership had lapsed. The platform already has that second answer (errorsx.Reason, a small closed set like "Unauthenticated" or "ResourceExhausted.TooManyRequests"), and it is what the error panels group by. Keeping it in a vendor-namespaced attribute is what lets the metrics be standard *and* keep the attribution they had.
Variables ¶
This section is empty.
Functions ¶
func BodyLimit ¶
func BodyLimit(maxBytes int64) gin.HandlerFunc
BodyLimit returns a gin middleware that caps the request body size. It wraps the request body with http.MaxBytesReader so reading an oversized body fails with 413 instead of buffering it unboundedly in memory.
func CORS ¶
func CORS(cfg CORSConfig) gin.HandlerFunc
CORS returns a gin middleware implementing Cross-Origin Resource Sharing. It handles preflight OPTIONS requests and appends the relevant headers to actual responses.
func GinHandler ¶
func GinHandler(m Middleware) gin.HandlerFunc
GinHandler bridges a unified Middleware to a gin handler. It injects a Transporter (Kind=HTTP, Operation="METHOD /route/template") into the request context and lets the middleware wrap the remainder of the gin chain via c.Next().
func IdentityFromContext ¶
IdentityFromContext returns the authenticated identity injected by Auth, and whether it was present.
func NewClientTracingInterceptor ¶ added in v0.0.5
func NewClientTracingInterceptor() grpc.UnaryClientInterceptor
NewClientTracingInterceptor returns UnaryClientInterceptor without a middleware chain around it, which is the ordinary case and the one pkg/client installs on every gRPC client it dials.
It exists as a function rather than as a package-level variable so that each dial builds its own interceptor closure, and so that a caller reading pkg/client's dial options sees a name that says what is being added instead of a bare UnaryClientInterceptor(...) whose argument is an empty middleware.
func Registered ¶
Registered reports whether a middleware is registered under name.
func StreamServerInterceptor ¶
func StreamServerInterceptor(m Middleware) grpc.StreamServerInterceptor
StreamServerInterceptor bridges a unified Middleware to a gRPC server stream interceptor. The middleware's req parameter carries the server stream, so cross-cutting concerns (recovery, logging, metrics, tracing, timeout) written once against the unary Handler also wrap streaming RPCs. Business streaming handlers receive a stream whose context carries the injected Transporter.
func UnaryClientInterceptor ¶
func UnaryClientInterceptor(m Middleware) grpc.UnaryClientInterceptor
UnaryClientInterceptor bridges a unified Middleware to a gRPC client interceptor, so the same middleware can wrap outbound calls.
It carries the caller's trace in the outgoing metadata, which is the gRPC half of what pkg/client's HTTP paths do with a traceparent header: without it the callee's server span is the root of a trace of its own, and one request spanning two services reads as two unrelated traces. See middleware.Tracing.
It also draws the call as a client span. Propagation alone makes the callee's span a child of the caller's, which is enough for the trace to be one trace — but the edge it forms carries the callee's view of the call and nothing else, so a downstream that was slow to accept the connection, or that the caller retried three times, is indistinguishable from one that answered quickly. The client span is the caller's side of that edge: its duration is the time the caller spent, and its status is what the caller concluded.
func UnaryServerInterceptor ¶
func UnaryServerInterceptor(m Middleware) grpc.UnaryServerInterceptor
UnaryServerInterceptor bridges a unified Middleware to a gRPC server interceptor. It injects a Transporter (Kind=GRPC, Operation=full method) into the context so downstream middleware can read protocol metadata.
Types ¶
type CORSConfig ¶
type CORSConfig struct {
// AllowOrigins lists permitted origins. Empty means allow all ("*").
AllowOrigins []string
// AllowMethods lists permitted methods. Empty means a sensible default.
AllowMethods []string
// AllowHeaders lists permitted request headers. Empty means reflect the
// request's Access-Control-Request-Headers.
AllowHeaders []string
// ExposeHeaders lists headers exposed to the browser.
ExposeHeaders []string
// AllowCredentials permits credentials (cookies, auth headers).
AllowCredentials bool
// MaxAge is the preflight cache duration in seconds.
MaxAge int
}
CORSConfig configures CORS handling.
type Factory ¶
type Factory func() Middleware
Factory constructs a Middleware. Registered middlewares can be referenced by name (e.g. from route-level middleware configuration).
type Handler ¶
Handler is the unified request handler. Its signature is identical to grpc.UnaryHandler, which is what makes the gRPC bridge a near-zero cost.
func FromGin ¶
func FromGin(h gin.HandlerFunc) Handler
FromGin lifts a raw gin.HandlerFunc into a unified Handler, useful as the terminal node of a middleware chain.
type Middleware ¶
Middleware decorates a Handler, following the decorator and chain-of-responsibility patterns.
func Auth ¶
func Auth(key, identityKey string) Middleware
Auth returns a middleware that verifies a Bearer JWT from the request and injects the extracted identity into the context. It is protocol-agnostic: the token is read from the Authorization header (HTTP) or the equivalent gRPC metadata via the Transporter. identityKey names the JWT claim holding the subject; key is the HMAC signing key.
Use IdentityFromContext to retrieve the identity in downstream handlers.
func Build ¶
func Build(ctx context.Context, builders ...MiddlewareBuilder) Middleware
Build resolves builders into a single Middleware, preserving order (first builder outermost). Nil middlewares produced by a builder are skipped.
func Chain ¶
func Chain(outer Middleware, others ...Middleware) Middleware
Chain assembles middlewares so that the first argument is the outermost (executed first) and the last is the innermost (closest to the handler).
Chain(m1, m2, m3)(h) == m1(m2(m3(h)))
func ChainSuites ¶
func ChainSuites(suites ...Suite) Middleware
ChainSuites combines the middlewares of several suites into one, preserving suite order (the first suite's middlewares are outermost).
func Get ¶
func Get(name string) (Middleware, error)
Get returns the middleware registered under name.
func Logging ¶
func Logging(logger *slog.Logger) Middleware
Logging returns a middleware that logs one structured record per request, including the transport kind, operation and elapsed duration. It uses the provided logger, falling back to the global default.
func Metrics ¶
func Metrics(meter metric.Meter) Middleware
Metrics returns a middleware that records each request under the OpenTelemetry semantic conventions: http.server.request.duration and http.server.active_requests for HTTP, rpc.server.call.duration for gRPC.
Why the conventions' names and not the framework's own ¶
These instruments used to be onexmesh.request.{count,duration,inflight} with a `kind` label distinguishing HTTP from gRPC. They were self-describing and wrong in two ways a reader could not see from the source.
The first is that no consumer knows them. A duration histogram named for the conventions arrives in Prometheus as http_server_request_duration_seconds_* with the attribute set those conventions define, which is what a dashboard, an SLO rule or a chart someone imports already queries. A name invented here arrives as nothing anybody can find without reading this file.
The second is that a self-describing name was describing the wrong thing. The old instruments carried `kind` and `operation` — and `operation`, for HTTP, was the raw request path, so the busiest series in the platform was keyed by user id. See transport.Transporter.Operation: the bounded route template is what belongs in a label, and http.route is the conventions' name for it.
What each protocol gets ¶
HTTP is reported per route with its response status, its method and its protocol version; failures additionally carry error.type and the platform's own classification (see OnexErrorReasonKey). gRPC is reported per full method with its status code. A request the transport knows nothing about is still timed; it is only the attributes that are absent, not the measurement.
func RateLimit ¶
func RateLimit(l ratelimit.Limiter) Middleware
RateLimit returns a middleware that drops requests when the limiter denies them, returning errno.ErrRateLimited. The limiter is transport-agnostic, so the same limiter can guard both gRPC and HTTP endpoints. When the limiter supports AllowContext, the handler's context is honored so a canceled request does not block on the backing store.
func Recovery ¶
func Recovery() Middleware
Recovery returns a middleware that recovers from panics in downstream handlers, logs the panic with its stack trace, and converts it into a returned error so the server keeps running.
func Timeout ¶
func Timeout(d time.Duration) Middleware
Timeout returns a middleware that imposes a per-request deadline on the downstream handler. It follows the deadline (timeout) stability pattern.
func Tracing ¶
func Tracing(tracer trace.Tracer) Middleware
Tracing returns a middleware that starts a server span per request, named by the transport operation, and describes the request and its outcome with the OpenTelemetry semantic conventions.
The span continues the caller's trace when the request carries one: the incoming headers are extracted with the configured propagator before the span starts, so this service's span is a child of the caller's rather than the root of a new trace. Without that step every request begins a trace of its own, which is invisible in a single service and fatal to every question asked across two — the spans of one request land in Tempo as unrelated traces, and the trace_id this service writes into its logs (see SlogOptions' TraceIDHandler) names a trace the caller never sees.
The caller is not always another one of our services. An eBPF agent instrumenting the node (OBI) also writes a traceparent into the request, and extracting it here is what folds this process's spans into the trace that agent already started — so the network-level spans, the application spans and the log lines of one request share one trace_id.
What the span carries ¶
Until this was added the span had no attributes at all, which made the trace store unqueryable in the way that matters: one could see that a request took 300ms, but not which route it was, what it returned, or whether it failed — and span-derived metrics could only group by span name. The attributes come from the same builders as the metrics (see semconv.go), so the two destinations cannot drift into disagreeing about what happened.
type MiddlewareBuilder ¶
type MiddlewareBuilder func(ctx context.Context) Middleware
MiddlewareBuilder constructs a Middleware at run time given a context, allowing a middleware to be parameterized by values only available then (an event bus, per-service configuration, ...). It is modeled on kitex's endpoint.MiddlewareBuilder.
type Suite ¶
type Suite interface {
// Name returns a stable identifier for the suite.
Name() string
// Build returns the middlewares in outermost-first order.
Build() []Middleware
}
Suite groups a set of related middlewares into a reusable unit, so callers can attach a coherent bundle (e.g. "observability" = logging+tracing+metrics) in one step instead of assembling the chain manually.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package matcher provides route-level middleware selection: it maps a transport operation to the middlewares that apply to it, so different endpoints can carry different cross-cutting concerns (e.g.
|
Package matcher provides route-level middleware selection: it maps a transport operation to the middlewares that apply to it, so different endpoints can carry different cross-cutting concerns (e.g. |