fabric

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.HTTPHandler

Remember that Microbus add an error 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: 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:

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()))