Documentation
¶
Overview ¶
Package middleware contains simple middleware implementations and utilities. The middlewares in this package do not depend on functionality from httputil/mux, and can be used with any router that uses middleware, or with net/http directly.
Index ¶
- func AllowAll() []string
- func AllowMethodsFromPattern(request *http.Request) []string
- func As[T http.Handler](h http.Handler) (v T, ok bool)
- func Unwrap(h http.Handler) http.Handler
- func Wrap(handler http.Handler, middleware ...Func) http.Handler
- type CORS
- type Func
- type Log
- type LogEntry
- type SecurityHeaders
- func (s *SecurityHeaders) Handler(next http.Handler) http.Handler
- func (s *SecurityHeaders) SetContentSecurityPolicy(directives ...string)
- func (s *SecurityHeaders) SetContentSecurityPolicyReportOnly(directives ...string)
- func (s *SecurityHeaders) SetCrossOriginEmbedderPolicy(value string)
- func (s *SecurityHeaders) SetCrossOriginOpenerPolicy(value string)
- func (s *SecurityHeaders) SetCrossOriginResourcePolicy(value string)
- func (s *SecurityHeaders) SetFrameOptionsSameOrigin()
- func (s *SecurityHeaders) SetPermissionsPolicy(reportTo string, value map[string][]string)
- func (s *SecurityHeaders) SetReferrerPolicy(value string)
- func (s *SecurityHeaders) SetReportingEndpoints(endpoints map[string]string)
- func (s *SecurityHeaders) SetStrictTransportSecurity(maxAge time.Duration, includeSubdomains, preload bool)
Examples ¶
- AllowAll (Preflight)
- AllowAll (Simple)
- AllowMethodsFromPattern
- AllowMethodsFromPattern (NoMethods)
- As
- As (Failed)
- CORS (Defaults)
- CORS (HeadersNotAllowed)
- CORS (MethodNotAllowed)
- CORS (Methods)
- CORS (OriginNotAllowed)
- CORS (Preflight)
- CORS (Simple)
- Log
- Log (Custom)
- LogEntry.LogValue
- MaxBytes
- SecurityHeaders (Defaults)
- SecurityHeaders (DisableAll)
- SecurityHeaders (V1)
- SecurityHeaders.SetContentSecurityPolicy
- SecurityHeaders.SetContentSecurityPolicyReportOnly
- SecurityHeaders.SetCrossOriginEmbedderPolicy
- SecurityHeaders.SetCrossOriginOpenerPolicy
- SecurityHeaders.SetCrossOriginResourcePolicy
- SecurityHeaders.SetFrameOptionsSameOrigin
- SecurityHeaders.SetPermissionsPolicy
- SecurityHeaders.SetReferrerPolicy
- SecurityHeaders.SetReportingEndpoints
- SecurityHeaders.SetStrictTransportSecurity
- Timeout
- Wrap
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AllowAll ¶
func AllowAll() []string
AllowAll returns the CORS and Permissions-Policy wildcard pattern.
Example (Preflight) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
// Allow all values. This creates an open API.
// Note that this is only allowed when AllowCredentials is false.
cors := middleware.CORS{
AllowCredentials: false,
AllowHeaders: middleware.AllowAll(),
AllowMethods: middleware.AllowAll(),
AllowOrigins: middleware.AllowAll(),
ExposeHeaders: middleware.AllowAll(),
}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(cors.Handler)
request := httptest.NewRequest(http.MethodOptions, "/", nil)
request.Header.Set("Origin", "http://example.invalid")
request.Header.Set("Access-Control-Request-Method", http.MethodGet)
request.Header.Set("Access-Control-Request-Headers", "X-API-Key")
rr := httptest.NewRecorder()
router.ServeHTTP(rr, request)
printHeader := func(name string) {
fmt.Printf("%s: %v\n", name, rr.Header().Values(name))
}
fmt.Printf("Status: %d\n", rr.Result().StatusCode)
printHeader("Access-Control-Allow-Credentials")
printHeader("Access-Control-Allow-Headers")
printHeader("Access-Control-Allow-Methods")
printHeader("Access-Control-Allow-Origin")
printHeader("Access-Control-Expose-Headers")
printHeader("Access-Control-Max-Age")
}
Output: Status: 204 Access-Control-Allow-Credentials: [] Access-Control-Allow-Headers: [*] Access-Control-Allow-Methods: [*] Access-Control-Allow-Origin: [*] Access-Control-Expose-Headers: [] Access-Control-Max-Age: []
Example (Simple) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
// Allow all values. This creates an open API.
// Note that this is only allowed when AllowCredentials is false.
cors := middleware.CORS{
AllowCredentials: false,
AllowHeaders: middleware.AllowAll(),
AllowMethods: middleware.AllowAll(),
AllowOrigins: middleware.AllowAll(),
ExposeHeaders: middleware.AllowAll(),
}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(cors.Handler)
request := httptest.NewRequest(http.MethodGet, "/", nil)
request.Header.Set("Origin", "http://example.invalid")
rr := httptest.NewRecorder()
router.ServeHTTP(rr, request)
printHeader := func(name string) {
fmt.Printf("%s: %v\n", name, rr.Header().Values(name))
}
fmt.Printf("Status: %d\n", rr.Result().StatusCode)
printHeader("Access-Control-Allow-Credentials")
printHeader("Access-Control-Allow-Headers")
printHeader("Access-Control-Allow-Methods")
printHeader("Access-Control-Allow-Origin")
printHeader("Access-Control-Expose-Headers")
printHeader("Access-Control-Max-Age")
}
Output: Status: 200 Access-Control-Allow-Credentials: [] Access-Control-Allow-Headers: [*] Access-Control-Allow-Methods: [*] Access-Control-Allow-Origin: [*] Access-Control-Expose-Headers: [*] Access-Control-Max-Age: []
func AllowMethodsFromPattern ¶
AllowMethodsFromPattern returns the method from the request pattern. It returns '*' (allowing all methods) if the pattern contains no methods. Note that this only works with 'simple' CORS request (not preflight) and only if there is always a single method associated with a request path.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("GET /index.json", func(http.ResponseWriter, *http.Request) {})
cors := middleware.CORS{
AllowMethodsFunc: middleware.AllowMethodsFromPattern,
AllowOrigins: []string{"*"},
AllowHeaders: []string{"*"},
}
router.Use(cors.Handler)
request := httptest.NewRequest(http.MethodGet, "/index.json", nil)
request.Header.Set("Origin", "http://example.invalid")
rr := httptest.NewRecorder()
router.ServeHTTP(rr, request)
printHeader := func(name string) {
fmt.Printf("%s: %v\n", name, rr.Header().Values(name))
}
fmt.Printf("Status: %d\n", rr.Result().StatusCode)
printHeader("Access-Control-Allow-Credentials")
printHeader("Access-Control-Allow-Headers")
printHeader("Access-Control-Allow-Methods")
printHeader("Access-Control-Allow-Origin")
printHeader("Access-Control-Expose-Headers")
printHeader("Access-Control-Max-Age")
}
Output: Status: 200 Access-Control-Allow-Credentials: [] Access-Control-Allow-Headers: [*] Access-Control-Allow-Methods: [GET] Access-Control-Allow-Origin: [*] Access-Control-Expose-Headers: [] Access-Control-Max-Age: []
Example (NoMethods) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/index.json", func(http.ResponseWriter, *http.Request) {})
cors := middleware.CORS{
AllowMethodsFunc: middleware.AllowMethodsFromPattern,
AllowOrigins: []string{"*"},
AllowHeaders: []string{"*"},
}
router.Use(cors.Handler)
request := httptest.NewRequest(http.MethodOptions, "/index.json", nil)
request.Header.Set("Origin", "http://example.invalid")
request.Header.Set("Access-Control-Request-Method", http.MethodGet)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, request)
printHeader := func(name string) {
fmt.Printf("%s: %v\n", name, rr.Header().Values(name))
}
fmt.Printf("Status: %d\n", rr.Result().StatusCode)
printHeader("Access-Control-Allow-Credentials")
printHeader("Access-Control-Allow-Headers")
printHeader("Access-Control-Allow-Methods")
printHeader("Access-Control-Allow-Origin")
printHeader("Access-Control-Expose-Headers")
printHeader("Access-Control-Max-Age")
}
Output: Status: 204 Access-Control-Allow-Credentials: [] Access-Control-Allow-Headers: [*] Access-Control-Allow-Methods: [*] Access-Control-Allow-Origin: [*] Access-Control-Expose-Headers: [] Access-Control-Max-Age: []
func As ¶
As unwraps the given handler using Unwrap until it finds a handler of the given type. It returns the found handler and a boolean indicating success.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
// We want to find this handler in a situation with a lot of middleware.
myHandler := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
fmt.Println("This is my handler!")
})
router := mux.NewRouter()
router.Use(
(&middleware.Log{}).Handler,
(&middleware.CORS{}).Handler,
(&middleware.SecurityHeaders{}).Handler,
middleware.MaxBytes(0),
middleware.Timeout(0, ""),
)
router.Handle("GET /something", myHandler)
handler, _ := router.Handler(httptest.NewRequest(http.MethodGet, "/something", nil))
found, ok := middleware.As[http.HandlerFunc](handler)
if !ok {
fmt.Println("handler not found")
return
}
found.ServeHTTP(nil, nil)
}
Output: This is my handler!
Example (Failed) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
router := mux.NewRouter()
router.Use(
(&middleware.Log{}).Handler,
(&middleware.CORS{}).Handler,
)
router.Subrouter("GET /something")
handler, _ := router.Handler(httptest.NewRequest(http.MethodGet, "/something", nil))
// We're not going to find an http.HandlerFunc
_, ok := middleware.As[http.HandlerFunc](handler)
if ok {
fmt.Println("found handler")
} else {
fmt.Println("handler not found")
}
}
Output: handler not found
func Unwrap ¶
Unwrap unwraps a middleware handler and returns the inner handler. It returns 'nil' if it cannot unwrap the given handler. In order to unwrap a middleware, it needs to implement the following method:
Unwrap() http.Handler
All middleware from this package supports unwrapping.
func Wrap ¶
Wrap wraps the given handler with the given middleware functions. Middlewares will handle requests in order, meaning that the first middleware will be the first to receive a request.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
)
func main() {
handler := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
fmt.Println("Handler")
})
mw1 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Middleware 1")
next.ServeHTTP(w, r)
})
}
mw2 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Middleware 2")
next.ServeHTTP(w, r)
})
}
wrapped := middleware.Wrap(handler, mw1, mw2)
wrapped.ServeHTTP(httptest.NewRecorder(),
httptest.NewRequest(http.MethodGet, "/", nil))
}
Output: Middleware 1 Middleware 2 Handler
Types ¶
type CORS ¶
type CORS struct {
// AllowCredentials indicates that requests can be made using credentials.
// This is omitted when false (the default).
// Note that wildcard values are not allowed when this is set to true.
AllowCredentials bool
// AllowMethods indicates that the returned methods are allowed for requests to a path.
// This defaults to allowing no headers.
AllowHeaders []string
// AllowHeadersFunc is an alternative to AllowHeaders.
// It can be used to set the allowed methods based on the request.
AllowHeadersFunc func(*http.Request) []string
// AllowMethods indicates that the returned methods are allowed for requests to a path.
// This defaults to allowing no methods.
AllowMethods []string
// AllowMethodsFunc is an alternative to AllowMethods.
// It can be used to set the allowed methods based on routing data.
// See `AllowMethodsFromPattern` to allow methods indicated in the routed pattern.
AllowMethodsFunc func(*http.Request) []string
// AllowOrigins indicates that the returned origins (hosts) are allowed for requests to a path.
// This defaults to allowing no origins.
AllowOrigins []string
// AllowOriginsFunc is an alternative to AllowOrigins.
// It can be used to set the allowed origins based on routing data.
// Note that 'nil' is equivalent to '*', use []string{} to disallow all headers.
AllowOriginsFunc func(*http.Request) []string
// ExposeHeaders allows headers with the given names to be accessed by JavaScript.
// This is omitted when nil (the default).
ExposeHeaders []string
// ExposeHeadersFunc is an alternative to ExposeHeaders.
// It is omitted when 'nil'.
ExposeHeadersFunc func(*http.Request) []string
// MaxAge indicates the maximum duration that the CORS request can be cached.
// This is omitted by default, in which case browsers default to 5s.
// Browsers limit this value to 24 hours at most.
MaxAge time.Duration
// PassOptions makes the middleware pass options requests through to the next handler
// instead of returning a 204 for the request.
PassOptions bool
}
CORS implements a middleware for setting the Cross-Origin Resource Sharing headers. The default behaviour is to allow no CORS requests. Configure AllowOrigins and AllowMethods (or their corresponding functions) to allow CORS requests and configure AllowHeaders if additional headers are allowed in requests.
See the MDN documentation for details.
Example (Defaults) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
cors := middleware.CORS{}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(cors.Handler)
request := httptest.NewRequest(http.MethodOptions, "/", nil)
request.Header.Set("Origin", "http://example.invalid")
request.Header.Set("Access-Control-Request-Method", http.MethodGet)
request.Header.Set("Access-Control-Request-Headers", "X-API-Key,X-Access-Token")
rr := httptest.NewRecorder()
router.ServeHTTP(rr, request)
printHeader := func(name string) {
fmt.Printf("%s: %v\n", name, rr.Header().Values(name))
}
fmt.Printf("Status: %d\n", rr.Result().StatusCode)
printHeader("Access-Control-Allow-Credentials")
printHeader("Access-Control-Allow-Headers")
printHeader("Access-Control-Allow-Methods")
printHeader("Access-Control-Allow-Origin")
printHeader("Access-Control-Expose-Headers")
printHeader("Access-Control-Max-Age")
}
Output: Status: 403 Access-Control-Allow-Credentials: [] Access-Control-Allow-Headers: [] Access-Control-Allow-Methods: [] Access-Control-Allow-Origin: [] Access-Control-Expose-Headers: [] Access-Control-Max-Age: []
Example (HeadersNotAllowed) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"time"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
cors := middleware.CORS{
AllowCredentials: true,
AllowHeaders: []string{"X-API-Key"},
AllowMethods: []string{http.MethodGet},
AllowOrigins: []string{"http://example.invalid"},
ExposeHeaders: []string{"MY-HEADER"},
MaxAge: 90 * time.Second,
}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(cors.Handler)
request := httptest.NewRequest(http.MethodOptions, "/", nil)
request.Header.Set("Origin", "http://example.invalid")
request.Header.Set("Access-Control-Request-Method", http.MethodGet)
request.Header.Set("Access-Control-Request-Headers", "X-API-Key,X-Access-Token")
rr := httptest.NewRecorder()
router.ServeHTTP(rr, request)
printHeader := func(name string) {
fmt.Printf("%s: %v\n", name, rr.Header().Values(name))
}
fmt.Printf("Status: %d\n", rr.Result().StatusCode)
printHeader("Access-Control-Allow-Credentials")
printHeader("Access-Control-Allow-Headers")
printHeader("Access-Control-Allow-Methods")
printHeader("Access-Control-Allow-Origin")
printHeader("Access-Control-Expose-Headers")
printHeader("Access-Control-Max-Age")
}
Output: Status: 406 Access-Control-Allow-Credentials: [] Access-Control-Allow-Headers: [] Access-Control-Allow-Methods: [GET] Access-Control-Allow-Origin: [http://example.invalid] Access-Control-Expose-Headers: [] Access-Control-Max-Age: []
Example (MethodNotAllowed) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"time"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
cors := middleware.CORS{
AllowCredentials: true,
AllowHeaders: []string{"X-API-Key"},
AllowMethods: []string{http.MethodGet},
AllowOrigins: []string{"http://example.invalid"},
ExposeHeaders: []string{"MY-HEADER"},
MaxAge: 90 * time.Second,
}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(cors.Handler)
request := httptest.NewRequest(http.MethodOptions, "/", nil)
request.Header.Set("Origin", "http://example.invalid")
request.Header.Set("Access-Control-Request-Method", "POST")
request.Header.Set("Access-Control-Request-Headers", "X-API-Key")
rr := httptest.NewRecorder()
router.ServeHTTP(rr, request)
printHeader := func(name string) {
fmt.Printf("%s: %v\n", name, rr.Header().Values(name))
}
fmt.Printf("Status: %d\n", rr.Result().StatusCode)
printHeader("Access-Control-Allow-Credentials")
printHeader("Access-Control-Allow-Headers")
printHeader("Access-Control-Allow-Methods")
printHeader("Access-Control-Allow-Origin")
printHeader("Access-Control-Expose-Headers")
printHeader("Access-Control-Max-Age")
}
Output: Status: 405 Access-Control-Allow-Credentials: [] Access-Control-Allow-Headers: [] Access-Control-Allow-Methods: [] Access-Control-Allow-Origin: [http://example.invalid] Access-Control-Expose-Headers: [] Access-Control-Max-Age: []
Example (Methods) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
// The CORS middleware allows getting the headers for specific requests.
// This makes it possible to use a different data source for the information.
// This example shows the trivial case of returning a static value.
cors := middleware.CORS{
AllowHeadersFunc: func(*http.Request) []string {
return []string{"X-API-Key"}
},
AllowMethodsFunc: func(*http.Request) []string {
return []string{http.MethodGet}
},
AllowOriginsFunc: func(*http.Request) []string {
return []string{"http://example.invalid"}
},
ExposeHeadersFunc: func(*http.Request) []string {
return []string{"MY-HEADER"}
},
}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(cors.Handler)
request := httptest.NewRequest(http.MethodGet, "/", nil)
request.Header.Set("Origin", "http://example.invalid")
rr := httptest.NewRecorder()
router.ServeHTTP(rr, request)
printHeader := func(name string) {
fmt.Printf("%s: %v\n", name, rr.Header().Values(name))
}
fmt.Printf("Status: %d\n", rr.Result().StatusCode)
printHeader("Access-Control-Allow-Credentials")
printHeader("Access-Control-Allow-Headers")
printHeader("Access-Control-Allow-Methods")
printHeader("Access-Control-Allow-Origin")
printHeader("Access-Control-Expose-Headers")
printHeader("Access-Control-Max-Age")
}
Output: Status: 200 Access-Control-Allow-Credentials: [] Access-Control-Allow-Headers: [X-API-Key] Access-Control-Allow-Methods: [GET] Access-Control-Allow-Origin: [http://example.invalid] Access-Control-Expose-Headers: [MY-HEADER] Access-Control-Max-Age: []
Example (OriginNotAllowed) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"time"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
cors := middleware.CORS{
AllowCredentials: true,
AllowHeaders: []string{"X-API-Key"},
AllowMethods: []string{http.MethodGet},
AllowOrigins: []string{"http://example.invalid"},
ExposeHeaders: []string{"MY-HEADER"},
MaxAge: 90 * time.Second,
}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(cors.Handler)
request := httptest.NewRequest(http.MethodOptions, "/", nil)
request.Header.Set("Origin", "http://wrong.invalid")
request.Header.Set("Access-Control-Request-Method", http.MethodGet)
request.Header.Set("Access-Control-Request-Headers", "X-API-Key")
rr := httptest.NewRecorder()
router.ServeHTTP(rr, request)
printHeader := func(name string) {
fmt.Printf("%s: %v\n", name, rr.Header().Values(name))
}
fmt.Printf("Status: %d\n", rr.Result().StatusCode)
printHeader("Access-Control-Allow-Credentials")
printHeader("Access-Control-Allow-Headers")
printHeader("Access-Control-Allow-Methods")
printHeader("Access-Control-Allow-Origin")
printHeader("Access-Control-Expose-Headers")
printHeader("Access-Control-Max-Age")
}
Output: Status: 403 Access-Control-Allow-Credentials: [] Access-Control-Allow-Headers: [] Access-Control-Allow-Methods: [] Access-Control-Allow-Origin: [] Access-Control-Expose-Headers: [] Access-Control-Max-Age: []
Example (Preflight) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"time"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
cors := middleware.CORS{
AllowCredentials: true,
AllowHeaders: []string{"X-API-Key"},
AllowMethods: []string{http.MethodGet},
AllowOrigins: []string{"http://example.invalid"},
ExposeHeaders: []string{"MY-HEADER"},
MaxAge: 90 * time.Second,
}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(cors.Handler)
request := httptest.NewRequest(http.MethodOptions, "/", nil)
request.Header.Set("Origin", "http://example.invalid")
request.Header.Set("Access-Control-Request-Method", http.MethodGet)
request.Header.Set("Access-Control-Request-Headers", "X-API-Key")
rr := httptest.NewRecorder()
router.ServeHTTP(rr, request)
printHeader := func(name string) {
fmt.Printf("%s: %v\n", name, rr.Header().Values(name))
}
fmt.Printf("Status: %d\n", rr.Result().StatusCode)
printHeader("Access-Control-Allow-Credentials")
printHeader("Access-Control-Allow-Headers")
printHeader("Access-Control-Allow-Methods")
printHeader("Access-Control-Allow-Origin")
printHeader("Access-Control-Expose-Headers")
printHeader("Access-Control-Max-Age")
}
Output: Status: 204 Access-Control-Allow-Credentials: [true] Access-Control-Allow-Headers: [X-API-Key] Access-Control-Allow-Methods: [GET] Access-Control-Allow-Origin: [http://example.invalid] Access-Control-Expose-Headers: [] Access-Control-Max-Age: [90]
Example (Simple) ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"time"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
cors := middleware.CORS{
AllowCredentials: true,
AllowHeaders: []string{"X-API-Key"},
AllowMethods: []string{http.MethodGet},
AllowOrigins: []string{"http://example.invalid"},
ExposeHeaders: []string{"MY-HEADER"},
MaxAge: 90 * time.Second,
}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(cors.Handler)
request := httptest.NewRequest(http.MethodGet, "/", nil)
request.Header.Set("Origin", "http://example.invalid")
rr := httptest.NewRecorder()
router.ServeHTTP(rr, request)
printHeader := func(name string) {
fmt.Printf("%s: %v\n", name, rr.Header().Values(name))
}
fmt.Printf("Status: %d\n", rr.Result().StatusCode)
printHeader("Access-Control-Allow-Credentials")
printHeader("Access-Control-Allow-Headers")
printHeader("Access-Control-Allow-Methods")
printHeader("Access-Control-Allow-Origin")
printHeader("Access-Control-Expose-Headers")
printHeader("Access-Control-Max-Age")
}
Output: Status: 200 Access-Control-Allow-Credentials: [true] Access-Control-Allow-Headers: [X-API-Key] Access-Control-Allow-Methods: [GET] Access-Control-Allow-Origin: [http://example.invalid] Access-Control-Expose-Headers: [MY-HEADER] Access-Control-Max-Age: [90]
type Func ¶
Func represents a middleware function. Middleware functions can modify or reject a request before calling the next handler. This can be used to log requests, perform authentication, modify request data, and much more. See http.AllowQuerySemicolons for an example of such a function in net/http.
func MaxBytes ¶
MaxBytes returns a middleware function based on http.MaxBytesHandler.
Example ¶
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/", func(_ http.ResponseWriter, r *http.Request) {
data, _ := io.ReadAll(r.Body)
defer r.Body.Close()
fmt.Println(string(data))
})
// Limit the body size to 5 bytes.
router.Use(middleware.MaxBytes(5))
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/",
strings.NewReader("Hello World")))
fmt.Println(rr.Body.String())
}
Output: Hello
func Timeout ¶
Timeout returns a middleware function using http.TimeoutHandler.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"time"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/", func(_ http.ResponseWriter, r *http.Request) {
<-r.Context().Done()
})
router.Use(middleware.Timeout(time.Millisecond, "timeout!"))
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/",
strings.NewReader("Hello World")))
fmt.Printf("%d %s\n", rr.Code, rr.Body.String())
}
Output: 503 timeout!
type Log ¶
type Log struct {
// Logger contains the logger used for logging requests.
// It defaults to `slog.Default`.
Logger *slog.Logger
// Level contains the log level on which to log requests.
// It defaults to log level 'INFO'.
Level slog.Leveler
// Message contains the log message accompanying requests.
// It defaults to 'HTTP Request'.
Message string
// Key contains the log key.
// It defaults to "", which unwraps any group from the value.
Key string
// LogValuer contains a function used to format the request.
// It defaults to LogEntry.LogValue when omitted.
Value func(entry LogEntry) slog.Value
// Log the incoming requests instead of finished requests.
// Note that various fields, like status code are not available at this time.
LogIncoming bool
}
Log provides a middleware for logging HTTP requests.
Example ¶
package main
import (
"log/slog"
"net/http"
"net/http/httptest"
"os"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
// Create a logger without timestamps for this example.
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey && len(groups) == 0 {
return slog.Attr{}
}
return a
},
}))
// Create a log middleware
logMiddleware := middleware.Log{
Logger: logger,
// Custom log value function with only the predictable request fields.
// See the example on LogEntry.LogValue for the default format.
Value: func(entry middleware.LogEntry) slog.Value {
return slog.GroupValue(
slog.String("method", entry.Request.Method),
slog.String("host", entry.Request.Host),
slog.String("path", entry.Request.URL.Path),
slog.String("protocol", entry.Request.Proto),
slog.Int("status", entry.Status),
slog.Int("size_bytes", entry.ResponseSize))
},
}
router := mux.NewRouter()
router.Use(logMiddleware.Handler)
router.ServeHTTP(httptest.NewRecorder(),
httptest.NewRequest(http.MethodGet, "https://example.test/index.html", nil))
}
Output: level=INFO msg="HTTP Request" method=GET host=example.test path=/index.html protocol=HTTP/1.1 status=404 size_bytes=9
Example (Custom) ¶
package main
import (
"log/slog"
"net/http"
"net/http/httptest"
"os"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
// Create a logger without timestamps for this example.
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey && len(groups) == 0 {
return slog.Attr{}
}
return a
},
}))
// Create a log middleware with custom functions.
logMiddleware := middleware.Log{
Logger: logger,
Level: slog.LevelWarn,
Message: "Finished Request",
Key: "request",
// Custom log value function with only the predictable request fields.
// See the example on LogEntry.LogValue for the default format.
Value: func(entry middleware.LogEntry) slog.Value {
return slog.GroupValue(
slog.String("method", entry.Request.Method),
slog.String("host", entry.Request.Host),
slog.String("path", entry.Request.URL.Path),
slog.String("protocol", entry.Request.Proto),
slog.Int("status", entry.Status),
slog.Int("size_bytes", entry.ResponseSize))
},
}
router := mux.NewRouter()
router.Use(logMiddleware.Handler)
router.ServeHTTP(httptest.NewRecorder(),
httptest.NewRequest(http.MethodGet, "https://example.test/index.html", nil))
}
Output: level=WARN msg="Finished Request" request.method=GET request.host=example.test request.path=/index.html request.protocol=HTTP/1.1 request.status=404 request.size_bytes=9
type LogEntry ¶
type LogEntry struct {
// Request contains the incoming request.
Request *http.Request
// Status contains the response status.
// This is not set until the request has finished.
Status int
// ResponseSize contains the amount of data written to the response writer (in bytes).
// This is not set until the request has finished.
ResponseSize int
// Duration contains the amount of time taken to handle the request.
// This is not set until the request has finished.
Duration time.Duration
}
LogEntry represents an entry logged by the Log middleware.
func (LogEntry) LogValue ¶
LogValue returns the log value for the log entry. It implements slog.LogValuer.
Example ¶
package main
import (
"log/slog"
"net/http"
"net/url"
"os"
"time"
"go.hofstra.dev/httputil/middleware"
)
func main() {
// Create a logger without timestamps for this example.
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey && len(groups) == 0 {
return slog.Attr{}
}
return a
},
}))
entry := middleware.LogEntry{
Request: &http.Request{
RemoteAddr: "[2001:db8:212::17]:8080",
Method: http.MethodGet,
Host: "example.com",
Proto: "HTTP/1.1",
URL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/api/v1/logs",
},
Header: http.Header{
"User-Agent": []string{"Go-http-client/1.1"},
},
},
Status: http.StatusNoContent,
ResponseSize: 0,
Duration: 21 * time.Second,
}
logger.Info("HTTP Request", "", entry)
}
Output: level=INFO msg="HTTP Request" remote_addr=[2001:db8:212::17]:8080 status=204 method=GET host=example.com path=/api/v1/logs duration_seconds=21 response_bytes=0 user_agent=Go-http-client/1.1
type SecurityHeaders ¶
type SecurityHeaders struct {
// Version sets the version of the security headers.
// Additions in headers or changes in defaults lead to a new version.
// It defaults to the latest version.
Version int
// Disable setting the 'Content-Security-Policy' header.
DisableContentSecurityPolicy bool
// Disable setting the 'Cross-Origin-Embedder-Policy' header.
DisableCrossOriginEmbeddedPolicy bool
// Disable setting the 'Cross-Origin-Opener-Policy' header.
DisableCrossOriginOpenerPolicy bool
// Disable setting the 'Cross-Origin-Resource-Policy' header.
DisableCrossOriginResourcePolicy bool
// Disable setting the 'Permissions-Policy' header.
DisablePermissionsPolicy bool
// Disable setting the 'Referrer-Policy' header.
DisableReferrerPolicy bool
// Disable setting the 'Strict-Transport-Security' header.
DisableStrictTransportSecurity bool
// Disable setting the 'X-Content-Type-Options' header.
DisableContentTypeOptions bool
// Disable setting the 'X-Frame-Options' header.
DisableFrameOptions bool
// contains filtered or unexported fields
}
SecurityHeaders implements a middleware that sets security headers. It sets the following headers by default:
Content-Security-Policy: default-src 'self' Cross-Origin-Embedder-Policy: require-corp Cross-Origin-Opener-Policy: same-origin Cross-Origin-Resource-Policy: same-origin Permissions-Policy: <disable everything> Referrer-Policy: no-referrer Strict-Transport-Security: max-age=63072000; includeSubdomains; preload X-Content-Type-Options: nosniff X-Frame-Options: DENY
This middleware is versioned with an increase in version when new headers are added. Set the Version field to the desired version to ensure backwards compatibility. Default headers per version are:
- 1: Content-Security-Policy, Cross-Origin-Embedder-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy, Permissions-Policy, Referrer-Policy, Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options.
Example (Defaults) ¶
package main
import (
"fmt"
"maps"
"net/http"
"net/http/httptest"
"slices"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
for _, header := range slices.Sorted(maps.Keys(rr.Header())) {
fmt.Printf("%s: %q\n", header, rr.Header().Get(header))
}
}
Output: Content-Security-Policy: "default-src 'self'" Cross-Origin-Embedder-Policy: "require-corp" Cross-Origin-Opener-Policy: "same-origin" Cross-Origin-Resource-Policy: "same-origin" Permissions-Policy: "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(), gamepad=(), speaker-selection=(), conversion-measurement=(), focus-without-user-activation=(), hid=(), idle-detection=(), interest-cohort=(), serial=(), sync-script=(), trust-token-redemption=(), unload=(), window-placement=(), vertical-scroll=()" Referrer-Policy: "no-referrer" Strict-Transport-Security: "max-age=63072000; includeSubdomains; preload" X-Content-Type-Options: "nosniff" X-Frame-Options: "DENY"
Example (DisableAll) ¶
package main
import (
"fmt"
"maps"
"net/http"
"net/http/httptest"
"slices"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{
DisableFrameOptions: true,
DisableContentTypeOptions: true,
DisableReferrerPolicy: true,
DisableStrictTransportSecurity: true,
DisableContentSecurityPolicy: true,
DisableCrossOriginEmbeddedPolicy: true,
DisableCrossOriginOpenerPolicy: true,
DisableCrossOriginResourcePolicy: true,
DisablePermissionsPolicy: true,
}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
for _, header := range slices.Sorted(maps.Keys(rr.Header())) {
fmt.Printf("%s: %q\n", header, rr.Header().Get(header))
}
}
Example (V1) ¶
package main
import (
"fmt"
"maps"
"net/http"
"net/http/httptest"
"slices"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{Version: 1}
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
for _, header := range slices.Sorted(maps.Keys(rr.Header())) {
fmt.Printf("%s: %q\n", header, rr.Header().Get(header))
}
}
Output: Content-Security-Policy: "default-src 'self'" Cross-Origin-Embedder-Policy: "require-corp" Cross-Origin-Opener-Policy: "same-origin" Cross-Origin-Resource-Policy: "same-origin" Permissions-Policy: "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(), gamepad=(), speaker-selection=(), conversion-measurement=(), focus-without-user-activation=(), hid=(), idle-detection=(), interest-cohort=(), serial=(), sync-script=(), trust-token-redemption=(), unload=(), window-placement=(), vertical-scroll=()" Referrer-Policy: "no-referrer" Strict-Transport-Security: "max-age=63072000; includeSubdomains; preload" X-Content-Type-Options: "nosniff" X-Frame-Options: "DENY"
func (*SecurityHeaders) Handler ¶
func (s *SecurityHeaders) Handler(next http.Handler) http.Handler
Handler returns a handler that sets the security headers and calls the next handler to handle the request.
It implements Func.
func (*SecurityHeaders) SetContentSecurityPolicy ¶
func (s *SecurityHeaders) SetContentSecurityPolicy(directives ...string)
SetContentSecurityPolicy sets the content security policy. See the MDN documentation for details.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{}
securityHeaders.SetContentSecurityPolicy(
"default-src 'self'",
"img-src 'self' example.com",
)
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
fmt.Println(rr.Header().Get("Content-Security-Policy"))
}
Output: default-src 'self'; img-src 'self' example.com
func (*SecurityHeaders) SetContentSecurityPolicyReportOnly ¶
func (s *SecurityHeaders) SetContentSecurityPolicyReportOnly(directives ...string)
SetContentSecurityPolicyReportOnly sets the content security policy for reporting only. It must include a 'report-to' directive, which reports to an endpoint set by SecurityHeaders.SetReportingEndpoints. See the MDN documentation for details.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{}
securityHeaders.SetContentSecurityPolicyReportOnly(
"default-src 'self'",
"img-src 'self' example.com",
"report-to csp-endpoint",
)
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
fmt.Println(rr.Header().Get("Content-Security-Policy-Report-Only"))
}
Output: default-src 'self'; img-src 'self' example.com; report-to csp-endpoint
func (*SecurityHeaders) SetCrossOriginEmbedderPolicy ¶
func (s *SecurityHeaders) SetCrossOriginEmbedderPolicy(value string)
SetCrossOriginEmbedderPolicy sets the Cross-Origin-Embedder-Policy header. Valid values are 'unsafe-none', 'require-corp' (the default) and 'credentialless'. See the MDN documentation for details.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{}
securityHeaders.SetCrossOriginEmbedderPolicy("credentialless")
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
fmt.Println(rr.Header().Get("Cross-Origin-Embedder-Policy"))
}
Output: credentialless
func (*SecurityHeaders) SetCrossOriginOpenerPolicy ¶
func (s *SecurityHeaders) SetCrossOriginOpenerPolicy(value string)
SetCrossOriginOpenerPolicy sets the Cross-Origin-Opener-Policy header. Valid values are 'unsafe-none', 'same-origin-allow-popups', 'same-origin' (the default) and 'noopener-allow-popups'. See the MDN documentation for details.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{}
securityHeaders.SetCrossOriginOpenerPolicy("same-origin-allow-popups")
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
fmt.Println(rr.Header().Get("Cross-Origin-Opener-Policy"))
}
Output: same-origin-allow-popups
func (*SecurityHeaders) SetCrossOriginResourcePolicy ¶
func (s *SecurityHeaders) SetCrossOriginResourcePolicy(value string)
SetCrossOriginResourcePolicy sets the Cross-Origin-Resource-Policy header. Valid values are 'same-site', 'same-origin' (the default) and 'cross-origin'. See the MDN documentation for details.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{}
securityHeaders.SetCrossOriginResourcePolicy("cross-origin")
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
fmt.Println(rr.Header().Get("Cross-Origin-Resource-Policy"))
}
Output: cross-origin
func (*SecurityHeaders) SetFrameOptionsSameOrigin ¶
func (s *SecurityHeaders) SetFrameOptionsSameOrigin()
SetFrameOptionsSameOrigin sets the referrer policy to 'SAMEORIGIN' instead of the default value of 'DENY'. See the MDN documentation for details.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{}
securityHeaders.SetFrameOptionsSameOrigin()
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
fmt.Println(rr.Header().Get("X-Frame-Options"))
}
Output: SAMEORIGIN
func (*SecurityHeaders) SetPermissionsPolicy ¶
func (s *SecurityHeaders) SetPermissionsPolicy(reportTo string, value map[string][]string)
SetPermissionsPolicy sets the Permissions-Policy header. The reportTo arguments sets the report-to value for all non-wildcard directives. The value argument is a mapping of the directive (eg "geolocation") to the allow list (eg {"self", "https://example.com"} or {"*"}). Quotes and braces are applied automatically.
See the MDN documentation for details on the header.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{}
securityHeaders.SetPermissionsPolicy(
"permissions-endpoint",
map[string][]string{
"geolocation": {"self", "https://example.com"},
"picture-in-picture": middleware.AllowAll(),
},
)
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
fmt.Println(rr.Header().Get("Permissions-Policy"))
}
Output: geolocation=(self "https://example.com");report-to=permissions-endpoint, picture-in-picture=*
func (*SecurityHeaders) SetReferrerPolicy ¶
func (s *SecurityHeaders) SetReferrerPolicy(value string)
SetReferrerPolicy overrides the string used for the referrer policy. See the MDN documentation for details.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{}
securityHeaders.SetReferrerPolicy("strict-origin-when-cross-origin")
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
fmt.Println(rr.Header().Get("Referrer-Policy"))
}
Output: strict-origin-when-cross-origin
func (*SecurityHeaders) SetReportingEndpoints ¶
func (s *SecurityHeaders) SetReportingEndpoints(endpoints map[string]string)
SetReportingEndpoints sets the Reporting-Endpoints header. The argument is a mapping of the endpoint name and URL.
See the MDN documentation for details on the header.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{}
securityHeaders.SetReportingEndpoints(map[string]string{
"csp-endpoint": "https://example.com/csp-reports",
"permissions-endpoint": "https://example.com/permissions-policy-reports",
})
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
fmt.Println(rr.Header().Get("Reporting-Endpoints"))
}
Output: csp-endpoint="https://example.com/csp-reports", permissions-endpoint="https://example.com/permissions-policy-reports"
func (*SecurityHeaders) SetStrictTransportSecurity ¶
func (s *SecurityHeaders) SetStrictTransportSecurity(maxAge time.Duration, includeSubdomains, preload bool)
SetStrictTransportSecurity overrides the string used for the strict transport security. See the MDN documentation for details.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"time"
"go.hofstra.dev/httputil/middleware"
"go.hofstra.dev/httputil/mux"
)
func main() {
securityHeaders := middleware.SecurityHeaders{}
securityHeaders.SetStrictTransportSecurity(time.Hour, true, true)
router := mux.NewRouter()
router.HandleFunc("/", func(http.ResponseWriter, *http.Request) {})
router.Use(securityHeaders.Handler)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/", nil))
fmt.Println(rr.Header().Get("Strict-Transport-Security"))
}
Output: max-age=3600; includeSubdomains; preload