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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build golangorg
// Package memcache provides a minimally compatible interface for
// google.golang.org/appengine/memcache
// and stores the data in Redis (e.g., via Cloud Memorystore).
package memcache
import (
"bytes"
"context"
"encoding/gob"
"encoding/json"
"errors"
"time"
"github.com/gomodule/redigo/redis"
)
var ErrCacheMiss = errors.New("memcache: cache miss")
func New(addr string) *Client {
const maxConns = 20
pool := redis.NewPool(func() (redis.Conn, error) {
return redis.Dial("tcp", addr)
}, maxConns)
return &Client{
pool: pool,
}
}
type Client struct {
pool *redis.Pool
}
type CodecClient struct {
client *Client
codec Codec
}
type Item struct {
Key string
Value []byte
Object interface{} // Used with Codec.
Expiration time.Duration // Read-only.
}
func (c *Client) WithCodec(codec Codec) *CodecClient {
return &CodecClient{
c, codec,
}
}
func (c *Client) Delete(ctx context.Context, key string) error {
conn, err := c.pool.GetContext(ctx)
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Do("DEL", key)
return err
}
func (c *CodecClient) Delete(ctx context.Context, key string) error {
return c.client.Delete(ctx, key)
}
func (c *Client) Set(ctx context.Context, item *Item) error {
if item.Value == nil {
return errors.New("nil item value")
}
return c.set(ctx, item.Key, item.Value, item.Expiration)
}
func (c *CodecClient) Set(ctx context.Context, item *Item) error {
if item.Object == nil {
return errors.New("nil object value")
}
b, err := c.codec.Marshal(item.Object)
if err != nil {
return err
}
return c.client.set(ctx, item.Key, b, item.Expiration)
}
func (c *Client) set(ctx context.Context, key string, value []byte, expiration time.Duration) error {
conn, err := c.pool.GetContext(ctx)
if err != nil {
return err
}
defer conn.Close()
if expiration == 0 {
_, err := conn.Do("SET", key, value)
return err
}
// NOTE(cbro): redis does not support expiry in units more granular than a second.
exp := int64(expiration.Seconds())
if exp == 0 {
// Redis doesn't allow a zero expiration, delete the key instead.
_, err := conn.Do("DEL", key)
return err
}
_, err = conn.Do("SETEX", key, exp, value)
return err
}
// Get gets the item.
func (c *Client) Get(ctx context.Context, key string) ([]byte, error) {
conn, err := c.pool.GetContext(ctx)
if err != nil {
return nil, err
}
defer conn.Close()
b, err := redis.Bytes(conn.Do("GET", key))
if err == redis.ErrNil {
err = ErrCacheMiss
}
return b, err
}
func (c *CodecClient) Get(ctx context.Context, key string, v interface{}) error {
b, err := c.client.Get(ctx, key)
if err != nil {
return err
}
return c.codec.Unmarshal(b, v)
}
var (
Gob = Codec{gobMarshal, gobUnmarshal}
JSON = Codec{json.Marshal, json.Unmarshal}
)
type Codec struct {
Marshal func(interface{}) ([]byte, error)
Unmarshal func([]byte, interface{}) error
}
func gobMarshal(v interface{}) ([]byte, error) {
var buf bytes.Buffer
if err := gob.NewEncoder(&buf).Encode(v); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func gobUnmarshal(data []byte, v interface{}) error {
return gob.NewDecoder(bytes.NewBuffer(data)).Decode(v)
}
|