httpingress/middleware
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.HTTPHandlerRemember that Microbus adds an error value to the standard Go web handler.
Chain
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.
In the Box
The following middlewares are available in the middleware package:
Authorizationlooks for a token in the “Authorization: Bearer” header or the “Authorization” cookie, validates it with its issuer, and associates the corresponding actor with the requestBlockedPathsfilters incoming requests based on their URL path. The default setting blocks common patterns that are obviously trying to probe the server for vulnerabilities. Supported block patterns:/exact/path,/path/subtree/*,*.extCacheControlsets the Cache-Control header if not otherwise specifiedCharsetUTF8appends; charset=utf-8to theContent-Typeheader of textual responsesCompresscompresses textual responses using brotli, gzip or deflateCORSresponds to the CORS origin OPTION request and blocks requests from disallowed originsDefaultFavIconresponds to/favicon.ico, if no microservice doesErrorPageRedirectthat redirects full-page browser requests that resulted in an error to an error pageErrorPrinteris the final catcher of errors. It converts the errors to the appropriate HTTP status code, typically500 Internal Server Error, and prints an error message to the userGroupchains nested middleware together and is often used in conjunction with theOnRoutemiddleware to apply a group of middleware to a specific routeInternalHeadersfilters internal headers from entering or exiting.Loggerlogs the incoming requests and error responses.Noopdoes nothingOnRouteapplies middleware conditionally based on the path of the URLRootPathrewrites the root path/with one that can be routed to such as/rootSecureRedirectredirects request from HTTP port:80to HTTPS port:443, if appropriateTimeoutapplies a timeout to the requestXForwardedsets theX-Forwardedheaders pertaining to the request, if not already set. These headers are used by downstream microservices to compose absolute URLs when necessary
Build
To 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()))