Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions project_1/DockerFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM golang

WORKDIR /usr/local

COPY ./client bin/client

COPY ./server bin/server

ENTRYPOINT ["bin/server"]
20 changes: 20 additions & 0 deletions project_1/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
build:
cd client_source; go build client.go; cd ..;
cd server_source; go build server.go; cd ..;
mv -f client_source/client .
mv -f server_source/server .
sudo docker build -f DockerFile -t custom_redis .

test:
cd client_source; go test -cover -coverprofile=coverage_client.out; cd ..;
cd server_source; go test -cover -coverprofile=coverage_server.out; cd ..;
mv -f client_source/coverage_client.out .
mv -f server_source/coverage_server.out .

check:
cd server_source; go vet; golint; goimports .; cd ..;
cd client_source; go vet; golint; goimports .; cd ..;
cd commonVariables; go vet; golint; goimports .; cd ..;

run:
sudo docker run --rm --name my_redis -it custom_redis
26 changes: 26 additions & 0 deletions project_1/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
This is a simplified pure-Go redis.
It requires Docker, golang image and Go to install and launch.

1. How to build it:
Move this folder to your Go workspace, then open the terminal and write command "make build". This will make a docker image with tag "custom_redis".

2. How to run it:
After building this project, open the terminal and write command "make run". This will will create a docker container with tag "my_redis". It's entrypoint is set to be server part. To launch client part, open another terminal and write command "docker exec -it my_redis client [ARGUMENTS]".

3. How to check it:
Open the terminal, go to the project's directory, type "make check".

4. How to test it:
Open the terminal, go to the project's directory, type "make test". Coverage of server source code and client source code will be located in the project's directory under names "coverage_server.out" and "coverage_client.out" accordingly.

5. If you want to change server launch arguments, open the Dockerfile and change ENTRYPOINT.

6. Available commands are:
- SET KEY VALUE - sets value to a key. If this key already exists, it will update the value. In case of success the answer will be either "OK.", or "Replaced existing value - PREVIOUS_VALUE" if key already exists.
- GET KEY - returns value, assigned to this key, or "(nil)"(notice, that it's a string, not the real nil value, cause nil can't be converted to string, so please be careful, when typing something similar as value), if key doesn't exist.
- DEL KEY [KEY[...]] - deletes keys from database and returns, what keys were deleted and what were ignored, 'cause they don't exist.

6.1. There is an additional option of inputting arguments to commands implemented. You can use quotes to input keys and values, containing spaces(quotes will be dropped and the string inside quotes is considered). But, because of this, using quotes in keys and values themselves is forbidden. Also, "_" and " " are considered the same symbol.
Example: "value 1", value_1, "value_1" are considered the same.

P.S. Honestly, i had some troubles with importing in Go, because it supports only absolute path importing through usage of GOPATH and GOROOT variables, so imports in my project may look a little strange.
Binary file added project_1/client
Binary file not shown.
163 changes: 163 additions & 0 deletions project_1/client_source/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package main

import (
"bufio"
"fmt"
"log"
"net"
"os"
cv "project_1/commonVariables"
"strings"
)

func main() {

defer func() {
if err := recover(); err != nil {
log.Println("ERROR: ", err)
}
}()

ClientInitialization(ClientArgumentParsing(os.Args))
}

//ClientArgumentParsing - checks, if arguments passed with client are valid
func ClientArgumentParsing(args []string) (string, string) {
var address = ":9090"
var host = "127.0.0.1"
var flagPortOrHost = cv.Nothing
argsLen := len(args)
if argsLen == 2 {
panic(cv.UsageClientErrorMessage)
}
if argsLen >= 3 {
if args[1] == "-h" || args[1] == "--host" {
host = args[2]
flagPortOrHost = cv.Host
} else if args[1] == "-p" || args[1] == "--port" {
address = args[2]
flagPortOrHost = cv.Port
} else {
panic(cv.UsageClientErrorMessage)
}
}
if argsLen == 4 {
panic(cv.UsageClientErrorMessage)
}
if argsLen == 5 {
if flagPortOrHost == cv.Host {
if args[3] == "-p" || args[3] == "--port" {
address = args[4]
} else {
panic(cv.UsageClientErrorMessage)
}
} else if flagPortOrHost == cv.Port {
if args[3] == "-h" || args[3] == "--host" {
host = args[4]
}
} else {
panic(cv.UsageClientErrorMessage)
}
}
if argsLen > 5 {
panic(cv.UsageClientErrorMessage)
}
return address, host
}

