Skip to content
Open
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
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM golang:1.11

COPY . /go/src/gohomework

ENTRYPOINT ["/go/src/gohomework/server/server"]

EXPOSE 9090
45 changes: 45 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
SRC_PATH = ./src
SERVER_PATH = ./src/server
SERVER_BIN = server
CLIENT_PATH = ./src/client
CLIENT_BIN = client

DOCKER_BUILDER := golang:1.11
BECOME := sudo -E
VERSION := $(shell cat VERSION)
DOCKER_IMAGE := gohomework:$(VERSION)
RUNNER = docker run --rm -v $(CURDIR):/go/src/gohomework/$(SERVER_PATH)
RUNNER += $(DOCKER_ENVS) -w /go/src/gohomework/$(SERVER_PATH)

BUILDER = $(RUNNER) $(DOCKER_BUILDER)
PORT := 9090

.PHONY: test
test:
go test -coverprofile coverage.out -v ./...

.PHONY: check
check:
goimports -e -l $(SRC_PATH)
golint -set_exit_status $(SRC_PATH)
go vet $(SERVER_PATH)
go vet $(CLIENT_PATH)


.PHONY: gohomework
tinyredis:
docker build -t gohomework .

.PHONY: build
build:
go build -o $(SERVER_BIN) $(SERVER_FILE)
go build -o $(CLIENT_BIN) $(CLIENT_FILE)

.PHONY: clean
clean:
$(BECOME) $(RM) $(SERVER_PATH)/server
$(BECOME) $(RM) $(CLIENT_PATH)/client

