parent
024f514886
commit
c1c1bb2c4c
@ -0,0 +1,49 @@ |
|||||||
|
// Package cache implements a simple LRU cache which can be completely purged
|
||||||
|
// whenever needed.
|
||||||
|
package cache |
||||||
|
|
||||||
|
import ( |
||||||
|
lru "github.com/hashicorp/golang-lru" |
||||||
|
) |
||||||
|
|
||||||
|
// Cache describes an in-memory cache for arbitrary objects, which has a Purge
|
||||||
|
// method for clearing everything at once.
|
||||||
|
type Cache interface { |
||||||
|
Get(key string) interface{} |
||||||
|
Set(key string, value interface{}) |
||||||
|
Purge() |
||||||
|
} |
||||||
|
|
||||||
|
type cache struct { |
||||||
|
cache *lru.Cache |
||||||
|
} |
||||||
|
|
||||||
|
// New instantiates and returns a new Cache which can hold up to the given
|
||||||
|
// number of entries.
|
||||||
|
func New(size int) Cache { |
||||||
|
c, err := lru.New(size) |
||||||
|
|
||||||
|
// instantiating the lru cache can't realistically fail
|
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
return &cache{c} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cache) Get(key string) interface{} { |
||||||
|
value, ok := c.cache.Get(key) |
||||||
|
if !ok { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
return value |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cache) Set(key string, value interface{}) { |
||||||
|
c.cache.Add(key, value) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cache) Purge() { |
||||||
|
c.cache.Purge() |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
package gmi |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"context" |
||||||
|
"io" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"git.sr.ht/~adnano/go-gemini" |
||||||
|
"github.com/mediocregopher/blog.mediocregopher.com/srv/cache" |
||||||
|
) |
||||||
|
|
||||||
|
type cacheRW struct { |
||||||
|
gemini.ResponseWriter |
||||||
|
status gemini.Status |
||||||
|
mediaType string |
||||||
|
buf []byte |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cacheRW) SetMediaType(mediaType string) { |
||||||
|
c.mediaType = mediaType |
||||||
|
c.ResponseWriter.SetMediaType(mediaType) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cacheRW) WriteHeader(status gemini.Status, meta string) { |
||||||
|
c.status = status |
||||||
|
c.ResponseWriter.WriteHeader(status, meta) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cacheRW) Write(b []byte) (int, error) { |
||||||
|
c.buf = append(c.buf, b...) |
||||||
|
return c.ResponseWriter.Write(b) |
||||||
|
} |
||||||
|
|
||||||
|
func cacheMiddleware(cache cache.Cache) func(h gemini.Handler) gemini.Handler { |
||||||
|
|
||||||
|
type entry struct { |
||||||
|
mediaType string |
||||||
|
body []byte |
||||||
|
} |
||||||
|
|
||||||
|
pool := sync.Pool{ |
||||||
|
New: func() interface{} { return new(bytes.Reader) }, |
||||||
|
} |
||||||
|
|
||||||
|
return func(h gemini.Handler) gemini.Handler { |
||||||
|
return gemini.HandlerFunc(func( |
||||||
|
ctx context.Context, |
||||||
|
rw gemini.ResponseWriter, |
||||||
|
r *gemini.Request, |
||||||
|
) { |
||||||
|
|
||||||
|
id := r.URL.String() |
||||||
|
|
||||||
|
if value := cache.Get(id); value != nil { |
||||||
|
|
||||||
|
entry := value.(entry) |
||||||
|
|
||||||
|
if entry.mediaType != "" { |
||||||
|
rw.SetMediaType(entry.mediaType) |
||||||
|
} |
||||||
|
|
||||||
|
reader := pool.Get().(*bytes.Reader) |
||||||
|
defer pool.Put(reader) |
||||||
|
|
||||||
|
reader.Reset(entry.body) |
||||||
|
|
||||||
|
io.Copy(rw, reader) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
cacheRW := &cacheRW{ |
||||||
|
ResponseWriter: rw, |
||||||
|
status: gemini.StatusSuccess, |
||||||
|
} |
||||||
|
|
||||||
|
h.ServeGemini(ctx, cacheRW, r) |
||||||
|
|
||||||
|
if cacheRW.status == gemini.StatusSuccess { |
||||||
|
cache.Set(id, entry{ |
||||||
|
mediaType: cacheRW.mediaType, |
||||||
|
body: cacheRW.buf, |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue