1 Star 1 Fork 0

benjamin.zhang/miniredis

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
miniredis.go 8.75 KB
一键复制 编辑 原始数据 按行查看 历史
Harmen 提交于 2018-05-22 16:45 . connection made in lua needs auth
// Package miniredis is a pure Go Redis test server, for use in Go unittests.
// There are no dependencies on system binaries, and every server you start
// will be empty.
//
// Start a server with `s, err := miniredis.Run()`.
// Stop it with `defer s.Close()`.
//
// Point your Redis client to `s.Addr()` or `s.Host(), s.Port()`.
//
// Set keys directly via s.Set(...) and similar commands, or use a Redis client.
//
// For direct use you can select a Redis database with either `s.Select(12);
// s.Get("foo")` or `s.DB(12).Get("foo")`.
//
package miniredis
import (
"fmt"
"net"
"strconv"
"sync"
"time"
redigo "github.com/gomodule/redigo/redis"
"github.com/alicebob/miniredis/server"
)
type hashKey map[string]string
type listKey []string
type setKey map[string]struct{}
// RedisDB holds a single (numbered) Redis database.
type RedisDB struct {
master *sync.Mutex // pointer to the lock in Miniredis
id int // db id
keys map[string]string // Master map of keys with their type
stringKeys map[string]string // GET/SET &c. keys
hashKeys map[string]hashKey // MGET/MSET &c. keys
listKeys map[string]listKey // LPUSH &c. keys
setKeys map[string]setKey // SADD &c. keys
sortedsetKeys map[string]sortedSet // ZADD &c. keys
ttl map[string]time.Duration // effective TTL values
keyVersion map[string]uint // used to watch values
}
// Miniredis is a Redis server implementation.
type Miniredis struct {
sync.Mutex
srv *server.Server
port int
password string
dbs map[int]*RedisDB
selectedDB int // DB id used in the direct Get(), Set() &c.
scripts map[string]string // sha1 -> lua src
signal *sync.Cond
now time.Time // used to make a duration from EXPIREAT. time.Now() if not set.
}
type txCmd func(*server.Peer, *connCtx)
// database id + key combo
type dbKey struct {
db int
key string
}
// connCtx has all state for a single connection.
type connCtx struct {
selectedDB int // selected DB
authenticated bool // auth enabled and a valid AUTH seen
transaction []txCmd // transaction callbacks. Or nil.
dirtyTransaction bool // any error during QUEUEing.
watch map[dbKey]uint // WATCHed keys.
}
// NewMiniRedis makes a new, non-started, Miniredis object.
func NewMiniRedis() *Miniredis {
m := Miniredis{
dbs: map[int]*RedisDB{},
scripts: map[string]string{},
}
m.signal = sync.NewCond(&m)
return &m
}
func newRedisDB(id int, l *sync.Mutex) RedisDB {
return RedisDB{
id: id,
master: l,
keys: map[string]string{},
stringKeys: map[string]string{},
hashKeys: map[string]hashKey{},
listKeys: map[string]listKey{},
setKeys: map[string]setKey{},
sortedsetKeys: map[string]sortedSet{},
ttl: map[string]time.Duration{},
keyVersion: map[string]uint{},
}
}
// Run creates and Start()s a Miniredis.
func Run() (*Miniredis, error) {
m := NewMiniRedis()
return m, m.Start()
}
// Start starts a server. It listens on a random port on localhost. See also
// Addr().
func (m *Miniredis) Start() error {
s, err := server.NewServer(fmt.Sprintf("127.0.0.1:%d", m.port))
if err != nil {
return err
}
return m.start(s)
}
// StartAddr runs miniredis with a given addr. Examples: "127.0.0.1:6379",
// ":6379", or "127.0.0.1:0"
func (m *Miniredis) StartAddr(addr string) error {
s, err := server.NewServer(addr)
if err != nil {
return err
}
return m.start(s)
}
func (m *Miniredis) start(s *server.Server) error {
m.Lock()
defer m.Unlock()
m.srv = s
m.port = s.Addr().Port
commandsConnection(m)
commandsGeneric(m)
commandsServer(m)
commandsString(m)
commandsHash(m)
commandsList(m)
commandsSet(m)
commandsSortedSet(m)
commandsTransaction(m)
commandsScripting(m)
return nil
}
// Restart restarts a Close()d server on the same port. Values will be
// preserved.
func (m *Miniredis) Restart() error {
return m.Start()
}
// Close shuts down a Miniredis.
func (m *Miniredis) Close() {
m.Lock()
defer m.Unlock()
if m.srv == nil {
return
}
m.srv.Close()
m.srv = nil
}
// RequireAuth makes every connection need to AUTH first. Disable again by
// setting an empty string.
func (m *Miniredis) RequireAuth(pw string) {
m.Lock()
defer m.Unlock()
m.password = pw
}
// DB returns a DB by ID.
func (m *Miniredis) DB(i int) *RedisDB {
m.Lock()
defer m.Unlock()
return m.db(i)
}
// get DB. No locks!
func (m *Miniredis) db(i int) *RedisDB {
if db, ok := m.dbs[i]; ok {
return db
}
db := newRedisDB(i, &m.Mutex) // the DB has our lock.
m.dbs[i] = &db
return &db
}
// Addr returns '127.0.0.1:12345'. Can be given to a Dial(). See also Host()
// and Port(), which return the same things.
func (m *Miniredis) Addr() string {
m.Lock()
defer m.Unlock()
return m.srv.Addr().String()
}
// Host returns the host part of Addr().
func (m *Miniredis) Host() string {
m.Lock()
defer m.Unlock()
return m.srv.Addr().IP.String()
}
// Port returns the (random) port part of Addr().
func (m *Miniredis) Port() string {
m.Lock()
defer m.Unlock()
return strconv.Itoa(m.srv.Addr().Port)
}
// CommandCount returns the number of processed commands.
func (m *Miniredis) CommandCount() int {
m.Lock()
defer m.Unlock()
return int(m.srv.TotalCommands())
}
// CurrentConnectionCount returns the number of currently connected clients.
func (m *Miniredis) CurrentConnectionCount() int {
m.Lock()
defer m.Unlock()
return m.srv.ClientsLen()
}
// TotalConnectionCount returns the number of client connections since server start.
func (m *Miniredis) TotalConnectionCount() int {
m.Lock()
defer m.Unlock()
return int(m.srv.TotalConnections())
}
// FastForward decreases all TTLs by the given duration. All TTLs <= 0 will be
// expired.
func (m *Miniredis) FastForward(duration time.Duration) {
m.Lock()
defer m.Unlock()
for _, db := range m.dbs {
db.fastForward(duration)
}
}
// redigo returns a redigo.Conn, connected using net.Pipe
func (m *Miniredis) redigo() redigo.Conn {
c1, c2 := net.Pipe()
m.srv.ServeConn(c1)
c := redigo.NewConn(c2, 0, 0)
if m.password != "" {
if _, err := c.Do("AUTH", m.password); err != nil {
// ?
}
}
return c
}
// Dump returns a text version of the selected DB, usable for debugging.
func (m *Miniredis) Dump() string {
m.Lock()
defer m.Unlock()
var (
maxLen = 60
indent = " "
db = m.db(m.selectedDB)
r = ""
v = func(s string) string {
suffix := ""
if len(s) > maxLen {
suffix = fmt.Sprintf("...(%d)", len(s))
s = s[:maxLen-len(suffix)]
}
return fmt.Sprintf("%q%s", s, suffix)
}
)
for _, k := range db.allKeys() {
r += fmt.Sprintf("- %s\n", k)
t := db.t(k)
switch t {
case "string":
r += fmt.Sprintf("%s%s\n", indent, v(db.stringKeys[k]))
case "hash":
for _, hk := range db.hashFields(k) {
r += fmt.Sprintf("%s%s: %s\n", indent, hk, v(db.hashGet(k, hk)))
}
case "list":
for _, lk := range db.listKeys[k] {
r += fmt.Sprintf("%s%s\n", indent, v(lk))
}
case "set":
for _, mk := range db.setMembers(k) {
r += fmt.Sprintf("%s%s\n", indent, v(mk))
}
case "zset":
for _, el := range db.ssetElements(k) {
r += fmt.Sprintf("%s%f: %s\n", indent, el.score, v(el.member))
}
default:
r += fmt.Sprintf("%s(a %s, fixme!)\n", indent, t)
}
}
return r
}
// SetTime sets the time against which EXPIREAT values are compared. EXPIREAT
// will use time.Now() if this is not set.
func (m *Miniredis) SetTime(t time.Time) {
m.Lock()
defer m.Unlock()
m.now = t
}
// handleAuth returns false if connection has no access. It sends the reply.
func (m *Miniredis) handleAuth(c *server.Peer) bool {
m.Lock()
defer m.Unlock()
if m.password == "" {
return true
}
if !getCtx(c).authenticated {
c.WriteError("NOAUTH Authentication required.")
return false
}
return true
}
func getCtx(c *server.Peer) *connCtx {
if c.Ctx == nil {
c.Ctx = &connCtx{}
}
return c.Ctx.(*connCtx)
}
func startTx(ctx *connCtx) {
ctx.transaction = []txCmd{}
ctx.dirtyTransaction = false
}
func stopTx(ctx *connCtx) {
ctx.transaction = nil
unwatch(ctx)
}
func inTx(ctx *connCtx) bool {
return ctx.transaction != nil
}
func addTxCmd(ctx *connCtx, cb txCmd) {
ctx.transaction = append(ctx.transaction, cb)
}
func watch(db *RedisDB, ctx *connCtx, key string) {
if ctx.watch == nil {
ctx.watch = map[dbKey]uint{}
}
ctx.watch[dbKey{db: db.id, key: key}] = db.keyVersion[key] // Can be 0.
}
func unwatch(ctx *connCtx) {
ctx.watch = nil
}
// setDirty can be called even when not in an tx. Is an no-op then.
func setDirty(c *server.Peer) {
if c.Ctx == nil {
// No transaction. Not relevant.
return
}
getCtx(c).dirtyTransaction = true
}
func setAuthenticated(c *server.Peer) {
getCtx(c).authenticated = true
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/threeDaoLiu/miniredis.git
git@gitee.com:threeDaoLiu/miniredis.git
threeDaoLiu
miniredis
miniredis
master

搜索帮助