requestctx

package
v0.0.0-...-8c930d8 Latest Latest
Warning

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

Go to latest
Published: Nov 11, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Index

Constants

View Source
const (
	KeyRoute        = "route"
	KeyPath         = "path"
	KeyMethod       = "method"
	KeyParams       = "params"
	KeyRequiresAuth = "requires_auth"
	KeyUsername     = "username"
	KeyUserID       = "user_id"
	KeySessionID    = "session_id"
	KeyTenantID     = "tenant_id"
)

The keys the request's own fields are stored under on its gin context, and the log fields carrying the same values. KeyRoute names the matched route pattern ("/api/users/:id") and KeyPath the concrete request path ("/api/users/42"): both identify the same request, the pattern has low cardinality and groups every request of one endpoint together, the path pins down a single request, so log aggregation and alerting key on the route, never on the path. KeyMethod names the request verb, which separates the actions one route pattern serves; a gRPC call carries the route and verb of the action its rpc stands for, and the full method as its path. KeyParams holds the names of the route's parameters on the gin context and the parameters as one object in the log, and KeyRequiresAuth whether the action requires authentication. KeyUsername, KeyUserID and KeySessionID name the authenticated caller and the session the request carries, and KeyTenantID the tenant the request acts in: whatever establishes the caller on the gin context writes them, and FromGin reads them as they stand into the Metadata the service context and the logs carry.

Variables

This section is empty.

Functions

func ApplyTrustedProxies

func ApplyTrustedProxies(engine *gin.Engine, configured []string) error

ApplyTrustedProxies declares to engine which peers may speak for the client, the stance ClientIP takes for the gRPC listener.

Gin trusts every peer out of the box, which makes the client address the value the caller wrote into X-Forwarded-For — an address the request picks for itself. That address reaches rate limiter keys, audit records, session records and the access log, so the stance here is to trust nothing by default: an unconfigured deployment reads the address off the connection, which no header can rewrite. A deployment behind a load balancer or ingress names that network in server.trusted_proxies, the configured entries, and the forwarding headers of those peers alone are then believed. An entry that cannot be read fails, for startup to stop instead of quietly leaving the process on a stance nobody chose.

func ClientIP

func ClientIP(peer netip.Addr, proxies []netip.Prefix, header func(name string) string) netip.Addr

ClientIP resolves the client address of a call that arrived from peer under the stance proxies, the networks whose peers may speak for the client, reading the forwarding headers of the call through header by name. It names the client gin names for the HTTP listener under the same stance (gin.Context.ClientIP): a peer outside the networks speaks for itself; from a peer inside them X-Forwarded-For is read from its right end, skipping the proxies, down to the first address that is not one, or the leftmost when every hop is one; a header holding something that is no address is passed over for the next, X-Real-IP, and when no header yields an address the peer is the client.

The resolution is the framework's own rather than the realip interceptor of go-grpc-middleware, which names a different client in three cases of that rule: it answers the peer when X-Forwarded-For is absent or malformed, without reading X-Real-IP, and when every hop of X-Forwarded-For is a proxy.

func GinClientIP

func GinClientIP(c *gin.Context) string

GinClientIP returns the client address gin resolved for the request, forwarding headers included, from the request's memoized connection fields.

func GinQuery

func GinQuery(c *gin.Context) url.Values

GinQuery returns the request's parsed query parameters, parsing them on the first call and reusing that result for the rest of the request.

url.URL.Query re-parses the raw query string on every call. Only the parsed values are memoized, nothing else: they derive from the request URL alone, which cannot change for the lifetime of a request, so the memo is correct no matter where in the middleware chain the first call happens.

The returned map is owned by this package and never written after it is stored. Metadata getters hand out clones, so callers of the public API cannot reach it; framework code reading it directly must treat it as read-only.

func GinQueryStrict

func GinQueryStrict(c *gin.Context) (url.Values, error)

GinQueryStrict parses the request's query string with the error url.URL.Query silently drops, and memoizes the parsed values on the gin context for GinQuery and everything built on it to reuse.

It exists for the strict-query gate: the gate must see the parse error to reject malformed input, while every consumer after it should reuse the parse instead of paying for another one. The memo is written unconditionally — the gate runs before anything else parses the query, and overwriting with the authoritative parse keeps a memo an earlier lenient parse may have stored from surviving past the gate.

func QueryValues

func QueryValues(ctx context.Context) url.Values

QueryValues returns the request's parsed query parameters for read-only framework use, without the defensive copy Metadata.Query makes for arbitrary callers.

The framework's own query parsers only read the values, and several of them run on every list request — one per query argument a database query is built from — so each paying for a deep clone would multiply the very cost the memoized parse removes. Callers must not modify the returned map; code outside the framework goes through Metadata.Query and gets a clone.

func TraceIDOf

func TraceIDOf(candidate string) string

TraceIDOf returns the trace id a request carries in candidate, the value of its X-Trace-ID header or metadata, when it is a W3C trace id, 32 lower-case hex digits that are not all zero, surrounding space aside; and a new id of the same form otherwise. It is the one rule both listeners read the header by when tracing is off; with tracing on the propagator applies the same parse, so a request carries the same kind of id whichever way it was read, and text that is not an id never reaches a log, a statement comment or a response header.

func TrustedProxies

func TrustedProxies(configured []string) ([]netip.Prefix, error)

TrustedProxies turns the entries of server.trusted_proxies into the networks whose peers may speak for the client, for both listeners to believe the same forwarding headers from the same peers: a CIDR block stays as it is, and a lone address names the network of itself alone, so an entry reads the same way on either listener. Surrounding spaces are trimmed and blank entries dropped, so that a list written as "10.0.0.0/8, 192.168.0.0/16" and an environment variable set to nothing both parse. An entry that is neither fails, for startup to stop on a stance nobody chose.

