102 lines
3.0 KiB
Go
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)
|
|
}
|
|
}
|