//ClientInitialization - starts the client and exchanges messages with server
func ClientInitialization(address string, host string) {
serverConnection, err1 := net.Dial("tcp", host + address)
if err1 != nil {
fmt.Println(err1)
}
for {
reader := bufio.NewReader(os.Stdin)
message, _ := reader.ReadString('\n')
commandIsWrong, messageCheckResult := CheckMessage(message)
if commandIsWrong {
fmt.Println(messageCheckResult)
} else {
serverConnection.Write([]byte(messageCheckResult))
scanner := bufio.NewReader(serverConnection)
answer, _ := scanner.ReadString('\n')
fmt.Print(answer)
}
}
}

//CheckMessage - checks, if the command, that we are trying to use, is correct
func CheckMessage(message string) (bool, string) {
var commandType cv.CommandFlag
commandIsWrong := false
switch strings.Split(message, " ")[0] {
case "GET":
commandType = cv.GET
case "SET":
commandType = cv.SET
case "DEL":
commandType = cv.DEL
default:
commandIsWrong = true
}
if commandIsWrong {
return commandIsWrong, cv.UsageCommandsErrorMessage
}
messageLength := len(message)
insideValue := false
quotesCounter := 0
for i := 4; i < messageLength; i++ {
if message[i] == ' ' {
if insideValue {
part1 := message[:i]
part2 := message[i+1:]
array := []string{part1, part2}
message = strings.Join(array, "_")
}
}
if message[i] == '"' {
if insideValue {
insideValue = false
if i + 1 < messageLength {
if message[i+1] != ' ' && message[i+1] != '\n' && message[i+1] != '\t' {
return true, cv.ArgumentsErrorMessage
}
}
} else {
insideValue = true
if i - 1 >= 4 {
if message[i-1] != ' ' && message[i-1] != '\t' {
return true, cv.ArgumentsErrorMessage
}
}
}
quotesCounter++
part1 := message[:i]
part2 := message[i+1:]
array := []string{part1, part2}
message = strings.Join(array, "")
messageLength--
i--
}
}
command := strings.Fields(message)
switch commandType {
case cv.GET:
if (quotesCounter != 0 && quotesCounter != 2) || len(command) != 2 {
commandIsWrong = true
}
case cv.SET:
if (quotesCounter != 0 && quotesCounter != 2 && quotesCounter != 4) ||
len(command) != 3 {
commandIsWrong = true
}
case cv.DEL:
if quotesCounter % 2 != 0 {
commandIsWrong = true
}
}
if commandIsWrong {
return commandIsWrong, cv.ArgumentsErrorMessage
}
return commandIsWrong, message
}
113 changes: 113 additions & 0 deletions project_1/client_source/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package main

import (
"fmt"
"strings"
"testing"
cv "project_1/commonVariables"
)

var argumentTests = []struct {
arguments string
expectedAddress string
expectedHost string
} {
{"client -h 131.0.0.1", ":9090", "131.0.0.1"},
{"client -p :9071", ":9071", "127.0.0.1"},
{"client -p :9071 -h 131.0.0.1", ":9071", "131.0.0.1"},
{"client -h 131.0.0.1 -p :9071", ":9071", "131.0.0.1"},
{"client", ":9090", "127.0.0.1"},
}

var commandsTests = []struct {
arguments string
expectedFlag bool
expectedMessage string
} {
{"SET key1 value1", false, "SET key1 value1"},
{"GET key1", false, "GET key1"},
{"DEL key1", false, "DEL key1"},
{"SET \"abc dg\" \"petro\"", false, "SET abc_dg petro"},
{"GET \"abc dg\"", false, "GET abc_dg"},
{"DEL \"abc dg\"", false, "DEL abc_dg"},
{"SeT key1 value1", true, cv.UsageCommandsErrorMessage},
{"SET \"abc dg \"petro\"", true, cv.ArgumentsErrorMessage},
{"GET \"abc dg", true, cv.ArgumentsErrorMessage},
{"DEL abc dg\"", true, cv.ArgumentsErrorMessage},
{"DEL \"\"abc dg", true, cv.ArgumentsErrorMessage},

}