.PHONY: cleancontainer
cleancontainer:
docker rm gohomework
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# GoHomework
Implementation of the server-client solution for storing KV data lightweight analog of the Redis (https://redis.io/).
More in file [Task.pdf](https://github.com/Dubouski/GoHomework/blob/working-branch/Task.pdf)

Use makefile for all actions

## Testing:
**make check**
Run "go vet", "goimports", "golint"

**make test**
Run tests and save the output to the "coverage.out" file.


## Work with application

## Server
You may run server using arguments:
- '-p' or '--port'
>listening port, default is 9090;
- '-m' or '--mode'
>storage mode, default is "memory", alternate mode is "disk" (save to file "data.json");
- '-v' or '--verbose'
>verbose mode, full log of the client requests.

## Client
You may run client using arguments:
- '-p' or '--port'
>connect to port, default is 9090;
- '-h' or '--host'
>connect to ip address, default is 127.0.0.1;
- '--dump'
>dump the whole database to the JSON format on STDOUT (example:'[{"key": "YOUR_KEY", "value": "YOUR_VALUE"}]'). Save to file 'data.json';
- '--restore'
>restore the database from the dumped file 'data.json'.

## Commands:
updates one key at a time with the given value:
- set key value
returns tuple of the value and the key state. The state either present or absent:
- get key
removes one key at a time and returns the state of the resource:
- del key
returns all keys matching pattern, for example "h?llo" matches "hello", "hallo" and "hxllo":
- keys [pattern]
exit from app
- exit


Binary file added Task.pdf
Binary file not shown.
228 changes: 228 additions & 0 deletions src/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package client

import (
"bufio"
"fmt"
"io"
"net"
"os"
"strconv"
"strings"
)

const (
publish = "publish"
subscribe = "subscribe"
cmdExit = "EXIT"
dataJSON = "data.json"

defaultPort = "9090"
defaultHost = "127.0.0.1"

fPort = "--port"
port = "-p"
fHost = "--host"
host = "-h"

dump = "--dump"
restore = "--restore"

protocolTCP = "tcp"
)

var (
_mainPort = defaultPort
_mainHost = defaultHost

_dump = false
_restore = false

_subscriber = false //подписчик
_publisher = false //слушатель
)

//для парсинга коммандной строки
func trimLastSymbol(s string) string {
if last := len(s) - 1; last >= 0 && s[last] == ',' {
s = s[:last]
return s
}
return s
}

func parseArguments() {

fmt.Println("Parse arguments")
var cmds = make(map[string]func(string))
// заполняем команды
cmds[port] = func(s string) {
//номер порта
s = trimLastSymbol(s)
fmt.Printf("Parse -p=%s\r\n", s)
if port, err := strconv.Atoi(s); err == nil {
//Valid numbers for ports are: 0 to 2^16-1 = 0 to 65535
//But user ports 1024 to 49151
if port < 49152 && port > 1024 {
_mainPort = s
}
} else {
_mainPort = defaultPort
}
fmt.Printf("New port: %s\r\n", _mainPort)
}
cmds[fPort] = func(s string) {
s = trimLastSymbol(s)
fmt.Printf("Parse --port=%s\r\n", s)
if port, err := strconv.Atoi(s); err == nil {
//Valid numbers for ports are: 0 to 2^16-1 = 0 to 65535
//But user ports 1024 to 49151
if port < 49152 && port > 1024 {
_mainPort = s
}
} else {
_mainPort = defaultPort
}
fmt.Printf("New port: %s\r\n", _mainPort)
}
cmds[host] = func(s string) {
s = trimLastSymbol(s)
//путь к файлу для сохранения
fmt.Printf("Parse --h=%s\r\n", s)
// TODO add validate ip address
fmt.Printf("New IP address: %s\r\n", s)
_mainHost = s // update ip address
}
cmds[fHost] = func(s string) {
s = trimLastSymbol(s)
//путь к файлу с сохранением
fmt.Printf("--host=%s\r\n", s)
// TODO add validate ip address
fmt.Printf("New IP address: %s\r\n", s)
_mainHost = s // update ip address
}
cmds[dump] = func(s string) {
s = trimLastSymbol(s)
//путь к файлу с сохранением
fmt.Println("--dump")
_dump = true
}
cmds[restore] = func(s string) {
s = trimLastSymbol(s)
//путь к файлу с сохранением
fmt.Println("--restore")
_restore = true
}

//анализ команд
for _, arg := range os.Args[1:] {
cmd := strings.Split(arg, "=")

if len(cmd) > 2 {
fmt.Println(arg, "don't know... ")
continue
}

// для красноречия
name := cmd[0]
param := ""
if trimLastSymbol(cmd[0]) == dump || trimLastSymbol(cmd[0]) == restore {
name = trimLastSymbol(cmd[0])
param = ""
} else {
param = cmd[1]
}

// ищем функцию
fn := cmds[name]
if fn == nil {
fmt.Println(name, "don't know... ")
continue
}

// исполняем команду
fn(param)
}
}

func main() {

if len(os.Args) > 0 {
parseArguments()
}

// connect to this socket
address := _mainHost + ":" + _mainPort
conn, err := net.Dial(protocolTCP, address)

if err == nil && conn != nil {

if _dump {
// send to socket dump command
fmt.Fprintf(conn, "dump\n")
// get file
file, _ := os.Create(dataJSON)
defer file.Close()
n, err := io.Copy(file, conn)
if err == io.EOF {
fmt.Println(err.Error())
}
fmt.Println("Bytes received", n)
} else if _restore {
// send to socket restore command
fmt.Fprintf(conn, "restore ")

file, errF := os.Open(dataJSON) // For read access.
if errF != nil {
fmt.Println("Unable to open file, " + errF.Error())
}
defer file.Close() // make sure to close the file even if we panic.
n, error := io.Copy(conn, file)
if error != nil {
fmt.Printf("Send file error %s\r\n", error.Error())
}
fmt.Println(n, "Bytes sent")
fmt.Fprintf(conn, "\r\n")
file.Close()
} else {

for {
// read in input from stdin
if !_subscriber {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Text to send: ")
text, _ := reader.ReadString('\n')
if strings.ToUpper(strings.TrimRight(text, "\r\n")) == cmdExit {
//пользователь решил удалиться)))
break
}

if strings.ToUpper(strings.TrimRight(text, "\r\n")) == publish {
_publisher = true
_subscriber = false
}
if strings.ToUpper(strings.TrimRight(text, "\r\n")) == subscribe {
_subscriber = true
_publisher = false
}

// send to socket
fmt.Fprintf(conn, text+"\n")
}

// listen for reply
if !_publisher {
message, _ := bufio.NewReader(conn).ReadString('\n')
fmt.Println("Message from client: " + message)
}
}
}

} else {
fmt.Print("Didn't connect")
if err != nil {
fmt.Printf(", Error: %s\n\r", err.Error())
}
}
defer conn.Close()
}

Loading