From 1108840967e2f62825d4fc66a78f9d61ddeee9ac Mon Sep 17 00:00:00 2001 From: James Mills <1290234+prologic@users.noreply.github.com> Date: Wed, 4 Sep 2019 22:44:33 +1000 Subject: [PATCH] Refactor the bitcaskd (redis compatible server) sample to improve code quality (#88) --- cmd/bitcaskd/main.go | 97 +++----------------------------- cmd/bitcaskd/server.go | 122 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 89 deletions(-) create mode 100644 cmd/bitcaskd/server.go diff --git a/cmd/bitcaskd/main.go b/cmd/bitcaskd/main.go index ba40096..67b4652 100644 --- a/cmd/bitcaskd/main.go +++ b/cmd/bitcaskd/main.go @@ -3,26 +3,22 @@ package main import ( "fmt" "os" - "strings" log "github.com/sirupsen/logrus" flag "github.com/spf13/pflag" - "github.com/tidwall/redcon" - "github.com/prologic/bitcask" "github.com/prologic/bitcask/internal" ) var ( - bind string - debug bool - version bool - maxDatafileSize int + bind string + debug bool + version bool ) func init() { flag.Usage = func() { - fmt.Fprintf(os.Stderr, "Usage: %s [options] \n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Usage: %s [options] \n", os.Args[0]) flag.PrintDefaults() } @@ -30,8 +26,6 @@ func init() { flag.BoolVarP(&debug, "debug", "d", false, "enable debug logging") flag.StringVarP(&bind, "bind", "b", ":6379", "interface and port to bind to") - - flag.IntVar(&maxDatafileSize, "max-datafile-size", 1<<20, "maximum datafile size in bytes") } func main() { @@ -55,86 +49,11 @@ func main() { path := flag.Arg(0) - db, err := bitcask.Open(path, bitcask.WithMaxDatafileSize(maxDatafileSize)) + server, err := newServer(bind, path) if err != nil { - log.WithError(err).WithField("path", path).Error("error opening database") - os.Exit(1) + log.WithError(err).Error("error creating server") + os.Exit(2) } - log.WithField("bind", bind).WithField("path", path).Infof("starting bitcaskd v%s", internal.FullVersion()) - - err = redcon.ListenAndServe(bind, - func(conn redcon.Conn, cmd redcon.Command) { - switch strings.ToLower(string(cmd.Args[0])) { - case "ping": - conn.WriteString("PONG") - case "quit": - conn.WriteString("OK") - conn.Close() - case "set": - if len(cmd.Args) != 3 { - conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") - return - } - key := cmd.Args[1] - value := cmd.Args[2] - err = db.Put(key, value) - if err != nil { - conn.WriteString(fmt.Sprintf("ERR: %s", err)) - } else { - conn.WriteString("OK") - } - case "get": - if len(cmd.Args) != 2 { - conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") - return - } - key := cmd.Args[1] - value, err := db.Get(key) - if err != nil { - conn.WriteNull() - } else { - conn.WriteBulk(value) - } - case "keys": - conn.WriteArray(db.Len()) - for key := range db.Keys() { - conn.WriteBulk([]byte(key)) - } - case "exists": - if len(cmd.Args) != 2 { - conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") - return - } - key := cmd.Args[1] - if db.Has(key) { - conn.WriteInt(1) - } else { - conn.WriteInt(0) - } - case "del": - if len(cmd.Args) != 2 { - conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") - return - } - key := cmd.Args[1] - err := db.Delete(key) - if err != nil { - conn.WriteInt(0) - } else { - conn.WriteInt(1) - } - default: - conn.WriteError("ERR unknown command '" + string(cmd.Args[0]) + "'") - } - }, - func(conn redcon.Conn) bool { - return true - }, - func(conn redcon.Conn, err error) { - }, - ) - if err != nil { - log.WithError(err).Fatal("oops") - } + log.Fatal(server.Run()) } diff --git a/cmd/bitcaskd/server.go b/cmd/bitcaskd/server.go new file mode 100644 index 0000000..5bb74e3 --- /dev/null +++ b/cmd/bitcaskd/server.go @@ -0,0 +1,122 @@ +package main + +import ( + "fmt" + "strings" + + log "github.com/sirupsen/logrus" + "github.com/tidwall/redcon" + + "github.com/prologic/bitcask" +) + +type server struct { + bind string + db *bitcask.Bitcask +} + +func newServer(bind, dbpath string) (*server, error) { + db, err := bitcask.Open(dbpath) + if err != nil { + log.WithError(err).WithField("dbpath", dbpath).Error("error opening database") + return nil, err + } + + return &server{ + bind: bind, + db: db, + }, nil +} + +func (s *server) handleSet(cmd redcon.Command, conn redcon.Conn) { + if len(cmd.Args) != 3 { + conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") + return + } + key := cmd.Args[1] + value := cmd.Args[2] + if err := s.db.Put(key, value); err != nil { + conn.WriteString(fmt.Sprintf("ERR: %s", err)) + } else { + conn.WriteString("OK") + } +} + +func (s *server) handleGet(cmd redcon.Command, conn redcon.Conn) { + if len(cmd.Args) != 2 { + conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") + return + } + key := cmd.Args[1] + value, err := s.db.Get(key) + if err != nil { + conn.WriteNull() + } else { + conn.WriteBulk(value) + } +} + +func (s *server) handleKeys(cmd redcon.Command, conn redcon.Conn) { + conn.WriteArray(s.db.Len()) + for key := range s.db.Keys() { + conn.WriteBulk([]byte(key)) + } +} + +func (s *server) handleExists(cmd redcon.Command, conn redcon.Conn) { + if len(cmd.Args) != 2 { + conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") + return + } + key := cmd.Args[1] + if s.db.Has(key) { + conn.WriteInt(1) + } else { + conn.WriteInt(0) + } +} + +func (s *server) handleDel(cmd redcon.Command, conn redcon.Conn) { + if len(cmd.Args) != 2 { + conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") + return + } + key := cmd.Args[1] + if err := s.db.Delete(key); err != nil { + conn.WriteInt(0) + } else { + conn.WriteInt(1) + } +} + +func (s *server) Run() (err error) { + err = redcon.ListenAndServe(s.bind, + func(conn redcon.Conn, cmd redcon.Command) { + switch strings.ToLower(string(cmd.Args[0])) { + case "ping": + conn.WriteString("PONG") + case "quit": + conn.WriteString("OK") + conn.Close() + case "set": + s.handleSet(cmd, conn) + case "get": + s.handleGet(cmd, conn) + case "keys": + s.handleKeys(cmd, conn) + case "exists": + s.handleExists(cmd, conn) + case "del": + s.handleDel(cmd, conn) + default: + conn.WriteError("ERR unknown command '" + string(cmd.Args[0]) + "'") + } + }, + func(conn redcon.Conn) bool { + return true + }, + func(conn redcon.Conn, err error) { + }, + ) + return +}