A middleware is a function that returns a function that can be added to the HTTP ingress proxy to pre or post process a request.
type Middleware func(next connector.HTTPHandler) connector.HTTPHandler
Remember that Microbus add an error error value to the standard Go web handler.
Middlewares are chained together. Each receives the request after it was processed by the preceding (upstream) middleware, passing it along to the next (downstream) middleware. And conversely, each receives the response from the next (downstream) middleware, and passes it back to the preceding (upstream) middleware. Both the request and the response may be modified by the middleware.
The HTTP ingress core microservice keeps the chain of middleware in a middleware.Chain construct that can be accessed via its Middleware method.
Each middleware in the chain is addressable by its name and can be replaced, removed or used as an insertion point. The chain is initialized with reasonable defaults: CharsetUTF8 -> ErrorPrinter -> BlockedPaths -> Logger -> Enter -> SecureRedirect -> CORS -> XForward -> InternalHeaders -> RootPath -> Timeout -> Authorization -> Ready -> CacheControl -> Compress -> DefaultFavIcon.
Enter is a noop marker that indicates that the request was accepted. Middleware after this point typically manipulate the request headers. Similarly, Ready is a noop marker that indicates that the request is ready to be processed. Middleware after this point typically manipulate the response headers or body. Both markers can be used as insertion points.
The following middlewares are available in the middleware package:
Authorization looks for a token in the “Authorization: Bearer” header or the “Authorization” cookie,
validates it with its issuer, and associates the corresponding actor with the requestBlockedPaths filters incoming requests based on their URL path. The default setting blocks common patterns that are obviously trying to probe the server for vulnerabilitiesCacheControl sets the Cache-Control header if not otherwise specifiedCharsetUTF8 appends ; charset=utf-8 to the Content-Type header of textual responsesCompress compresses textual responses using brotli, gzip or deflateCORS responds to the CORS origin OPTION request and blocks requests from disallowed originsDefaultFavIcon responds to /favicon.ico, if the app does notErrorPageRedirect that redirects full-page browser requests that resulted in an error to an error pageErrorPrinter is the final catcher of errors. It converts the errors to the appropriate HTTP status code, typically 500 Internal Server Error, and prints an error message to the userGroup chains nested middleware together and is often used in conjunction with the OnRoute middleware to apply a group of middleware to a specific routeInternalHeaders filters internal headers from entering or exiting.Logger logs the incoming requests and error responses.Noop does nothingOnRoute applies middleware conditionally based on the path of the URLRootPath rewrites the root path / with one that can be routed to such as /rootSecureRedirect redirects request from HTTP port :80 to HTTPS port :443, if appropriateTimeout applies a timeout to the requestXForwarded sets the X-Forwarded headers pertaining to the request, if not already set. These headers are used by downstream microservices to compose absolute URLs when necessaryTo modify the request, a middleware should manipulate the http.Request before delegating it to the next (downstream) middleware.
func DisableCompression() Middleware {
return func(next connector.HTTPHandler) connector.HTTPHandler {
return func(w http.ResponseWriter, r *http.Request) (err error) {
r.Header.Del("Accept-Encoding")
return next(w, r)
}
}
}
Similarly, to modify the header or status code of the response, a middleware should manipulate the http.ResponseWriter after delegating the request to the next (downstream) middleware.
func RemoveSecretHeader(secretHeaderName string) Middleware {
return func(next connector.HTTPHandler) connector.HTTPHandler {
return func(w http.ResponseWriter, r *http.Request) (err error) {
err = next(w, r)
r.Header.Del(secretHeaderName)
return err // No trace
}
}
}
A middleware that wants to manipulate the body of the response should create a local http.ResponseWriter and delegate that downstream instead of the one it received. httpx.NewResponseRecorder works in conjunction with httpx.Copy to reduce memory allocations.
func Zipper() Middleware {
return func(next connector.HTTPHandler) connector.HTTPHandler {
return func(w http.ResponseWriter, r *http.Request) (err error) {
ww := httpx.NewResponseRecorder()
err = next(ww, r)
if err != nil {
return err // No trace
}
res := ww.Result()
body := res.Body
isCompress := strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") && res.Header.Get("Content-Encoding") == ""
if isCompress {
res.Body = nil
}
err = httpx.Copy(w, res)
if err != nil {
return errors.Trace(err)
}
if isCompress {
w.Header().Set("Content-Encoding", "gzip")
zipper := gzip.NewWriter(w)
err = io.Copy(zipper, body)
zipper.Close()
if err != nil {
return errors.Trace(err)
}
}
return nil
}
}
}
Custom middleware is inserted to the chain during initialization of the ingress proxy. The following inserts the DisableCompression middleware after the Enter marker middleware and sets it to operate on the /documents/ path.
httpIngress := httpingress.NewService()
httpIngress.Middleware().InsertAfter(httpingress.Enter, middleware.OnRoutePrefix("/documents/", DisableCompression()))