How to chain HTTP Handlers in Go

Nikita
Nikita
Dec 11, 2017 · 4 min read

Hello! Today i want to share a way, how to chain HTTP Handlers in Go programming language.

Before Go, i use NodeJS +ExpressJS to write HTTP server applications. This framework provide simple way to use middlewares and chaining many router points, so you don’t have to specify whole route URL to add handler to it.

Router points concept

The idea is to ‘split’ your route and handle each part with chain of handlers, responsible for only one part. It’s simple to understand and very easy to use and maintain, so first of all i tried to make something similar in Go.

Out of the box, Go provides great http package, which contains many different tools, and, of course ListenAndServe method, which starts HTTP server on given port and Handler. So what that Handler is?

type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

Handler is interface, which has one method — ServeHTTP to handle incoming requests and write an response.

But what if we want to specify an handler for each of our root routes, e.g. /api/, /home, /about, etc. ?

To cope with this helps the ServeMux — an http requests multiplexer. With ServeMux we can specify an Handler of HandleFunc to serve any given route, but trouble here is that we cant make any nested ServeMux.

Example from documentation:

mux := http.NewServeMux()
mux.Handle("/api/", apiHandler{})
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
// The "/" pattern matches everything, so we need to check
// that we're at the root here.
if req.URL.Path != "/" {
http.NotFound(w, req)
return
}
fmt.Fprintf(w, "Welcome to the home page!")
})

We can see, that in example specified a handler for ‘/api/’ routes and handle func for root routes, so any routes, which starts with ‘/api/*’ will be served with apiHandler Handle func, but if we want chain an usersHandler to apiHandler we just don’t have any way to do it without any additional brainstorming and coding.

For this reason i write a tiny library — gosplitter, which provide only one common method Match(url string, mux *http.ServeMux, http.Handler|http.HandlerFunc|interface{}) — which matches given route part to and Handler, HandlerFunc or even any struct that you specify!

Let’s take a look for an example:

/**
* Specify handle types
*/
type APIV1Handler struct {
mux *http.ServeMux
}

type ColorsHandler struct {
mux *http.ServeMux
}

/**
* Start - binds parent to children
*/
func (a *APIV1Handler) Start() {
var colorsHandler = ColorsHandler{
mux: a.mux,
}
gosplitter.Match("/ping", a.mux, a.HandlePing())
gosplitter.Match("/colors", a.mux, colorsHandler)
colorsHandler.Start()
}
func (c *ColorsHandler) Start() {
gosplitter.Match("/black", c.mux, c.HandleBlack())
}
/**
* Simple http handler function
*/
func (a *APIV1Handler) HandlePing() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
}
}



func (c *ColorsHandler) HandleBlack() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("#000000"))
}
}

func main() {
var mux = http.NewServeMux()
var apiV1 = APIV1Handler{
mux: mux,
}

/**
* bind api handler to root
*/
gosplitter.Match("/api/v1", mux, apiV1)
/**
* start api handler
*/

apiV1.Start()

}

Tour of example:

/**
* Specify handle types
*/
type APIV1Handler struct {
mux *http.ServeMux
}

type ColorsHandler struct {
mux *http.ServeMux
}

Here we specify our Handlers, which are a structures

/**
* Start - binds parent to children
*/
func (a *APIV1Handler) Start() {
var colorsHandler = ColorsHandler{
mux: a.mux,
}
gosplitter.Match("/ping", a.mux, a.HandlePing())
gosplitter.Match("/colors", a.mux, colorsHandler)
colorsHandler.Start()
}
func (c *ColorsHandler) Start() {
gosplitter.Match("/black", c.mux, c.HandleBlack())
}

Add Start method to our handlers, to ‘activate’ handling functions.

/**
* Simple http handler function
*/
func (a *APIV1Handler) HandlePing() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
}
}



func (c *ColorsHandler) HandleBlack() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("#000000"))
}
}

Add HandlePing to our APIV1Handler which responds with ‘pong’ and HandleBlack which responds with #000000.

func main() {
var mux = http.NewServeMux()
var apiV1 = APIV1Handler{
mux: mux,
}

/**
* bind api handler to root
*/
gosplitter.Match("/api/v1", mux, apiV1)
/**
* start api handler
*/

apiV1.Start()

}

In our main function we create new ServeMux then we make instance of APIV1Handler, bind it to ‘/api/v1’ route and then Start() it.

So after all this simple operations we have two working routes: ‘/api/v1/ping’ which responds with ‘pong’ and ‘/api/v1/colors/black’ which responds with #000000.

Isn’t it easy to use? I think so, and now use this library in my own projects for convenient route splitting and handlers chaining :)

Thanks for reading! Any suggestions and critiques are welcome!

Nikita

Written by

Nikita

https://github.com/goncharovnikita

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade