/nelly

A high performance and modern HTTP Middleware for Golang (inspired by Kubernetes API server)

Primary LanguageGoMIT LicenseMIT

nelly

Build Status GoDoc Go Report Card Coverage

A high performance and modern HTTP Middleware for Golang (inspired by Kubernetes API server)

nelly

Introduction

Nelly is a minimal chaining middleware, that is designed to work directly with julienschmidt/httprouter for its high performance and lightweight implementation. Further, it provides some default and useful middleware handlers. Nelly is inspired by Kubernetes API server filters and the chaining middleware Alice which uses the traditional net/http handlers.

A list of supported handlers which are recommended to be used in the following order if they are chained:

Getting Started

A chian is an immutable list of a middleware handlers. Its handlers have an order where the request pass through the chain. The middleware handlers have the form

// nelly package
type Handler func(httprouter.Handle) httprouter.Handle

where the httprouter handle has the form

// httprouter package
type Handle func(http.ResponseWriter, *http.Request, Params)

To write a new middleware handler that could be chained to other middleware handlers as in the following example:

func someHandler(h httprouter.Handle) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		// middleware handler implementation goes here
		h(w, r, p)
	}
}

Further, any of the default middleware handlers that is provided by nelly could be used to create a new chain. To create a new chain form a set of handlers:

chain := nelly.NewChain(someHandler, otherHanlder, ...)

To wrap your handler appHandler (httprouter.Handle) with the created chain:

func appHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	// httprouter.Handle implementation goes here
	return
}

chainedHandler := chain.Then(appHandler)

Then you can use the created chainedHandler with httprouter

// create new router
router := httprouter.New()

// use chainedHandler (httprouter.Handle)
router.GET("/", chainedHandler)
log.Fatal(http.ListenAndServe(":8080", router))

The requests will pass someHandler first, then otherHanlder till the end of the set of the passed handlers to NewChain in the same order, and finally to appHandler which is equivalent to:

someHandler(otherHanlder(...(appHandler))

For more flexible usage of the created chain, you can use Append(handler) or Extend(chain):

  • Append - will return a new chain, leaving the original one untouched, and adds the specified middleware handlers as the last ones in the request flow of the original chain.

  • Extend - will return a new chain, leaving the original one untouched, and adds the specified chain as the last one in the request flow of the original chain.

// Create new chian
chain := nelly.NewChain(handler_A, handler_B)

// Append to the chain some handlers
chain_1 := chain.Append(handler_C, handler_D)

// Create another chain
chain_2 := nelly.NewChain(handler_E, handler_F)

// Create new chian by chaining chain1 with chain2
newChain := chain1.Extend(chain2)

// wrap the appHandler with the created chain
chainedHandler := chain2.Then(appHandler)

In previous example using chainedHandler in httprouter will pass the requests as follow

handler_A -> handler_B -> handler_C -> handler_D -> handler_E -> handler_F -> appHandler

Default Handlers

The Classic() version of nelly returns a new Chain with some default middleware handlers already in the chain with the following order:

WithPanicRecovery() -> WithLogging() -> WithInstrument() -> WithCacheControl()

To use classic version:

// create classic chain
classicChain := nelly.Classic()

// extend the classic chain with some default chain (WithCORS)
chian := classicChain.Append(WithCORS(opts), someHandler, ...)

Using any of the default handlers which implements middleware handler is recommended in the following order:

Recovery

Tracing (OpenTelemetry)

Nelly had a support for tracing using OpenTelemetry which has been deprecated in the favour of othttp (OpenTelemetry HTTP Handler) which only support the traditional net/http handler. Unfortunately, it is not possible to use othttp directly with nelly middleware, but it could be used with julienschmidt/httprouter as follow:

// import "go.opentelemetry.io/otel/instrumentation/othttp"

// create new router
router := httprouter.New()

// use chainedHandler (httprouter.Handle)
router.GET("/", chainedHandler)

// wrap httprouter with OpenTelemetry http
otWrap := othttp.NewHandler(router, "server")

log.Fatal(http.ListenAndServe(":8080", otWrap))

The router will be wrapped with othttp handler which support the traditional net/http Handler.

Todo:

  • Improve metrics handler and more metrics
  • Improve timeout for long running requests