Files
platform/httpclient/cors.go
T
2026-07-18 23:28:51 +02:00

102 lines
3.0 KiB
Go

package httpclient
import (
"net/http"
)
type corsConfig struct {
origin string // "" = mirror request Origin; "*" = wildcard (no credentials)
methods string
headers string
exposeHeaders string
}
// CORSOption customises the CORS middleware.
type CORSOption func(*corsConfig)
// WithOrigin sets the Access-Control-Allow-Origin value. Use "*" for public
// APIs that don't need credentials. The default (empty string) mirrors the
// request's Origin header and enables credentials.
func WithOrigin(origin string) CORSOption {
return func(c *corsConfig) { c.origin = origin }
}
// WithMethods sets Access-Control-Allow-Methods. Default: "GET, POST, PUT, OPTIONS".
func WithMethods(methods string) CORSOption {
return func(c *corsConfig) { c.methods = methods }
}
// WithHeaders sets Access-Control-Allow-Headers. Default: "*".
func WithHeaders(headers string) CORSOption {
return func(c *corsConfig) { c.headers = headers }
}
// WithExposeHeaders sets Access-Control-Expose-Headers. Default: empty.
func WithExposeHeaders(headers string) CORSOption {
return func(c *corsConfig) { c.exposeHeaders = headers }
}
// CORS returns middleware that sets CORS response headers and handles
// preflight (OPTIONS) requests. By default it mirrors the request Origin,
// enables credentials, and allows GET/POST/PUT/OPTIONS with any request
// headers.
//
// Use WithOrigin("*") for public APIs that don't need credentials (per the
// CORS spec, credentials and wildcard origin are mutually exclusive).
//
// Common patterns:
//
// // Public API (wildcard origin, no credentials):
// mux := http.NewServeMux()
// handler := httpclient.CORS(
// httpclient.WithOrigin("*"),
// httpclient.WithMethods("GET, POST, OPTIONS"),
// )(mux)
//
// // Authenticated API (origin reflection, credentials):
// handler := httpclient.CORS()(mux)
func CORS(opts ...CORSOption) func(http.Handler) http.Handler {
cfg := corsConfig{
methods: "GET, POST, PUT, OPTIONS",
headers: "*",
}
for _, o := range opts {
o(&cfg)
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
setCORSHeaders(w, r, cfg)
if r.Method == http.MethodOptions {
w.Header().Set("Access-Control-Max-Age", "86400")
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
}
func setCORSHeaders(w http.ResponseWriter, r *http.Request, cfg corsConfig) {
origin := cfg.origin
if origin == "" {
origin = r.Header.Get("Origin")
}
if origin == "" {
return
}
w.Header().Set("Access-Control-Allow-Origin", origin)
// Per spec, credentials can't be used with wildcard origin.
if origin != "*" {
w.Header().Set("Access-Control-Allow-Credentials", "true")
}
if cfg.exposeHeaders != "" {
w.Header().Set("Access-Control-Expose-Headers", cfg.exposeHeaders)
}
if cfg.methods != "" {
w.Header().Set("Access-Control-Allow-Methods", cfg.methods)
}
if cfg.headers != "" {
w.Header().Set("Access-Control-Allow-Headers", cfg.headers)
}
}