diff --git a/.gitignore b/.gitignore index 977e020..780174a 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ converter-main/converter-main tcpclient/tcpclient tcpserver/tcpserver echoserver/echoserver +example-redis/example-redis diff --git a/example-redis/main.go b/example-redis/main.go new file mode 100644 index 0000000..56f9aba --- /dev/null +++ b/example-redis/main.go @@ -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() + }() + } +} diff --git a/example-redis/main_test.go b/example-redis/main_test.go new file mode 100644 index 0000000..07175bb --- /dev/null +++ b/example-redis/main_test.go @@ -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() + } + } + } +}