Files
homelab-frontend/internal/server/router.go
T
Admin Bot fc5c0e967d feat(cag): memory standing-context adapter, warmup semaphore, cache-aware routing
Issue #44: code-register memory adapter with standing-context resource; fix
dispatcher method-level Auth.Capability override (was always using verb-derived
capability, ignoring per-method Auth field).

Issue #45: per-backend bounded in-memory queue (VLLM_MAX_QUEUE_DEPTH=20) +
concurrency semaphore (VLLM_MAX_NUM_SEQS=4); warmup requests (X-Poimen-Warmup:
true) sleep WARMUP_DELAY_MS before queue entry; /metrics endpoint wired.

Issue #46: rendezvous-hash backend selection on X-Poimen-Cache-Key; round-robin
fallback; MarkFailed excludes unhealthy backends 30s; ModifyResponse captures
vllm-num-cached-tokens + emits structured log + increments counter.

Closes #44 Closes #45 Closes #46
2026-09-27 18:21:41 +08:00

92 lines
2.9 KiB
Go

package server
import (
"net/http"
"forgejo.riotpiao.com/rock/homelab-frontend/internal/serviceadapter"
"forgejo.riotpiao.com/rock/homelab-frontend/internal/webhook"
)
// Router implements an HTTP handler that routes health endpoints,
// ServiceAdapter X-Service requests, Temporal workflow endpoints,
// and other requests to upstream handlers.
type Router struct {
healthChecker *HealthChecker
dispatcher *serviceadapter.Dispatcher
temporalHandler http.Handler
upstreamHandler http.Handler
forgejoWebhook *webhook.ForgejoHandler
metricsHandler http.Handler
}
// NewRouter creates a new router with health endpoints.
// Health endpoints (/healthz and /readyz) are handled locally.
// X-Service requests are dispatched via ServiceAdapter CRD.
// Temporal endpoints (/workflow*) are routed to temporalHandler.
// All other paths are passed to the upstream handler.
func NewRouter(healthChecker *HealthChecker, dispatcher *serviceadapter.Dispatcher, temporalHandler http.Handler, upstreamHandler http.Handler) *Router {
return &Router{
healthChecker: healthChecker,
dispatcher: dispatcher,
temporalHandler: temporalHandler,
upstreamHandler: upstreamHandler,
forgejoWebhook: webhook.NewForgejoHandler(),
}
}
// SetMetricsHandler registers the Prometheus /metrics endpoint handler.
func (r *Router) SetMetricsHandler(h http.Handler) {
r.metricsHandler = h
}
// ServeHTTP implements http.Handler.
// Priority order:
// 1. /healthz and /readyz to health handlers
// 2. X-Service header to ServiceAdapter dispatcher (phase 8) - PREFERRED routing method
// 3. /workflow* to temporal handler - DEPRECATED: use X-Service: workflow instead
// 4. All other paths to upstream handler (phase 0-7)
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Health endpoints first
switch req.URL.Path {
case "/healthz":
LivenessHandler(r.healthChecker)(w, req)
return
case "/readyz":
ReadinessHandler(r.healthChecker)(w, req)
return
case "/metrics":
if r.metricsHandler != nil {
r.metricsHandler.ServeHTTP(w, req)
return
}
}
// X-Service (ServiceAdapter) routing - checked before path-based routing
// PREFERRED: All service routing should use X-Service header pattern for consistency,
// auth enforcement, and resource-based access control.
if req.Header.Get("X-Service") != "" {
if r.dispatcher != nil {
r.dispatcher.Dispatch(w, req)
return
}
}
// Forgejo webhook receiver — no auth, HMAC-verified by handler
if req.URL.Path == "/v1/webhooks/forgejo" {
r.forgejoWebhook.ServeHTTP(w, req)
return
}
// Workflow endpoints
// DEPRECATED: Path-based /workflow routing is legacy.
// New clients should use X-Service: workflow header instead for consistent auth.
switch req.URL.Path {
case "/workflow", "/workflow/health", "/workflow/metrics":
r.temporalHandler.ServeHTTP(w, req)
return
}
// Default: upstream handler (all other paths)
r.upstreamHandler.ServeHTTP(w, req)
}