func WithMetadata

func WithMetadata(ctx context.Context, meta Metadata) context.Context

WithMetadata returns a context carrying immutable request metadata.

The metadata is stored as given rather than rebuilt from its own getters. Metadata is immutable: its fields are unexported, nothing writes them after construction, and the two getters exposing maps hand out clones, so no caller can reach the stored maps to change them. Rebuilding here would clone those maps twice more on every request to produce a copy nothing could tell apart from the original.

Types

type Fields

type Fields struct {
	Route      string
	Path       string
	Method     string
	Username   string
	UserID     string
	SessionID  string
	TenantID   string
	Params     map[string]string
	Query      url.Values
	RawQuery   string
	RequestURI string
	ClientIP   string
	UserAgent  string
	Host       string
	TLS        bool

	RequiresAuth bool
}

Fields contains request metadata fields for non-gin callers and tests.

RawQuery is optional: when it is empty and Query is not, New re-encodes Query to fill it.

type Metadata

type Metadata struct {
	// contains filtered or unexported fields
}

Metadata contains immutable request-scoped fields shared by logging and lower-level infrastructure.

Route and path are two identities of the same request and both are kept: the route is the matched route pattern ("/api/users/:id") that groups every request of one endpoint together, the path is the concrete request path ("/api/users/42") that pins down a single request. Consumers aggregating requests must use the route; the path only locates one of them. The method is the request verb, which separates the actions one route pattern serves and neither of the other two distinguishes.

A call over gRPC carries the same three: the route and method are the ones the registration described the call's action with, the route and HTTP method the action is served at over HTTP (STREAM for a Stream action), and the path is the full method of the call, its target on the wire, as is the request URI. A call whose method the registration did not describe carries the full method as its route and POST as its method.

The client address, user agent, host and TLS flag describe the connection the request arrived on. They are carried here rather than read off the HTTP request on demand, so that a context built without an HTTP request -- one a transport other than HTTP constructs -- answers for them all the same. So is whether the action requires authentication, which the HTTP listener marks on the gin context and another transport knows from its own registration.

func FromContext

func FromContext(ctx context.Context) Metadata

FromContext extracts request metadata from ctx.

func FromGin

func FromGin(c *gin.Context) Metadata

FromGin extracts Metadata from gin.Context.

Route and path are read straight off the gin context instead of through context keys another middleware has to set first, so metadata is complete no matter where in the handler chain it is taken. The route is empty for requests gin did not match to a registered route (NoRoute, NoMethod); the path stays populated in those cases and identifies the request on its own.

Metadata is constructed several times per request — the controller span, every service context, and every database handle each build one — so the expensive parts, parsing the query string, building the route parameter map and resolving the connection fields, are memoized on the gin context by GinQuery, ginParams and ginConnection. Identity fields are deliberately read fresh on every call, and so is the authentication requirement the auth marker middleware sets: they are cheap context lookups, and re-reading them keeps a construction that runs before the identity middleware from freezing empty identity into the constructions that follow it.

The struct is built without New: New defensively clones the params and query maps of its caller, while both maps here are the package's own memoized values, which nothing writes after they are stored.

func New

func New(fields Fields) Metadata

New creates Metadata from explicit fields.

func (Metadata) ClientIP

func (m Metadata) ClientIP() string

ClientIP, UserAgent, Host and TLS describe the connection the request arrived on: the client address gin resolved, forwarding headers included; the User-Agent header; the host the request was addressed to; and whether it arrived over TLS, directly or as the forwarding headers of a proxy in front declare.

func (Metadata) Host

func (m Metadata) Host() string

func (Metadata) Method

func (m Metadata) Method() string

func (Metadata) Param

func (m Metadata) Param(key string) string

func (Metadata) Params

func (m Metadata) Params() map[string]string

func (Metadata) Path

func (m Metadata) Path() string

func (Metadata) Query

func (m Metadata) Query() url.Values

func (Metadata) RawQuery

func (m Metadata) RawQuery() string

RawQuery returns the query string as the client sent it ("a=1&b=2").

Request handling wants the query parsed into url.Values; logging wants the opposite. Logged as structured key-value pairs, every query key any client ever sends becomes a field in the log storage's mapping, and query keys are caller-controlled and unbounded: a misspelled parameter, a bracketed filter syntax and a scanner sending random parameters each add a permanent field, until the index reaches its field limit and silently rejects further entries. One raw string keeps the mapping at exactly one field, and keeps what was actually sent visible instead of a normalized reconstruction.

func (Metadata) RequestURI

func (m Metadata) RequestURI() string

RequestURI returns the request target as the client sent it, path and query string together and undecoded ("/api/users/42?tag=blue"). Path and RawQuery are its two parsed halves; this is the request line's own text, which the operation log keeps.

func (Metadata) RequiresAuth

func (m Metadata) RequiresAuth() bool

RequiresAuth reports whether the action the request is for requires authentication: over HTTP a route outside the public group, which the auth marker middleware flags on the gin context; over gRPC a method not described as public. It is what ServiceContext.RequiresAuth answers.

func (Metadata) Route

func (m Metadata) Route() string

func (Metadata) SessionID

func (m Metadata) SessionID() string

func (Metadata) TLS

func (m Metadata) TLS() bool

func (Metadata) TenantID

func (m Metadata) TenantID() string

func (Metadata) UserAgent

func (m Metadata) UserAgent() string

func (Metadata) UserID

func (m Metadata) UserID() string

func (Metadata) Username

func (m Metadata) Username() string

Jump to

Keyboard shortcuts

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