A web service is nothing more than a simple Go program that binds to a specific HTTP address and port and serves requests, so we get to use all our command-line tool-writing knowledge and techniques.
Before we write our main
function, let's look at a few design goals of our API program:
Atop the main.go
file, replace the main
function placeholder with the following code:
func main() { var ( addr = flag.String("addr", ":8080", "endpoint address") mongo = flag.String("mongo", "localhost", "mongodb address") ) flag.Parse() log.Println("Dialing mongo", *mongo) db, err := mgo.Dial(*mongo) if err != nil { log.Fatalln("failed to connect to mongo:", err) } defer db.Close() mux := http.NewServeMux() mux.HandleFunc("/polls/", withCORS(withVars(withData(db, withAPIKey(handlePolls))))) log.Println("Starting web server on", *addr) graceful.Run(*addr, 1*time.Second, mux) log.Println("Stopping...") }
This function is the entirety of our API main
function, and even as our API grows, there is just a little bloat we would need to add to this.
The first thing we do is to specify two command-line flags, addr
and mongo
, with some sensible defaults, and to ask the flag
package to parse them. We then attempt to dial the MongoDB database at the specified address. If we are unsuccessful, we abort with a call to log.Fatalln
. Assuming the database is running and we are able to connect, we store the reference in the db
variable before deferring the closing of the connection. This ensures our program properly disconnects and tidies up after itself when it ends.
We then create a new http.ServeMux
object, which is a request multiplexer provided by the Go standard library, and register a single handler for all requests that begin with the path /polls/
.
Finally, we make use of Tyler Bunnell's excellent Graceful
package, which can be found at https://github.com/stretchr/graceful to start the server. This package allows us to specify time.Duration
when running any http.Handler
(such as our ServeMux
handler), which will allow any in-flight requests some time to complete before the function exits. The Run
function will block until the program is terminated (for example, when someone presses Ctrl + C).
It is when we call HandleFunc
on the ServeMux
handler that we are making use of our handler function wrappers, with the line:
withCORS(withVars(withData(db, withAPIKey(handlePolls)))))
Since each function takes an http.HandlerFunc
type as an argument and also returns one, we are able to chain the execution just by nesting the function calls as we have done previously. So when a request comes in with a path prefix of /polls/
, the program will take the following execution path:
withCORS
is called, which sets the appropriate header.withVars
is called, which calls OpenVars
and defers CloseVars
for the request.withData
is then called, which copies the database session provided as the first argument and defers the closing of that session.withAPIKey
is called next, which checks the request for an API key and aborts if it's invalid, or else calls the next handler function.handlePolls
is then called, which has access to variables and a database session, and which may use the helper functions in respond.go
to write a response to the client.withAPIKey
that just exits.withData
that exits, therefore calling the deferred session Close
function and clearing up the database session.withVars
that exits, therefore calling CloseVars
and tidying that up too.withCORS
that just exits.The order that we nest the wrapper functions in is important, because withData
puts the database session for each request in that request's variables map using SetVar
. So withVars
must be outside withData
. If this isn't respected, the code will likely panic and you may want to add a check so that the panic is more meaningful to other developers.