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.
3.145.51.153