Wrapping handler functions

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.

API key

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.

Database session

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.

Per request variables

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.

Cross-browser resource sharing

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.

Tip

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.21.158