fabric

Package coreservices/httpingress

Think of Microbus as a closed garden that requires a special key to access. In order to send and receive messages on Microbus, it’s necessary to communicate over NATS using a specific protocol. This is basically what the Connector facilitates for service-to-service calls.

Practically all solutions require interaction with a source that is outside Microbus. The most common scenario is perhaps a request generated from a web browser to a public API endpoint. In this case, something needs to bridge the gap between the incoming real HTTP request and the HTTP messages that travel over Microbus. This is where the HTTP ingress proxy comes into play.

On one end, the HTTP ingress proxy listens on port :8080 for real HTTP requests; on the other end it is connected to NATS. The ingress proxy converts real requests into requests on the bus; and on the flip side, converts responses from the bus to real responses. Because the bus messages in Microbus are formatted themselves as HTTP messages, this conversion is trivial, with minor adjustments:

Configuration

The HTTP ingress proxy supports several configuration properties that can be set in in config.yaml:

http.ingress.core:
  Ports: 9090

Ports is a comma-separated list of real HTTP ports on which to listen for requests. The default is to listen on port :8080.

PortMappings is a comma-separated list of mappings in the form x:y->z where x is the inbound HTTP port, y is the requested internal port, and z is the internal port to serve. Put differently, an HTTP request https://ingresshost:x/servicehost:y/path is mapped to internal NATS request https://servicehost:z/path. Both x and y can be * to indicate all ports. Setting z to * indicates to serve the requested port y without change. More specific rules take precedence over * rules.

Ports can be used to differentiate between traffic that is coming from trusted and untrusted sources. For example, the default setting 8080:*->*, 443:*->443, 80:*->443 grants port :8080 access to all internal ports, while ports :443 and :80 are restricted to internal port :443. The idea is to expose ports :443 and :80 to the internet and restrict :8080 to trusted clients only.

Four config properties are used to safeguard against long requests:

RequestMemoryLimit is the memory capacity used to hold pending requests, in megabytes.

AllowedOrigins is a comma-separated list of CORS origins to allow requests from. The * origin can be used to allow CORS request from all origins.

Respected Headers

The HTTP ingress proxy respects the following incoming headers:

Middleware

Middleware is a function that returns a function that can be added to pre- or post-process a request.

type Middleware func(next connector.HTTPHandler) connector.HTTPHandler

Middlewares are chained together. Each receives the request after it was processed by the preceding (upstream) middleware, passing it along to the next (downstream) one. And conversely, each receives the response from the next (downstream) middleware, and passes it back to the preceding (upstream) middleware. Both request and 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 name and can be replaced, removed or used as an insertion point.

The chain is initialized with reasonable defaults that perform various functions: ErrorPrinter -> BlockedPaths -> Logger -> Enter -> SecureRedirect -> CORS -> XForward -> InternalHeaders -> RootPath -> Timeout -> Ready -> CacheControl -> Compress -> DefaultFavIcon

The Enter middleware is a noop marker that indicates that the request was accepted. Middleware after this point typically manipulate the request headers. The Ready middleware 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.

A somewhat contrived example that inserts a middleware named Contrived after the Enter marker to process requests whose path starts with /images/.

httpIngress := NewService()
httpIngress.Middleware().InsertAfter("Enter", "Contrived", middleware.OnRoutePrefix("/images/", func(next connector.HTTPHandler) connector.HTTPHandler {
	return func(w http.ResponseWriter, r *http.Request) (err error) {
		// Modifying the request should be done before calling next
		r.Header.Del("Accept-Encoding") // Disable compression
		
		// Call the next middleware in the chain
		err = next(w, r)
		if err == nil {
			w.Header().Add("X-Check", "OK")
			return nil
		}

		// Modifying the result typically makes sense after calling next
		if ww, ok := w.(*httpx.ResponseRecorder); ok { // Always true
			ww.ClearBody()
			ww.Write([]byte("Oops!"))
		}
		w.Header().Add("X-Check", "Error")
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(err.Error()))
		return nil
	}
}))

Note that the w passed to the middleware is an httpx.ResponseRecorder whose headers and status code can be modified even after the body had been written. Appending to the body is also allowed. Modifying the body requires casting in order to clear it first.