web-server/cache.go

74 lines
1.5 KiB
Go

package main
import (
"net/http"
"runtime"
"unsafe"
)
type realCacheEntry struct {
r Response
cache map[string]cacheEntry
key string
}
// store a pointer to the real cache entry as an int here
// so that the garbage collector can collect it if needed
type cacheEntry struct {
p uintptr
//r Response
}
func (e cacheEntry) access() *Response {
ptr := unsafe.Pointer(e.p)
entry := (*realCacheEntry)(ptr)
return &entry.r
}
func addEntry(cache map[string]cacheEntry, s string, r Response) cacheEntry {
entry := new(realCacheEntry)
entry.r = r
entry.cache = cache
entry.key = s
cache[s] = cacheEntry{
p: (uintptr)(unsafe.Pointer(entry)),
}
runtime.SetFinalizer(entry, func(e *realCacheEntry) {
delete(e.cache, e.key)
})
return cache[s]
}
func Cache(h http.Handler) http.Handler {
c := make(map[string]cacheEntry)
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
rw.WriteHeader(403)
rw.Write([]byte("invalid request type"))
return
}
// NOTE: not threadsafe, TODO fix that
entry, exists := c[r.URL.String()]
if exists {
// NOTE: the finalizer can theoretically execute between the map read
// and here
entry.access().WriteResponse(rw)
} else {
rc := ResponseCollector{}
// copy request in case they modify it
req := *r
h.ServeHTTP(&rc, &req)
resp := rc.CollectResponse()
if resp.Code == 200 {
addEntry(c, r.URL.String(), resp)
}
resp.WriteResponse(rw)
}
// TODO bookkeeping for the cache here
})
}