Add CORS support and response caching to aggregate endpoints
- Introduced CORS configuration options in config.example.yaml, allowing specification of allowed origins for cross-origin requests. - Enhanced the aggregate handler to support response caching with a configurable TTL, improving performance for repeated requests. - Updated the aggregate API to return a structured response indicating whether any upstream requests failed, enhancing error handling and response clarity. - Modified documentation in AGGREGATE.md and README.md to reflect the new CORS and caching features. - Added tests to validate the new functionality in the aggregate handler.
This commit is contained in:
@@ -28,6 +28,7 @@ type Gateway struct {
|
||||
log *slog.Logger
|
||||
transport *http.Transport
|
||||
promHandler http.Handler
|
||||
corsAllowed []string
|
||||
}
|
||||
|
||||
// NewGateway builds handlers and reverse proxies from parsed config.
|
||||
@@ -68,19 +69,68 @@ func NewGateway(p *config.Parsed, log *slog.Logger, geo *geoip.Service) (*Gatewa
|
||||
}
|
||||
g.proxies[s.Alias] = rp
|
||||
}
|
||||
g.agg = aggregate.NewHandler(p, &http.Client{Transport: t}, geo)
|
||||
var aggCacheTTL time.Duration
|
||||
if p.Config.Aggregate != nil && p.Config.Aggregate.CacheTTLMs > 0 {
|
||||
aggCacheTTL = time.Duration(p.Config.Aggregate.CacheTTLMs) * time.Millisecond
|
||||
}
|
||||
g.corsAllowed = append([]string(nil), p.Config.CorsAllowedOrigins...)
|
||||
g.agg = aggregate.NewHandler(p, &http.Client{Transport: t}, geo, aggCacheTTL)
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// Handler returns the root HTTP handler with middleware.
|
||||
func (g *Gateway) Handler() http.Handler {
|
||||
var h http.Handler = http.HandlerFunc(g.serve)
|
||||
h = g.withCORS(h)
|
||||
h = g.withWhitelist(h)
|
||||
h = g.withAccessLog(h)
|
||||
h = g.withMetrics(h)
|
||||
return h
|
||||
}
|
||||
|
||||
func (g *Gateway) withCORS(next http.Handler) http.Handler {
|
||||
if len(g.corsAllowed) == 0 {
|
||||
return next
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Vary", "Origin")
|
||||
origin := r.Header.Get("Origin")
|
||||
ok, allowOrigin := corsMatch(g.corsAllowed, origin)
|
||||
if ok {
|
||||
w.Header().Set("Access-Control-Allow-Origin", allowOrigin)
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Request-Id")
|
||||
w.Header().Set("Access-Control-Max-Age", "86400")
|
||||
}
|
||||
if r.Method == http.MethodOptions {
|
||||
if ok {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func corsMatch(allowed []string, origin string) (ok bool, allowOrigin string) {
|
||||
if origin == "" {
|
||||
return false, ""
|
||||
}
|
||||
for _, a := range allowed {
|
||||
a = strings.TrimSpace(a)
|
||||
if a == "" {
|
||||
continue
|
||||
}
|
||||
if a == "*" {
|
||||
return true, "*"
|
||||
}
|
||||
if strings.EqualFold(a, origin) {
|
||||
return true, origin
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func (g *Gateway) withWhitelist(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/health" {
|
||||
|
||||
Reference in New Issue
Block a user