Using Negroni middleware in Golang for specific routes with httprouter
In the last days I’ve played a little bit with negroni
middleware and httprouter
router. I found that the documentation about how can we use negroni middlewares for specific routes with httprouter is pretty poor. So I decided to write this article to share with you what I’ve found.
Negroni is an idiomatic approach to web middleware in Golang which will help you build and stack middleware very easily. It comes with some default middlewares like:
- negroni.Recovery - Panic Recovery Middleware.
- negroni.Logger - Request/Response Logger Middleware.
- negroni.Static - Static File serving under the "public" directory.
But it also letting you create your own middlewares very easily:
func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { // do some stuff before next(rw, r) // do some stuff after } … n := negroni.New() n.Use(negroni.HandlerFunc(MyMiddleware))
Negroni is BYOR (Bring your own Router) so we can use it with httprouter.
Httprouter is a Golang lightweight and high performance HTTP request router which is fast and has low memory consumption. TLDR: it is one of the fastest routers.
Here you have an example on how a simple route can be added when using httprouter:
router := httprouter.New() router.POST("/login", loginHandler)
Where loginHandler will look like:
func loginHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) { // login controller logic }
Now lets suppose you want to access GET profile
endpoint but you need to be authenticated.
We will start by creating an authentication middleware:
// auth middleware func auth(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { // do some stuff before log.Println("auth middleware -> before executing controller") // call endpoint handler next(rw, r) // do some stuff after log.Println("auth middleware -> after the controller was executed") }
Then we will create a handler which will return profile information after the authentication will be done. This handler will be sent to the auth middleware as a callback using "next" parameter:
func profileHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "This is the content of the profile controller\n") log.Println("executing profile controller") }
Now let’s send everything to the router:
nProfile := negroni.New() // set the middleware nProfile.Use(negroni.HandlerFunc(auth)) // set the handler nProfile.UseHandlerFunc(profileHandler) // attach negroni middleware and handler to our route router.Handler("GET", "/profile", nProfile)
That was simple, but now if we have a route which contains parameters inside it (like /hello/:name
) we cannot access it from our handler function. To solve that, I’ve written the following functions which will help us call the handler with the parameters:
// callwithParams function is helping us to call controller from middleware having access to URL params func callWithParams(router *httprouter.Router, handler func(w http.ResponseWriter, r *http.Request, ps httprouter.Params)) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { params := getUrlParams(router, r) handler(w, r, params) } } // getUrlParams function is extracting URL parameters func getUrlParams(router *httprouter.Router, req *http.Request) httprouter.Params { _, params, _ := router.Lookup(req.Method, req.URL.Path) return params }
And will be used like this:
router := httprouter.New() nHello := negroni.New() // add auth middleware nHello.Use(negroni.HandlerFunc(auth)) // add handler using callWithParams function so we can access the URL parameters in handler nHello.UseHandlerFunc(callWithParams(router, helloHandler)) router.Handler("GET", "/hello/:name", nHello) n.UseHandler(router) log.Fatal(http.ListenAndServe(":8080", n))
The full working example can be found here.