aboutsummaryrefslogtreecommitdiff
path: root/mapcache/main.go
blob: 0b6b697bf7e145987169d6e409e605e77842532b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Package mapcache is very similar to rediscache, except there are no
// external dependencies and the page output is saved in a map
package mapcache

import (
	"bytes"
	"net/http"
	"sync"
)

type responseWriter struct {
	Headers    http.Header
	StatusCode int
	Data       []byte
	buf        *bytes.Buffer
}

func (rw *responseWriter) Header() http.Header {
	if rw.Headers == nil {
		rw.Headers = http.Header{}
	}

	return rw.Headers
}

func (rw *responseWriter) Write(msg []byte) (int, error) {
	if rw.buf == nil {
		rw.buf = &bytes.Buffer{}
	}

	return rw.buf.Write(msg) //nolint:wrapcheck
}

// Simply for satisfying the http.ResponseWriter interface.
func (rw *responseWriter) WriteHeader(code int) {
	rw.StatusCode = code
}

// WriteData takes the internal buffer and writes out the entire
// contents to the 'Data' field for storage in Redis.
func (rw *responseWriter) WriteData() {
	rw.Data = rw.buf.Bytes()
}

func New() *Cache {
	return &Cache{
		m:      &sync.RWMutex{},
		cached: map[string]*responseWriter{},
	}
}

type Cache struct {
	m      *sync.RWMutex
	cached map[string]*responseWriter
}

func (c *Cache) Remove(path string) {
	c.m.Lock()
	delete(c.cached, path)
	c.m.Unlock()
}

func (c *Cache) Clear() {
	c.m.Lock()
	c.cached = map[string]*responseWriter{}
	c.m.Unlock()
}

// HandleWithParams is the same as Handle but caches for the GET params
// rather than discarding them.
func (c *Cache) HandleWithParams(next http.Handler) http.Handler {
	return c.handle(true, next)
}

// Handle is a Simple function that will cache the response for given handler in
// redis and instead of responding with the result from the handler it will
// simply dump the contents of the redis key if it exists.
func (c *Cache) Handle(next http.Handler) http.Handler {
	return c.handle(false, next)
}

func (c *Cache) handle(params bool, next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		subkey := r.URL.Path
		if params {
			subkey = r.URL.Path + "?" + r.URL.RawQuery
		}

	content:
		c.m.RLock()
		rw, ok := c.cached[subkey]
		c.m.RUnlock()
		if !ok {
			rw := &responseWriter{}
			next.ServeHTTP(rw, r)

			rw.WriteData()

			c.m.Lock()
			c.cached[subkey] = rw
			c.m.Unlock()

			// We got the content, let's go back around again and dump
			// it out from the cache
			goto content
		}

		if rw.Headers != nil {
			for k, v := range rw.Headers {
				w.Header()[k] = v
			}
		}

		if rw.StatusCode != 0 {
			w.WriteHeader(rw.StatusCode)
		}
		_, _ = w.Write(rw.Data)
	})
}