diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7bd198d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.11 + +COPY ./src/server/server /go/src/server +COPY ./src/client/client /go/src/client +RUN go get -u golang.org/x/lint/golint +RUN go get golang.org/x/tools/cmd/goimports +RUN go get github.com/golang/go/src/cmd/vet + +WORKDIR /go/src/ +RUN go build /go/src/server.go +ENTRYPOINT ["/go/src/server"] +EXPOSE 9090 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5036367 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +SERVER_PATH = ./src/server +SERVER_BIN = server + +CLIENT_PATH = ./src/client +CLIENT_BIN = client + +SEARCH_GOFILES := $(shell find -type f -name "*.go") + +build: clean client server docker + +.PHONY: test + test: + go test -coverprofile=coverage.out $(SERVER_PATH) + +.PHONY: check + check: + goimports -e -l $(SEARCH_GOFILES) + golint -set_exit_status $(SEARCH_GOFILES) + go vet $(SERVER_PATH) + go vet $(CLIENT_PATH) + +.PHONY: run + run: build + sudo docker run -d gohomework + +clean: + rm -rf $(SERVER_PATH)/$(SERVER_BIN) + rm -rf $(CLIENT_PATH)/$(CLIENT_BIN) + +client: + go build -o $(CLIENT_PATH)/$(CLIENT_BIN) $(CLIENT_PATH)/client.go + +server: + go build -o $(SERVER_PATH)/$(SERVER_BIN) $(SERVER_PATH)/server.go + +.PHONY: docker + docker: server client + sudo docker build -t "gohomework" . diff --git a/README.md b/README.md new file mode 100644 index 0000000..32ec892 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Redis like database +Small Redis like database in Go. +Supported commands: SET, GET, DEL, KEYS +# Installation Instructions +Type `make` to build everything necessary +Use `src/server/server` for server and `src/client/client` for client. +Server must be run first. +# Running Redis like database in Docker +Run `docker build -t "gohomework" .` to build docker image. +Run `docker run gohomework` to run the server. + diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..9f8e9b6 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/src/client/client.go b/src/client/client.go new file mode 100644 index 0000000..a1a7bfe --- /dev/null +++ b/src/client/client.go @@ -0,0 +1,40 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "net" + "os" +) + +const DEFAULT_PORT = "9090" +const DEFAULT_HOST = "127.0.0.1" + +func main() { + var connectPort string + var connectHost string + flag.StringVar(&connectPort, "port", DEFAULT_PORT, "Port to listen on.") + flag.StringVar(&connectPort, "p", DEFAULT_PORT, "Port to listen on.") + flag.StringVar(&connectHost, "host", DEFAULT_HOST, "Host to listen on.") + flag.StringVar(&connectHost, "h", DEFAULT_HOST, "Host to listen on.") + flag.Parse() + fmt.Print("Sending commands on port:" + connectPort + "\n") + fmt.Print("To host:" + connectHost + "\n") + + conn, conn_err := net.Dial("tcp", connectHost+":"+connectPort) + if conn_err != nil { + fmt.Println(conn_err) + } + for { + reader := bufio.NewReader(os.Stdin) + fmt.Print("> ") + text, err := reader.ReadString('\n') + if err != nil { + fmt.Println(err) + } + conn.Write([]byte(text)) + response, _ := bufio.NewReader(conn).ReadString('\n') + fmt.Print("Response: " + response) + } +} diff --git a/src/server/server.go b/src/server/server.go new file mode 100644 index 0000000..43b105d --- /dev/null +++ b/src/server/server.go @@ -0,0 +1,89 @@ +// server_main.go +package main + +import ( + "bufio" + "flag" + "fmt" + "net" + "strings" +) + +const DEFAULT_PORT = "9090" +const DEFAULT_HOST = "127.0.0.1" + +var database = make(map[string]string) + +func main() { + var listenPort string + var listenHost string + flag.StringVar(&listenPort, "port", DEFAULT_PORT, "Port to listen on.") + flag.StringVar(&listenPort, "p", DEFAULT_PORT, "Port to listen on.") + flag.StringVar(&listenHost, "host", DEFAULT_HOST, "Host to listen on.") + flag.StringVar(&listenHost, "h", DEFAULT_HOST, "Host to listen on.") + flag.Parse() + fmt.Print("Listening on port:" + listenPort + "\n") + fmt.Print("Listening on host:" + listenHost + "\n") + + //fmt.Println(listenHost) + //fmt.Println(listenPort) + listener, err := net.Listen("tcp", listenHost+":"+listenPort) + if err != nil { + fmt.Println(err) + } + for { + conn, err := listener.Accept() + if err != nil { + fmt.Println(err) + } + go handleRequest(conn) + } + +} + +func handleRequest(conn net.Conn) { + defer conn.Close() + connReader := bufio.NewReader(conn) + for { + request, err := connReader.ReadString('\n') + if err != nil { + fmt.Println(err) + return + } + + command := strings.Split(request[:len(request)-1], " ") + fmt.Println(command[0]) + switch command[0] { + case "SET": + if len(command) == 3 { + database[command[1]] = command[2] + conn.Write([]byte("SET successful" + "\n")) + } else { + conn.Write([]byte("Error in command syntax. Syntax: set [key] [value]" + "\n")) + } + case "GET": + if len(command) == 2 { + conn.Write([]byte(database[command[1]] + "\n")) + } else { + conn.Write([]byte("Error in command syntax. Syntax: get [key]" + "\n")) + } + case "DEL": + if len(command) == 2 { + delete(database, command[1]) + conn.Write([]byte("DEL successful" + "\n")) + } else { + conn.Write([]byte("Error in command syntax. Syntax: del [key]" + "\n")) + } + case "KEYS": + all_key := []string{} + for key, _ := range database { + all_key = append(all_key, key) + } + conn.Write([]byte(strings.Join(all_key, " ") + "\n")) + + default: + conn.Write([]byte("Unsupported command: " + "\n")) + } + } + +} diff --git a/src/server/server_test.go b/src/server/server_test.go new file mode 100644 index 0000000..3b027ca --- /dev/null +++ b/src/server/server_test.go @@ -0,0 +1,60 @@ +package main + +import ( + "bufio" + "net" + "testing" + "time" +) + +// var TEST_PAIRS = map[string]string{ +// "UNSUPPORTED" + "\n": "Unsupported command: " + "\n", +// "SET" + "\n": "Error in command syntax. Syntax: set [key] [value]" + "\n", +// "GET" + "\n": "Error in command syntax. Syntax: get [key]" + "\n", +// "DEL" + "\n": "Error in command syntax. Syntax: del [key]" + "\n", +// "SET key1 val1" + "\n": "SET successful" + "\n", +// "SET key2 val2" + "\n": "SET successful" + "\n", +// "GET key2" + "\n": "val2" + "\n", +// "DEL key2" + "\n": "DEL successful" + "\n", +// "KEYS" + "\n": "key1 val1" + "\n", +// } + +var test_pairs = []struct { + test_case string + test_result string +}{ + + {"UNSUPPORTED" + "\n", "Unsupported command: " + "\n"}, + {"SET" + "\n", "Error in command syntax. Syntax: set [key] [value]" + "\n"}, + {"GET" + "\n", "Error in command syntax. Syntax: get [key]" + "\n"}, + {"DEL" + "\n", "Error in command syntax. Syntax: del [key]" + "\n"}, + {"SET key1 val1" + "\n", "SET successful" + "\n"}, + {"SET key2 val2" + "\n", "SET successful" + "\n"}, + {"GET key2" + "\n", "val2" + "\n"}, + {"DEL key2" + "\n", "DEL successful" + "\n"}, + {"KEYS" + "\n", "key1" + "\n"}, +} + +func TestServer(t *testing.T) { + go main() + time.Sleep(100 * time.Millisecond) + + conn, conn_err := net.Dial("tcp", "127.0.0.1:9090") + if conn_err != nil { + t.Error(conn_err) + } + for _, test_pair := range test_pairs { + test_case := test_pair.test_case + test_result := test_pair.test_result + + conn.Write([]byte(test_case)) + + response, _ := bufio.NewReader(conn).ReadString('\n') + //fmt.Print("Response: " + response) + //fmt.Print("Result: " + test_result) + if response != test_result { + t.Errorf("Test failed, expected: '%s', got: '%s'", test_result, response) + } + } + +}