One of the most valuable patterns to learn when building web services and websites in Go is one we already utilized in Chapter 2, Adding Authentication, where we decorated http.Handler
types by wrapping them with other http.Handler
types. For our RESTful API, we are going to apply this same technique to http.HandlerFunc
functions, to deliver an extremely powerful way of modularizing our code without breaking the standard func(w http.ResponseWriter, r *http.Request)
interface.
Most web APIs require clients to register an API key for their application, which they are asked to send along with every request. Such keys have many purposes, ranging from simply identifying which app the requests are coming from to addressing authorization concerns in situations where some apps are only able to do limited things based on what a user has allowed. While we don't actually need to implement API keys for our application, we are going to ask clients to provide one, which will allow us to add an implementation later while keeping the interface constant.
Add the essential main.go
file inside your api
folder:
package main func main(){}
Next we are going to add our first HandlerFunc
wrapper function called withAPIKey
to the bottom of main.go
:
func withAPIKey(fn http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !isValidAPIKey(r.URL.Query().Get("key")) { respondErr(w, r, http.StatusUnauthorized, "invalid API key") return } fn(w, r) } }
As you can see, our withAPIKey
function both takes an http.HandlerFunc
type as an argument and returns one; this is what we mean by wrapping in this context. The withAPIKey
function relies on a number of other functions that we are yet to write, but you can clearly see what's going on. Our function immediately returns a new http.HandlerFunc
type that performs a check for the query parameter key
by calling isValidAPIKey
. If the key is deemed invalid (by the return of false
), we respond with an invalid API key
error. To use this wrapper, we simply pass an http.HandlerFunc
type into this function to enable the key
parameter check. Since it returns an http.HandlerFunc
type too, the result can then be passed into other wrappers or given directly to the http.HandleFunc
function to actually register it as the handler for a particular path pattern.
Let's add our isValidAPIKey
function next:
func isValidAPIKey(key string) bool { return key == "abc123" }
For now, we are simply going to hardcode the API key as abc123
; anything else will return false
and therefore be considered invalid. Later we could modify this function to consult a configuration file or database to check the authenticity of a key without affecting how we use the isValidAPIKey
method, or indeed the withAPIKey
wrapper.
Now that we can be sure a request has a valid API key, we must consider how handlers will connect to the database. One option is to have each handler dial its own connection, but this isn't very
DRY (Don't Repeat Yourself), and leaves room for potentially erroneous code, such as code that forgets to close a database session once it is finished with it. Instead, we will create another HandlerFunc
wrapper that manages the database session for us. In main.go
, add the following function:
func withData(d *mgo.Session, f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { thisDb := d.Copy() defer thisDb.Close() SetVar(r, "db", thisDb.DB("ballots")) f(w, r) } }
The withData
function takes a MongoDB session representation using the mgo
package, and another handler as per the pattern. The returned http.HandlerFunc
type will copy the database session, defer the closing of that copy, and set a reference to the ballots
database as the db
variable using our SetVar
helper, before finally calling the next HandlerFunc
. This means that any handlers that get executed after this one will have access to a managed database session via the GetVar
function. Once the handlers have finished executing, the deferred closing of the session will occur, which will clean up any memory used by the request without the individual handlers having to worry about it.
Our pattern allows us to very easily perform common tasks on behalf of our actual handlers. Notice that one of the handlers is calling OpenVars
and CloseVars
so that GetVar
and SetVar
may be used without individual handlers having to concern themselves with setting things up and tearing them down. The function will return an http.HandlerFunc
that first calls OpenVars
for the request, defers the calling of CloseVars
, and calls the specified handler function. Any handlers wrapped with withVars
will be able to use GetVar
and SetVar
.
Add the following code to main.go
:
func withVars(fn http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { OpenVars(r) defer CloseVars(r) fn(w, r) } }
There are lots of other problems that can be addressed using this pattern; and whenever you find yourself duplicating common tasks inside handlers, it's worth considering whether a handler wrapper function could help simplify code.
The same-origin security policy mandates that AJAX requests in web browsers be only allowed for services hosted on the same domain, which would make our API fairly limited since we won't be necessarily hosting all of the websites that use our web service. The CORS technique circumnavigates the same-origin policy, allowing us to build a service capable of serving websites hosted on other domains. To do this, we simply have to set the Access-Control-Allow-Origin
header in response to *
. While we're at it—since we're using the Location
header in our create poll call—we'll allow that header to be accessible by the client too, which can be done by listing it in the Access-Control-Expose-Headers
header. Add the following code to main.go
:
func withCORS(fn http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Expose-Headers", "Location") fn(w, r) } }
This is the simplest wrapper function yet; it just sets the appropriate header on the ResponseWriter
type and calls the specified http.HandlerFunc
type.
In this chapter, we are handling CORS explicitly so we can understand exactly what is going on; for real production code, you should consider employing an open source solution such as https://github.com/fasterness/cors.