mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-06-05 22:44:17 +00:00
68e1c4319c
Adds requeststate.go and threads a *requestState through the ServeHTTP -> processAuthorizedRequestRS -> forwardAuthorized path. rs is allocated once at the top of ServeHTTP, populates SessionData field snapshots under a SINGLE sd.sessionMutex.RLock, and caches the MetadataSnapshot. Downstream handlers read the cached fields instead of calling session.GetX() / t.metadataSnap() repeatedly. Why --- Under Yaegi each method dispatch (including RWMutex.RLock) costs ~1-5ms of interpreter overhead. SessionData getters each take an RLock on sd.sessionMutex; the previous hot path called 5-7 of them per request (GetAuthenticated, GetAccessToken, GetIDToken, GetRefreshToken, GetUserIdentifier, plus the same set again inside processAuthorizedRequest). With one batched RLock + cached fields, that drops to a single RLock for the whole handler chain. This is scoped — not a wholesale architectural refactor: * requestState is per-request (alloc at ServeHTTP entry, dropped on return). It is NOT a shared cache and never escapes the request. * The original processAuthorizedRequest is kept unchanged for any callers we don't migrate this round (bearer path, callback handlers, expired-token handlers). New code path is the RS-aware processAuthorizedRequestRS, which middleware.ServeHTTP now uses for the happy authenticated-and-not-needing-refresh case. * Cross-request caches (tokenCache, JWKCache, sessionEntries, sessionInvalidationCache) are unchanged. rs is additive, not a replacement. What this does NOT change ------------------------- * The refresh path still calls session.GetX() in middleware.go (handleExpiredToken, refreshToken, defaultInitiateAuthentication) because those flows can mutate session state and a stale rs would be wrong. * validateStandardTokens still has its own session.GetX() calls. Deep plumbing into the token-verification path is a follow-up. * No semantic changes to authentication, refresh, or session lifecycle — only the read path is optimised. All tests pass with -race; golangci-lint clean.
72 lines
2.6 KiB
Go
72 lines
2.6 KiB
Go
// Package traefikoidc provides OIDC authentication middleware for Traefik.
|
|
// requestState bundles read-mostly fields for a single ServeHTTP call.
|
|
package traefikoidc
|
|
|
|
import "net/http"
|
|
|
|
// requestState is a per-request context object allocated at the top of
|
|
// ServeHTTP and threaded through to downstream handlers. It caches values
|
|
// that would otherwise require a Yaegi-dispatched lock acquisition each time
|
|
// they're read:
|
|
//
|
|
// - The metadata snapshot (atomic.Value.Load once, not per-handler).
|
|
// - SessionData getter results (one RLock on sd.sessionMutex covers all
|
|
// fields, instead of 5-7 separate RLock/RUnlock pairs scattered through
|
|
// the handler chain).
|
|
//
|
|
// The struct is alloc'd at request entry, populated under at most one RLock
|
|
// of sd.sessionMutex, and discarded at request exit. It is NOT shared across
|
|
// requests and never written from another goroutine, so no synchronization
|
|
// on its fields is required.
|
|
//
|
|
// Cross-request global caches (tokenCache, JWKCache, sessionEntries,
|
|
// sessionInvalidationCache) remain — they're orthogonal. requestState's job
|
|
// is to eliminate redundant per-handler reads of values that don't change
|
|
// within a single request.
|
|
type requestState struct {
|
|
// Globals snapshotted once.
|
|
metadata *MetadataSnapshot
|
|
|
|
// SessionData fields snapshotted under one RLock. The pointer to the
|
|
// SessionData is retained so handlers that genuinely need to mutate
|
|
// (Save, Clear, etc.) still have access.
|
|
session *SessionData
|
|
|
|
authenticated bool
|
|
accessToken string
|
|
idToken string
|
|
refreshToken string
|
|
userIdentifier string
|
|
createdAtUnixSec int64
|
|
|
|
// Output: scheme/host/redirect path determined at top of ServeHTTP.
|
|
scheme string
|
|
host string
|
|
redirectURL string
|
|
|
|
// Carry the next handler so forwardAuthorized doesn't need to close over t.
|
|
next http.Handler
|
|
}
|
|
|
|
// captureSession populates requestState's SessionData-derived fields under a
|
|
// single RLock of sd.sessionMutex. Returns the populated rs for chaining.
|
|
//
|
|
// Replaces a sequence of SessionData.GetX() calls each of which acquires
|
|
// sd.sessionMutex.RLock(). Under Yaegi each RLock costs ~1-5ms of
|
|
// interpreter dispatch; batching saves the rest.
|
|
func (rs *requestState) captureSession(sd *SessionData) *requestState {
|
|
if sd == nil {
|
|
return rs
|
|
}
|
|
rs.session = sd
|
|
sd.sessionMutex.RLock()
|
|
rs.authenticated = sd.getAuthenticatedUnsafe()
|
|
rs.accessToken = sd.getAccessTokenUnsafe()
|
|
rs.idToken = sd.getIDTokenUnsafe()
|
|
rs.refreshToken = sd.getRefreshTokenUnsafe()
|
|
rs.userIdentifier = sd.getUserIdentifierUnsafe()
|
|
rs.createdAtUnixSec = sd.getCreatedAtUnsafe()
|
|
sd.sessionMutex.RUnlock()
|
|
return rs
|
|
}
|