TCP redis like

This commit is contained in:
Meutel 2017-07-09 16:50:55 +02:00
parent 984616b527
commit 47bd07057a
3 changed files with 205 additions and 0 deletions

1
.gitignore vendored
View File

@ -47,3 +47,4 @@ converter-main/converter-main
tcpclient/tcpclient tcpclient/tcpclient
tcpserver/tcpserver tcpserver/tcpserver
echoserver/echoserver echoserver/echoserver
example-redis/example-redis

116
example-redis/main.go Normal file
View File

@ -0,0 +1,116 @@
package main
import (
"bufio"
"errors"
"fmt"
"io"
"log"
"net"
"strings"
)
func getCommand(reader io.Reader) (string, error) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
str := scanner.Text()
return str, nil
}
return "", scanner.Err()
}
func isCommand(str string) bool {
switch str {
case "GET":
fallthrough
case "SET":
fallthrough
case "DEL":
return true
default:
return false
}
}
func parseCmd(line string) (string, string, string, error) {
cmd, key, val := "", "", ""
var err error
parts := strings.Split(line, " ")
if len(parts) != 2 && len(parts) != 3 {
err = errors.New("Invalid number of arguments")
} else {
for i, part := range parts {
switch i {
case 0:
if isCommand(part) {
cmd = part
} else {
err = errors.New("Unknown command")
break
}
case 1:
key = part
case 2:
if cmd == "SET" {
val = part
} else {
err = errors.New("Invalid number of arguments")
}
}
}
}
return cmd, key, val, err
}
func respond(writer io.Writer, ret string) {
_, err := io.WriteString(writer, ret+"\n")
if err != nil {
log.Println(err)
}
}
func main() {
db := make(map[string]string)
ln, err := net.Listen("tcp", ":9000")
if err != nil {
panic(err)
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
panic(err)
}
go func() {
line, err := getCommand(conn)
if err != nil {
respond(conn, fmt.Sprint(err))
}
cmd, key, val, err := parseCmd(line)
if err != nil {
respond(conn, fmt.Sprint(err))
}
switch cmd {
case "DEL":
log.Println("Deleting", key)
delete(db, key)
case "GET":
log.Println("Getting", key)
ret, ok := db[key]
if ok {
log.Println("Value found", ret)
respond(conn, ret)
} else {
log.Println("Unknown key", key)
}
case "SET":
log.Println("Setting", key)
db[key] = val
}
defer conn.Close()
}()
}
}

View File

@ -0,0 +1,88 @@
package main
import (
"bytes"
"strings"
"testing"
)
func TestGetCommand(t *testing.T) {
r := strings.NewReader("test\n")
line, err := getCommand(r)
if err != nil {
t.Log("Should not err")
t.Fail()
}
if line != "test" {
t.Log("Invalid command")
t.Fail()
}
}
func TestIsCommand(t *testing.T) {
tests := map[string]bool{
"GET": true,
"SET": true,
"DEL": true,
"XXX": false,
}
for k, v := range tests {
if isCommand(k) != v {
t.Log("Error " + k)
t.Fail()
}
}
}
func TestRespond(t *testing.T) {
var bs []byte
buf := bytes.NewBuffer(bs)
respond(buf, "test")
if buf.String() != "test\n" {
t.Log("Invalid response (" + buf.String() + ")")
t.Fail()
}
}
type ParseCmdTest struct {
line string
fails bool
cmd string
key string
val string
}
func TestParseCommand(t *testing.T) {
tests := []ParseCmdTest{
{"test", true, "", "", ""},
{"test test", true, "", "", ""},
{"SET 1 2 3", true, "", "", ""},
{"GET 1 2", true, "", "", ""},
}
for _, test := range tests {
c, k, v, e := parseCmd(test.line)
if test.fails {
if e == nil {
t.Log("should fail (" + test.line + ")")
t.Fail()
}
} else {
if e != nil {
t.Log("should no fail", e)
t.Fail()
}
if c != test.cmd {
t.Log("Error command", c, test.cmd)
t.Fail()
}
if k != test.key {
t.Log("Error key", k, test.key)
t.Fail()
}
if v != test.val {
t.Log("Error value", v, test.val)
t.Fail()
}
}
}
}