In my last tutorial we learned about HTTP routing using Go's net/http
package and Gorilla's mux
package, and we briefly talked about HTTP Handlers,
which are actually a major building block of Go servers.
You
can use Handlers
to organize your web app's functionality with middleware like in
Express. If you've worked with Express, you know middleware can carry
out really useful functionality like authentication, logging, and
input validation. So in this tutorial, we'll learn about what Handlers
are and how to use them as middleware.
If
you'd like to follow along, in your GOPATH's
src
directory add a gorilla-tutorial-2
directory with a file names server.go.
If you want to follow along in Express as well, run npm
install express and
npm
install morgan add a
file named server.js.
You can find the source code for the Go and Express examples on this GitHub repository.
You can find the source code for the Go and Express examples on this GitHub repository.
So
what is a Handler?
The
definition of a Handler
in Go's http
source code is:
type Handler interface{ ServeHTTP(ResponseWriter, *Request) }
So a Handler can be any type as long as it has a ServeHTTP method that takes in a ResponseWriter and a Request pointer. Hmm, those parameters sound familiar...
func serveHelloWorld(w http.ResponseWriter, r *http.Request){ fmt.Fprintf(w, “Hello world!”) }
As you can see, request-handling functions like serveHelloWorld can be used as ServeHTTP methods. Let's try that! Put this in server.go:
package main import ( "fmt" "net/http" ) type helloWorldHandler struct{} //1 func (h helloWorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){//2 fmt.Fprintf(w, "Hello world!") } func main(){ http.Handle("/", helloWorldHandler{}) //3 http.ListenAndServe(":1123", nil) }
1. Our
helloWorldHandler
type is just a blank struct with a ServeHTTP
method.
2. We
give our Handler
a ServeHTTP
method based on serveHelloWorld
to satisfy the Handler
interface
3. To
put a Handler
on a route in the server, instead of using http.HandleFunc,
we use http.Handle.
Now if
you request localhost:1123,
your server should serve the message “Hello world!”. This server
works like the hello world server in the last tutorial, except
instead of using HandleFunc
it uses Handler.
Here's a diagram of how our new hello world server works.
You know what else looks like a ServeHTTP method taking in a ResponseWriter and a Request pointer? Express middleware taking in request and response objects!
function serveHelloWorld(req, res){ res.send('Hello world!') }
ServeHTTP
methods of Handlers
are the central to Go web middleware, and they work a lot like
Express middleware. But the struct part of our helloWorldHandler
was pointless. We only needed the ServeHTTP
method. Luckily, we have a data type for making Handlers
from functions.
Introducing
HandlerFuncs
Go's
http
package defines this awesome data type
type
HandlerFunc func(ResponseWriter, *Request)
that
converts any function taking in a ResponseWriter
and Request
pointer to a Handler.
To try
that out, add the serveHelloWorld
function in server.go
and replace the first line of main
with
helloHandler := http.HandlerFunc(serveHelloWorld) http.Handle("/", helloHandler)
And
your server should still work the same, and just like on an Express
server, you're using a function to handle your routes. As in, a
function that happens to be a Handler.
What's really cool is what ServeHTTP
method HandlerFuncs
use to satisfy the Handler
interface:
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request){ f(w, r) }
A
HandlerFunc's
ServeHTTP
method is a function that calls
the HandlerFunc
itself! A HandlerFunc
is basically ITS OWN SERVEHTTP METHOD!
By the way, this is how http.HandleFunc works. Behind the scenes it takes the function you give it and converts it to a HandlerFunc.
I
know, right? This diagram shows what's going on with our
HandlerFunc.
By the way, this is how http.HandleFunc works. Behind the scenes it takes the function you give it and converts it to a HandlerFunc.
So
we know a Handler
is any type with a ServeHTTP
method. Now let's see how those ServeHTTPs
can be used for middleware chaining.
ServeHTTP()
is the next() of Go middleware chaining
In
Express, what's really great about middlewares is you can add
multiple middlewares to the same route and have them run in a chain
so each middleware handles a different functionality.
In
Express you make a middleware function part of a chain by giving it a
next
parameter after its req
and res
parameters. That next
parameter is a function used to call the next middleware. For an
example, if you're following along in Express, add this to server.js:
var express = require('express'), timers = require('timers'); var app = express(); var sleepMiddleware = function(req, res, next){ //1 console.log('Sloth is sleeping, please wait'); timers.setTimeout(function(){ next(); //1 }, 3000); } app.use('/sloths', sleepMiddleware, function(req, res){ //2 res.send('<img src="http://andyhaskell.github.io/Slothful-Soda/images/sloth.jpg" width="240px" height="300px" />'); }); app.use('/', function(req, res){ res.send('Hello world!'); }); app.listen(1123);
1.
sleepMiddleware
takes in a request, a response, and the next middleware. It tells us
the sloth is sleeping, pauses for 3 seconds, and then calls the next
middleware by calling next()
after the pause.
2. On
“/sloths”
we call sleepMiddleware
followed by an anonymous middleware serving a picture of a sloth.
That's our middleware chain, and the anonymous middleware is
sleepMiddleware's
next().
If you
run node
server.js and go to
localhost:1123/sloths,
you should get:
In server.js, add this line to the list of modules you're requiring:
In
Express, we make one middleware call another by calling next()
in the first function.
In Go, we have one middleware call the next one by calling the next Handler's ServeHTTP in the first Handler's ServeHTTP!
In Go, we have one middleware call the next one by calling the next Handler's ServeHTTP in the first Handler's ServeHTTP!
To try
this, in server.go
add "time"
to the list of imported packages and add this function:
func sloth(w http.ResponseWriter, r *http.Request){ fmt.Fprintf(w, `<body><img src="http://andyhaskell.github.io/Slothful-Soda/images/sloth.jpg" width="240px" height="300px" /></body>`) }
Then
in the beginning of main
add this code:
slothHandler := http.HandlerFunc(sloth) //1 sleepHandler := http.HandlerFunc( func(w http.ResponseWriter, r *http.Request){ //2 fmt.Println("Sloth is sleeping, please wait") time.Sleep(3000 * time.Millisecond) slothHandler.ServeHTTP(w, r) //2 }) http.Handle("/sloths", sleepHandler) //3
1. In
slothHandler,
we serve the sloth picture.
2.
sleepHandler
tells the user the sloth is sleeping and pauses 3 seconds. Then
slothHandler.ServeHTTP
is called, causing slothHandler
to take over, serving the sloth picture.
3.
sleepHandler
calls slothHandler
so we have a chain. Since sleepHandler
starts the chain, we put the chain on the “/sloths”
route by putting sleepHandler
on the route.
If you
run go
install and
gorilla-tutorial-2
and then go to localhost:1123/sloths
you should get the same results as in the Express version.
Here's
a diagram of how that works:
Chaining
with closures
In the
last example, we had a chain of middlewares, but the order they ran
in was hard-coded. It'd be good if our chain worked like Express where
we define the middleware to call next()
without knowing what that next middleware will be.
Luckily,
Go supports closures,
where we can pass in arguments to a function that then uses those arguments to make a new function.
So
what if we had a function that took in a Handler
and returned a HandlerFunc
that does some sort of functionality and then called the Handler's
ServeHTTP?
Then we could pass in any
Handler
we wanted
and we'd get back a
HandlerFunc
chained with our Handler!
To
see this in action, add this function to server.go:
func sleepConstructor(h http.Handler) http.Handler{ //1 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){ //2 fmt.Println("Sloth is sleeping, please wait") time.Sleep(3000 * time.Millisecond) h.ServeHTTP(w, r) //3 }) }
And
add this code to main:
http.Handle("/sloths2", sleepConstructor(slothHandler)) //4
Here's
what's going on:
1.
sleepConstructor
takes in a Handler
and returns a Handler.
That's the key feature of this trick.
2. We
have our sleepHandler
function as the HandlerFunc
to be returned, except...
3.
Instead of calling the ServeHTTP
of a pre-defined Handler,
it calls the ServeHTTP
of the Handler
passed in, chaining sleepConstructor's
returned HandlerFunc
with the Handler
passed in.
4.
sleepConstructor(slothHandler)
makes our chain, which we pass to "/sloths2".
So we
can make a middleware chain by making a function that takes in one
Handler
and gives us a middleware function chained to that Handler,
that function itself being a Handler.
That's
actually how StripPrefix/FileServer works
Here's
a practical example of Go middleware chaining. Remember how in the
last tutorial we had a StripPrefix
function? And how we gave that function our FileServer?
http.Handle("/images/", http.StripPrefix("/images/", http.FileServer(http.Dir("public/images"))))
FileServer
gives us a Handler,
which we pass into StripPrefix.
What we get back from StripPrefix
is itself a Handler,
so this is starting to look like another middleware constructor.
If you
look in the code for StripPrefix,
you'll see that's exactly what StripPrefix
is. When we call StripPrefix,
we get back a HandlerFunc
that removes a prefix from the Request's
path and then calls our
next Handler's
ServeHTTP,
which, in the case of our image server, serves our images.
Here's
what that looks like:
By
the way, we can make our chains as long as we want and in any order
Since
passing a Handler
into a middleware constructor gives us our chain as a new Handler,
we could in turn pass the chain into another middleware constructor
and get back yet another Handler
with its functionality added to the chain. So we can chain as many
middlewares as we want.
Sloths
love hibiscus flowers, so in server.go
let's make a teaConstructor
function that creates a HandlerFunc
that has our sloth drink a cup of hibiscus tea before calling the
next ServeHTTP:
func teaConstructor(h http.Handler) http.Handler{ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){ fmt.Println("*drinks hibiscus tea*") time.Sleep(500 * time.Millisecond) h.ServeHTTP(w, r) }) }
Now
we can chain teaConstructor
and sleepConstructor
in any order we want. To try that, add these chains to main
in server.go:
// In this chain the sloth sleeps, drinks some hibiscus tea, and then // serves our sloth picture. // // sleepConstructor takes in the Handler constructed from // calling teaConstructor(slothHandler) // | // V sleepTeaSlothChain := sleepConstructor(teaConstructor(slothHandler)) // In this chain the sloth drinks some hibiscus tea, sleeps, and then // serves our sloth picture. Note that since teaConstructor and // sleepConstructor take in a Handler and return a Handler, we can // have the constructors call each other in different orders to run the // middlewares in a different order. teaSleepSlothChain := teaConstructor(sleepConstructor(slothHandler)) // In this chain the sloth drinks two cups of hibiscus tea and then serves // our sloth picture. Since middleware constructors take in and return a // Handler, we can call the same constructor more than once in a chain to // run a middleware twice. teaTwiceChain := teaConstructor(teaConstructor(slothHandler))
As you
can see, if you have some middleware constructors, you can put them
in any order. But if you have a lot of constructors, the chain can
quickly get annoying to read and build upon.
Luckily,
there are several Go libraries, like MuxChain, Negroni, and Alice,
that you can use for more readable middleware chaining. In the next
section, I'll show you how the Alice library not only makes
middleware easier to work with, it also makes it real familiar if you
are coming from an Express background.
Express-like
middleware chaining with Alice
Alice
is a very simple middleware chaining library to work with. It's so
simple that at the time I'm writing this, there were just 36 lines of
code in the library excluding whitespace and comments. To get it,
just run go
get github.com/justinas/alice.
Then
in server.go,
import "github.com/justinas/alice".
Now
let's see what the middleware chains from before would look like if
we used Alice:
sleepTeaSlothChain := alice.New(sleepConstructor, teaConstructor).Then(slothHandler) //1 //2 //3 teaSleepSlothChain := alice.New(teaConstructor, sleepConstructor).Then(slothHandler) teaTwiceChain := alice.New(teaConstructor, teaConstructor).Then(slothHandler)
1. We
make a middleware chain with alice.New.
2. We
pass our middleware constructor functions (which can be any function
that takes in a Handler
and returns a Handler)
into alice.New
in the order we want their middlewares called in. We can pass in as
many constructors as we want to add to the chain.
3. We
need to pass in a Handler
to complete the chain, so then we pass slothHandler
into Then,
which causes Alice to construct a middleware chain from the
constructors and Handler.
Let's
try passing sleepTeaSlothChain
into our Go router using Alice. Add this to main
in server.go.
http.Handle("/sloths3", alice.New(sleepConstructor, teaConstructor).Then(slothHandler))
Compare
that to what it would look like in Express if we had those
middlewares:
app.use('/sloths3', sleepMiddleware, teaMiddleware, serveSloth)
What
if we wanted to apply a middleware to every route on the server?
Sometimes
you might want a middleware to be run on every request your server
gets. In Express, you would do that with this syntax:
app.use('/',
yourMiddleware)
Or
just app.use(yourMiddleware)
since / is the default route. One example of this would be running a
request logger middleware that gives you information about all the
requests your server gets.
In server.js, add this line to the list of modules you're requiring:
var
logger = require('morgan');
And
right after the line var
app = express();
add the line
app.use(logger('common'));
Now
when a request goes to your Express server, the first route it will
reach is your logger route that all routes match, so your request will
be logged to the console no matter what route your request is to.
In
Go, we have a catch-all route, so we could put a logging middleware
on the catch-all route, but how would that request then get to all
the other routes on the server? It turns out, every Go server has
one Handler
all of its requests go to. And where can we add that Handler
to the server?
http.ListenAndServe(":1123",
nil)
The
first parameter of ListenAndServe,
as we know, is the port we are listening on. That second parameter
that we are passing nil into is a Handler.
So if we passed in a HandlerFunc
for serveHelloWorld,
all requests would be handled with serveHelloWorld,
getting the response “Hello world!”.
But
if we don't pass in any Handler
to ListenAndServe,
a special router Handler
called http.DefaultServeMux
is used instead.
That
DefaultServeMux
is where all your routes go when you use http.Handle
and http.HandleFunc.
When it gets a request, DefaultServeMux
matches the request's path with a route and then calls ServeHTTP
for that route's Handler.
In
other words, DefaultServeMux
is a middleware that chains with the Handlers
on its routes.
And
if DefaultServeMux
is a middleware, it can be chained with other middlewares, like:
http.ListenAndServe(":1123",
LoggingMiddlewareConstructor(http.DefaultServeMux))
Let's
try that! In Gorilla, there is a package of useful middleware
handlers called the handlers package so to get it, go
get github.com/gorilla/handlers.
Add
“github.com/gorilla/handlers”
and “os” to the list of packages you are importing in server.go.
And
then in server.go
replace this line
http.ListenAndServe(":1123",
nil)
with:
muxWithLog := handlers.LoggingHandler(os.Stdout, http.DefaultServeMux) //1 http.ListenAndServe(":1123", muxWithLog) //2
1. We
pass os.Stdout
and DefaultServeMux
into the Gorilla LoggingHandler
function to create a new middleware chain, muxWithLog.
That chain starts with the request going to our LoggingHandler,
which logs the request and then passes it to the DefaultServeMux
to route and serve the request.
2. We
then use it on our server by passing muxWithLog
to ListenAndServe.
Now if
we request localhost:1123/sloths3,
we should get:
As you
can see from this tutorial, although the building block of a Go
net/http
or Gorilla server is a Handler,
these Handlers
are a lot like Express.js middleware. Because of the more strict
type system in Go, it is somewhat more complicated to use Handlers. But overall with both the net/http
middleware chaining and Gorilla's routing Go + Gorilla has a lot of
the flexibility of Express.
If
you want to go deeper into learning about middleware in Go, I highly
recommend trying out Negroni and MuxChain as alternatives to Alice
and also looking into the other middleware in Gorilla's handlers
package. Until next time, stay slothful!
Image
credits:
- The author of the picture of the sloth in the examples is Stefan Laube and the picture is public domain.
- The
author of the picture of the sloth at the end of the tutorial is
Emma Websdale and the picture is licensed under CC
BY 2.0.
Also, shoutout to my parents and Jai Singh from Boston Golang for proofreading this tutorial and to my dog Lola for making a perfect reaction face for finding out how HandlerFunc works.