84 lines
2.6 KiB
Go
84 lines
2.6 KiB
Go
// Package requestid carries a per-request identifier across the gin handler
|
|
// chain.
|
|
//
|
|
// The middleware reads the inbound `X-Request-ID` header, generates a UUIDv4
|
|
// when absent, stores the value on the gin context, and reflects it on the
|
|
// response. Downstream code retrieves the identifier through FromContext.
|
|
package requestid
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Header is the canonical case-correct header name carrying the request id.
|
|
const Header = "X-Request-ID"
|
|
|
|
// ginContextKey is the gin.Context key under which the resolved request id is
|
|
// stored. The value is a string. The key is exported in lowercase form so that
|
|
// it never collides with handler-level keys; consumers should prefer
|
|
// FromContext rather than reading the gin context directly.
|
|
const ginContextKey = "backend.request_id"
|
|
|
|
// requestIDContextKey is the unexported context.Context key used when the
|
|
// resolved request id is propagated outside gin (background goroutines,
|
|
// downstream client calls). The unexported value type prevents accidental
|
|
// collisions across packages.
|
|
type requestIDContextKey struct{}
|
|
|
|
// Middleware returns the gin middleware that resolves and propagates the
|
|
// request id.
|
|
func Middleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
requestID := strings.TrimSpace(c.GetHeader(Header))
|
|
if requestID == "" {
|
|
requestID = uuid.NewString()
|
|
}
|
|
|
|
c.Set(ginContextKey, requestID)
|
|
c.Writer.Header().Set(Header, requestID)
|
|
c.Request = c.Request.WithContext(WithValue(c.Request.Context(), requestID))
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// FromContext returns the request id stored on ctx by Middleware. The boolean
|
|
// reports whether an id was found. Consumers must always check the boolean
|
|
// before using the returned string.
|
|
func FromContext(ctx context.Context) (string, bool) {
|
|
if ctx == nil {
|
|
return "", false
|
|
}
|
|
value, ok := ctx.Value(requestIDContextKey{}).(string)
|
|
if !ok || value == "" {
|
|
return "", false
|
|
}
|
|
return value, true
|
|
}
|
|
|
|
// FromGin returns the request id stored on the gin context by Middleware. The
|
|
// boolean reports whether an id was found.
|
|
func FromGin(c *gin.Context) (string, bool) {
|
|
if c == nil {
|
|
return "", false
|
|
}
|
|
raw, ok := c.Get(ginContextKey)
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
value, ok := raw.(string)
|
|
if !ok || value == "" {
|
|
return "", false
|
|
}
|
|
return value, true
|
|
}
|
|
|
|
// WithValue stores requestID on ctx under the package-private context key.
|
|
// Exposed primarily for tests that build a context outside the middleware.
|
|
func WithValue(ctx context.Context, requestID string) context.Context {
|
|
return context.WithValue(ctx, requestIDContextKey{}, requestID)
|
|
}
|