Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to store or cache values to be served in http requests in Golang?

I am writing some Go web services (also implementing the webserver in Go with http.ListenAndServe). I have a map of structs which I would like to keep in memory (with an approximate data size of 100Kb) to be used by different HTTP requests.

How can this be achieved in Go? I am thinking to use global package variables or caching systems (like memcache/groupcache).

like image 620
Daniele B Avatar asked Aug 28 '13 12:08

Daniele B


People also ask

Which HTTP requests can be cached?

The POST response body can only be cached for subsequent GET requests to the same resource. Set the Location or Content-Location header in the POST response to communicate which resource the body represents. So the only technically valid way to cache a POST request, is for subsequent GETs to the same resource.

Which HTTP caching header is used to avoid making requests to the origin server?

Expiration is the caching mechanism by which a client can entirely avoid making requests to the origin server. When the origin server specifies an explicit expiration time in the resource, a cache can check that expiration time and respond accordingly without having to contact the server first.

Can we cache https?

Myth #7 – HTTPS Never Caches People often claim that HTTPS content is never cached by the browser; perhaps because that seems like a sensible idea in terms of security. In reality, HTTPS caching is controllable with response headers just like HTTP.

Can we store dataset in cache?

In a web farm the Session can be local (which works only if affinity is set), or remote (state server or database, or custom), but the cache is always local. So, storing a DataTable in the cache will consume memory, but it will not use serialization.


1 Answers

In addition to the answers you've already received, consider making use of receiver-curried method values and http.HandlerFunc.

If your data is data that is loaded before the process starts, you could go with something like this:

type Common struct {
    Data map[string]*Data
}

func NewCommon() (*Common, error) {
    // load data
    return c, err
}

func (c *Common) Root(w http.ResponseWriter, r *http.Request) {
    // handler
}

func (c *Common) Page(w http.ResponseWriter, r *http.Request) {
    // handler
}

func main() {
    common, err := NewCommon()
    if err != nil { ... }

    http.HandleFunc("/", common.Root)
    http.HandleFunc("/page", common.Page)

    http.ListenAndServe(...)
}

This works nicely if all of the Common data is read-only. If the Common data is read/write, then you'll want to have something more like:

type Common struct {
    lock sync.RWMutex
    data map[string]Data // Data should probably not have any reference fields
}

func (c *Common) Get(key string) (*Data, bool) {
    c.lock.RLock()
    defer c.lock.RUnlock()
    d, ok := c.data[key]
    return &d, ok
}

func (c *Common) Set(key string, d *Data) {
    c.lock.Lock()
    defer c.lock.Unlock()
    c.data[key] = *d
}

The rest is basically the same, except instead of accessing the data through the receiver's fields directly, you'd access them through the getters and setters. In a webserver where most of the data is being read, you will probably want an RWMutex, so that reads can be executed concurrently with one another. Another advantage of the second approach is that you've encapsulated the data, so you can add in transparent writes to and/or reads from a memcache or a groupcache or something of that nature in the future if your application grows such a need.

One thing that I really like about defining my handlers as methods on an object is that it makes it much easier to unit test them: you can easily define a table driven test that includes the values you want and the output you expect without having to muck around with global variables.

like image 195
Kyle Lemons Avatar answered Sep 21 '22 20:09

Kyle Lemons