middleware

package
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 2, 2026 License: MIT Imports: 29 Imported by: 0

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

View Source
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

func IdentityFromContext(ctx context.Context) (string, bool)

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 Register

func Register(name string, f Factory)

Register registers a middleware factory under name.

func Registered

func Registered(name string) bool

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

type Handler func(ctx context.Context, req interface{}) (interface{}, error)

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

type Middleware func(Handler) Handler

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.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL