Documentation
¶
Index ¶
- Variables
- func NewServer(conf *ServerConfig, handler http.Handler) (*http.Server, error)
- func ParseContentType(r *http.Request) (mediaType string, params map[string]string, err error)
- func SetupServerFactory(name string, handler http.Handler) ...
- type CORSConfig
- type Listener
- type SecurityConfig
- type ServerConfig
- type TLSConfig
Constants ¶
This section is empty.
Variables ¶
var ( HeaderContentType = http.CanonicalHeaderKey("Content-Type") HeaderRequestedBy = http.CanonicalHeaderKey("X-Requested-By") )
var DenySimpleRequests = DenySimpleRequestsFactory(nil)
DenySimpleRequests is the default middleware instance that denies CORS simple requests without the X-Requested-By header. Use DenySimpleRequestsFactory for custom skip logic.
var DenySimpleRequestsFactory = func(skipCheck func(r *http.Request) bool) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if skipCheck != nil && skipCheck(r) { next.ServeHTTP(w, r) return } if r.Header.Get(HeaderRequestedBy) != "" { next.ServeHTTP(w, r) return } switch r.Method { case http.MethodGet, http.MethodHead, http.MethodPost: mediaType, _, _ := ParseContentType(r) if mediaType == "" || slices.Contains(SimpleRequestContentTypes, mediaType) { http.Error(w, fmt.Sprintf("%s header is required", HeaderRequestedBy), http.StatusBadRequest) return } } next.ServeHTTP(w, r) }) } }
DenySimpleRequestsFactory creates a configurable middleware that prevents CORS simple requests. This provides CSRF protection by requiring either:
- The X-Requested-By header to be present, OR
- A Content-Type that is NOT a simple request type (which triggers CORS preflight)
Per the Fetch Standard, CORS simple requests are limited to:
- Methods: GET, HEAD, POST
- Content-Types: application/x-www-form-urlencoded, multipart/form-data, text/plain
Requests with other Content-Types (e.g., application/json) automatically trigger a preflight OPTIONS request, which can be validated by CORS policies, so they are allowed through.
The skipCheck function allows selective exemption of certain requests from this check. When skipCheck returns true, the request bypasses all validation. If skipCheck is nil, all requests will be checked.
var NoStore = func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0") if !r.ProtoAtLeast(1, 1) { w.Header().Set("Pragma", "no-cache") w.Header().Set("Expires", "0") } next.ServeHTTP(w, r) }) }
NoStore is a middleware that sets HTTP headers to prevent caching of responses. It applies the appropriate cache control headers based on the HTTP protocol version: - For HTTP/1.1+: Sets Cache-Control: no-store - For HTTP/1.0 and below: Also sets Pragma: no-cache and Expires: 0
var Security = func(conf SecurityConfig) func(next http.Handler) http.Handler { corsOpts := cors.Options{ AllowedOrigins: conf.CORS.AllowedOrigins, AllowCredentials: true, AllowedMethods: lo.Uniq(slices.Concat([]string{http.MethodPost}, conf.CORS.AllowedMethods)), AllowedHeaders: lo.Uniq(slices.Concat([]string{HeaderContentType, HeaderRequestedBy}, conf.CORS.AllowedHeaders, connectcors.AllowedHeaders())), ExposedHeaders: lo.Uniq(slices.Concat(conf.CORS.ExposedHeaders, connectcors.ExposedHeaders())), MaxAge: int(conf.CORS.MaxAge.Seconds()), Debug: conf.CORS.Debug, } if len(corsOpts.AllowedOrigins) == 0 { corsOpts.AllowOriginFunc = func(_ string) bool { return false } } c := cors.New(corsOpts) frameAncestors := buildFrameAncestors(conf.CORS.AllowedOrigins) denySimpleRequests := DenySimpleRequestsFactory(conf.CORS.SkipDenySimpleRequests) return func(next http.Handler) http.Handler { var handler http.Handler handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if conf.DenyMIMETypeSniffing { w.Header().Set("X-Content-Type-Options", "nosniff") } if conf.DenyClickjacking { w.Header().Set("Content-Security-Policy", frameAncestors) w.Header().Set("X-Frame-Options", "SAMEORIGIN") } if conf.EnableHSTS { w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") } next.ServeHTTP(w, r) }) if conf.CORS.DenySimpleRequests { handler = denySimpleRequests(handler) } return c.Handler(handler) } }
var SimpleRequestContentTypes = []string{
"application/x-www-form-urlencoded",
"multipart/form-data",
"text/plain",
}
SimpleRequestContentTypes are the only Content-Type values that qualify as CORS simple requests. Per the Fetch Standard (https://fetch.spec.whatwg.org/#cors-safelisted-request-header), only these three Content-Types are allowed in simple requests without triggering a preflight.
Functions ¶
func ParseContentType ¶
Types ¶
type CORSConfig ¶
type CORSConfig struct {
Debug bool `confx:"debug" usage:"CORS debug"`
AllowedOrigins []string `confx:"allowedOrigins" usage:"CORS allowed origins" validate:"dive,http_url"`
AllowedMethods []string `` /* 150-byte string literal not displayed */
AllowedHeaders []string `confx:"allowedHeaders" usage:"CORS allowed headers, Content-Type is always allowed"`
ExposedHeaders []string `confx:"exposedHeaders" usage:"CORS exposed headers"`
MaxAge time.Duration `confx:"maxAge" usage:"CORS max age"`
DenySimpleRequests bool `confx:"denySimpleRequests" usage:"CORS Deny simple requests"`
// SkipDenySimpleRequests allows selective exemption from X-Requested-By header requirement.
// When this function returns true for a request, the header check is skipped.
// Common use cases: health checks, webhooks, or specific API endpoints that need exemption.
SkipDenySimpleRequests func(r *http.Request) bool `confx:"-" json:"-"`
}
type Listener ¶
func SetupListener ¶
func SetupListener(lc *lifecycle.Lifecycle, conf *ServerConfig) (Listener, error)
type SecurityConfig ¶
type SecurityConfig struct {
CORS CORSConfig `confx:"cors"`
DenyMIMETypeSniffing bool `confx:"denyMIMETypeSniffing" usage:"Deny MIME type sniffing"`
DenyClickjacking bool `confx:"denyClickjacking" usage:"Deny clickjacking"`
EnableHSTS bool `confx:"enableHSTS" usage:"Enable HSTS"`
}
type ServerConfig ¶
type ServerConfig struct {
Address string `confx:"address" usage:"HTTP server address" validate:"required"`
PathPrefix string `` /* 235-byte string literal not displayed */
ReadTimeout time.Duration `confx:"readTimeout" usage:"maximum duration before timing out read of the request"`
// stop_if guards the ltefield: ReadTimeout == 0 means no read deadline at
// all, so it is not an upper bound, and a plain `ltefield` would reject a
// config that sets only a header timeout.
ReadHeaderTimeout time.Duration `` /* 150-byte string literal not displayed */
WriteTimeout time.Duration `confx:"writeTimeout" usage:"maximum duration before timing out write of the response"`
IdleTimeout time.Duration `confx:"idleTimeout" usage:"maximum amount of time to wait for the next request when keep-alives are enabled"`
// MaxRequestBodySize caps the request body via http.MaxBytesHandler. 0 means unlimited.
// Without it a single oversized body can be read entirely into memory.
MaxRequestBodySize int64 `confx:"maxRequestBodySize" usage:"maximum request body size in bytes, 0 for unlimited" validate:"gte=0"`
// MaxConcurrentStreams caps HTTP/2 streams per connection. 0 uses Go's default (250).
//
// This is PER CONNECTION, not global. Together with MaxConnections it gives a hard
// upper bound on in-flight requests: MaxConnections × MaxConcurrentStreams. On its own
// it bounds nothing — a client can just open more connections.
//
// Lowering it buys little: the same request volume just opens more connections.
// Leave it at 0 unless you specifically need the in-flight bound to be
// arithmetically knowable.
MaxConcurrentStreams int `` /* 194-byte string literal not displayed */
// MaxConnections caps concurrent TCP connections via netutil.LimitListener. 0 means unlimited.
//
// It counts CONNECTIONS, not requests. Under HTTP/1.1 a connection carries one request
// at a time so the two roughly coincide, but under HTTP/2 a single connection multiplexes
// many concurrent streams — so this is NOT a concurrency limit.
//
// Past the limit netutil.LimitListener stops calling Accept, so further connections
// wait in the kernel backlog until the client gives up: nothing is logged and nothing
// is rejected.
MaxConnections int `` /* 199-byte string literal not displayed */
TLS TLSConfig `confx:"tls"`
Security SecurityConfig `confx:",squash"`
}