Sharing data between handlers

If we want to keep our handlers as pure as the http.Handler interface from the Go standard library, while still extracting common functionality into our own methods, we need a way of sharing data between handlers. The HandlerFunc signature that follows tells us that we are only allowed to pass in an http.ResponseWriter object and an http.Request object, and nothing else:

type HandlerFunc func(http.ResponseWriter, *http.Request)

This means that we cannot create and manage database session objects in one place and pass them into our handlers, which is ideally what we want to do.

Instead, we are going to implement an in-memory map of per-request data, and provide an easy way for handlers to access it. Alongside the twittervotes and counter folders, create a new folder called api and create a new file called vars.go inside it. Add the following code to the file:

package main
import (
  "net/http"
  "sync"
)
var vars map[*http.Request]map[string]interface{}
var varsLock sync.RWMutex

Here we declare a vars map that has a pointer to an http.Request type as its key, and another map as the value. We will store the map of variables keyed with the request instances that the variables belong to. The varsLock mutex is important, as our handlers will all be trying to access and change the vars map at the same time as handling many concurrent HTTP requests, and we need to ensure that they do this safely.

Next we are going to add the OpenVars function that allows us to prepare the vars map to hold variables for a particular request:

func OpenVars(r *http.Request) {
  varsLock.Lock()
  if vars == nil {
    vars = map[*http.Request]map[string]interface{}{}
  }
  vars[r] = map[string]interface{}{}
  varsLock.Unlock()
}

This function first locks the mutex so that we can safely modify the map, before ensuring that vars contains a non-nil map, which would otherwise cause a panic when we try to access its data. Finally, it assigns a new empty map value using the specified http.Request pointer as the key, before unlocking the mutex and therefore freeing other handlers to interact with it.

Once we have finished handling the request, we need a way to clean up the memory that we are using here; otherwise the memory footprint of our code would continuously increase (also known as a memory leak). We do this by adding a CloseVars function:

func CloseVars(r *http.Request) {
  varsLock.Lock()
  delete(vars, r)
  varsLock.Unlock()
}

This function safely deletes the entry in the vars map for the request. As long as we call OpenVars before we try to interact with the variables, and CloseVars when we have finished, we will be free to safely store and retrieve data for each request. However, we don't want our handler code to have to worry about locking and unlocking the map whenever it needs to get or set some data, so let's add two helper functions, GetVar and SetVar:

func GetVar(r *http.Request, key string) interface{} {
  varsLock.RLock()
  value := vars[r][key]
  varsLock.RUnlock()
  return value
}
func SetVar(r *http.Request, key string, value interface{}) {
  varsLock.Lock()
  vars[r][key] = value
  varsLock.Unlock()
}

The GetVar function will make it easy for us to get a variable from the map for the specified request, and SetVar allows us to set one. Notice that the GetVar function calls RLock and RUnlock rather than Lock and Unlock; this is because we're using sync.RWMutex, which means it's safe for many reads to occur at the same time, as long as a write isn't happening. This is good for performance on items that are safe to concurrently read from. With a normal mutex, Lock would block execution—waiting for the thing that has locked it to unlock it—while RLock will not.

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

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