func TestClientArgumentParsing(t *testing.T) {
for _, test := range argumentTests {
resA, resH := ClientArgumentParsing(strings.Fields(test.arguments))
if resA != test.expectedAddress || resH != test.expectedHost {
t.Errorf("Expected address to be %s. Expected host to be %s. Got %s and %s.",
test.expectedAddress, test.expectedHost, resA, resH)
}
}
t.Run("3 argument string.", TestCAP6)
t.Run("1 argument string.", TestCAP7)
t.Run("5 argument string.", TestCAP8)
t.Run("Random string.", TestCAP9)
}

func TestCAP6(t *testing.T) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered.", r)
}
}()
ClientArgumentParsing(strings.Fields("client -h 131.0.0.1 -p"))
t.Error("Allowed 3 arguments to be passed.")
}

func TestCAP7(t *testing.T) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered.", r)
}
}()
ClientArgumentParsing(strings.Fields("client -h"))
t.Error("Allowed 1 argument to be passed.")
}

func TestCAP8(t *testing.T) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered.", r)
}
}()
ClientArgumentParsing(strings.Fields("client -h 131.0.0.1 -p :9071 dljfkad"))
t.Error("Allowed 5 arguments to be passed.")
}

func TestCAP9(t *testing.T) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered.", r)
}
}()
ClientArgumentParsing(strings.Fields("client -asdf 1ags31.0.0.1 -sadg :9sgad071"))
t.Error("Passed some random string and it worked.")
}

func TestCheckMessage(t *testing.T) {
for _, test := range commandsTests {
resF, resM := CheckMessage(test.arguments)
if resF == test.expectedFlag {
if !resF {
if resM != test.expectedMessage {
t.Errorf("Expected corrected message to be %s. Got %s.",
test.expectedMessage, resM)
}
}
} else {
if !resF {
t.Errorf("Expected command to be wrong. Message - %s.", resM)
} else {
t.Errorf("Expected command to be correct. Message - %s.", resM)
}
}
}
}
57 changes: 57 additions & 0 deletions project_1/commonVariables/CommonStaff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package commonVariables

//KeyState - type for keys states
type KeyState int32
const (
//Absent shows, that this key is absent
Absent KeyState = 0
//Present shows, that this key is present
Present KeyState = 1
//Ignored shows, that we don't care about state
Ignored KeyState = 2
//ERROR shows, that an error occurred
ERROR KeyState = 3
)

//ArgumentFlag - flag for argument parsing, so we know, what argument we've already encountered
type ArgumentFlag int
const (
//Nothing means no argument encountered yet
Nothing ArgumentFlag = 0
//Mode means mode argument encountered
Mode ArgumentFlag = 1
//Port means port argument encountered
Port ArgumentFlag = 2
//Host means host argument encountered
Host ArgumentFlag = 3
)

//CommandFlag - flag just for convenience to remember what command we are going to process
type CommandFlag int
const (
//GET - it's a GET command
GET CommandFlag = 0
//SET - it's a SET command
SET CommandFlag = 1
//DEL - it's a DEL command
DEL CommandFlag = 2
)

//Answer - struct for holding database answer and key state
type Answer struct {
Answer string
State KeyState
}

//UsageServerErrorMessage - error message for passing invalid arguments for server
var UsageServerErrorMessage = "USAGE: server [-m/--mode MODE(disk)] [-p/--port PORT]."
//UsageClientErrorMessage - error message for passing invalid arguments for client
var UsageClientErrorMessage = "USAGE: client [-h/--host HOST] [-p/--port PORT]."
//UsageCommandsErrorMessage - error message for passing invalid command
var UsageCommandsErrorMessage = "Available commands:\n\tGET KEY\n\tSET KEY VALUE\n\tDEL KEY [KEY[...]]"
//GetErrorMessage - error message for GET
var GetErrorMessage = "USAGE: GET KEY"
//SetErrorMessage - error message for SET
var SetErrorMessage = "USAGE: SET KEY VALUE"
//ArgumentsErrorMessage - error message for passing invalid arguments as keys or values
var ArgumentsErrorMessage = "There is something wrong with arguments."
Binary file added project_1/server
Binary file not shown.
Loading