Hi there, I'm Roman
faraway - |
|__cmd|
| |__client|
| | |__main.go
| |__server|
| |__main.go
|__internal|
| |__client|
| | |__client.go
| | |__client_test.go
| |__server|
| |__server.go
| |__server_test.go
|__pkg|
| |__pow|
| | |__pow.go
| | |__pow_test.go
| |config|
| | |__config.go
| |clock|
| | |__clock.go
| |
| |protocol|
| | |__protocol.go
| | |__protocol_test.go
| |
| |redis|
| | |__redis.go
| | |__memory.go
|
|_.gitignore
|_go.mod
|_client.Dockerfile
|_server.Dockerfile
|_docker-compose.yml
|_README.md
|_Makefile
Test task for Server Engineer
Design and implement “Word of Wisdom” tcp server.
- TCP server should be protected from DDOS attacks with the Prof of Work (https://en.wikipedia.org/wiki/Proof_of_work), the challenge-response protocol should be used.
- The choice of the POW algorithm should be explained.
- After Prof Of Work verification, server should send one of the quotes from “word of wisdom” book or any other collection of the quotes.
- Docker file should be provided both for the server and for the client that solves the POW challenge
- SHA-256 (Bitcoin): :Преимущества: Широко известен, имеет большое сообщество и высокий уровень безопасности благодаря своей распространенности и долгому времени работы. :Недостатки: Майнинг на ASIC-устройствах может привести к централизации, так как оборудование ASIC дорого и не доступно каждому.
- Ethash (Ethereum): :Преимущества: Ориентирован на GPU, что способствует более равномерному распределению майнинга. Майнинг на стандартных видеокартах доступен широкому кругу людей. :Недостатки: Требует большого объема памяти, что может быть непрактичным для мобильных устройств или устройств с ограниченной памятью.
- Scrypt (Litecoin): :Преимущества: Также ориентирован на GPU и менее подвержен централизации через ASIC. Относительно более доступный майнинг для обычных пользователей. :Недостатки: Возможно, менее безопасен, чем более распространенные алгоритмы.
- RandomX (Monero): :Преимущества: Сложный для реализации на ASIC, что поддерживает более равномерное распределение майнинга. Майнинг может быть выполнен на обычных компьютерах. :Недостатки: Требует большого объема памяти и вычислительной мощности, что может быть недоступно для некоторых устройств.
- Выводит сообщение "start server" при старте сервера.
- Загружает конфигурацию из файла "config/config.json" с помощью функции config.Load(). Если загрузка конфигурации завершается с ошибкой, выводит сообщение об ошибке и завершает выполнение.
- Инициализирует контекст (ctx) и передает в него загруженную конфигурацию с помощью context.WithValue().
- Инициализирует и добавляет в контекст экземпляр clock.SystemClock{} для работы с временем.
- Инициализирует и добавляет в контекст экземпляр redis.RedisCache для работы с кэшем Redis.
- Инициализирует генератор случайных чисел с использованием текущего времени для случайного выбора цитат.
- Запускает серверное приложение с помощью функции server.Run(), передавая контекст и адрес. Если запуск сервера завершается с ошибкой, выводит сообщение об ошибке.
- Выводит сообщение "start client" при старте клиента.
- Загружает конфигурацию из файла "config/config.json" с помощью функции config.Load(). Если загрузка конфигурации завершается с ошибкой, выводит сообщение об ошибке и завершает выполнение.
- Инициализирует контекст (ctx) и передает в него загруженную конфигурацию с помощью context.WithValue().
- Создает строку адреса, объединяя значения хоста и порта из конфигурации.
- Запускает клиентское приложение с помощью функции client.Run(), передавая контекст и адрес. Если запуск клиента завершается с ошибкой, выводит сообщение об ошибке.
- NewClient(conn net.Conn) *Client: Эта функция создает и возвращает новый экземпляр клиента Client, связанный с переданным соединением conn. Она инициализирует читатель (reader) и писатель (writer) для этого клиента.
- Close() error: Эта метод функции Client закрывает соединение клиента. Это важно для корректного завершения соединения после использования клиента.
- RequestChallenge() (*pow.HashcashData, error): Этот метод запроса вызывает сервер для получения вызова (challenge). Он отправляет соответствующее сообщение на сервер, читает и разбирает ответ, включая полученные данные вызова, и возвращает указатель на структуру HashcashData, представляющую полученный вызов.
- ComputeAndSendHashcash(ctx context.Context, hashcash *pow.HashcashData, conf *config.Config) error: Этот метод вычисляет хэш-наличие (hashcash) для проверки Proof of Work и отправляет его обратно на сервер. Он принимает контекст, данные хэш-наличия и конфигурацию, вычисляет хэш-наличие согласно максимальному количеству итераций в конфигурации, маршалирует его в JSON и отправляет обратно на сервер.
- GetResultQuote() (string, error): Этот метод получает и возвращает результат (цитату) от сервера после успешного завершения работы с хэш-наличием. Он читает сообщение от сервера и извлекает полезную нагрузку (payload) цитаты.
- Run(ctx context.Context, address string) error: Это главная функция, которая управляет работой клиента. Она устанавливает соединение с сервером, создает экземпляр клиента, выполняет цикл, отправляя запросы на сервер, вычисляя и отправляя хэш-наличие, и получая результаты. Она также обрабатывает контекст и таймер для управления временем между запросами.
- Quotes: Слайс строк, содержащий несколько цитат для ответов на запросы клиента.
- ErrQuit: Ошибка, которая используется для завершения соединения с клиентом.
- Run(ctx context.Context, address string) error: Это главная функция, которая запускает сервер, прослушивая указанный адрес. Она создает слушатель (listener) для принятия входящих подключений. Затем она в бесконечном цикле принимает подключения и запускает для каждого нового подключения функцию handleConnection() в отдельной горутине.
- handleConnection(ctx context.Context, conn net.Conn): Эта функция обрабатывает взаимодействие с клиентом через установленное соединение conn. Она читает входящие сообщения от клиента с помощью bufio.NewReader, вызывает функцию ProcessRequest() для обработки запроса клиента и отправляет ответ обратно с помощью функции sendMsg().
- ProcessRequest(ctx context.Context, msgStr string, clientInfo string) (*protocol.Message, error): Эта функция обрабатывает запрос от клиента в зависимости от типа сообщения. В зависимости от заголовка сообщения (типа запроса), она вызывает соответствующую функцию для обработки.
- handleChallengeRequest(ctx context.Context, clientInfo string) (*protocol.Message, error): Эта функция обрабатывает запрос клиента на получение задания (challenge). Она создает новое задание (challenge) для клиента, добавляет случайное значение в кеш и отправляет сообщение с заданием обратно клиенту.
- handleResourceRequest(ctx context.Context, clientInfo string, payload string) (*protocol.Message, error): Эта функция обрабатывает запрос клиента на ресурс. Она проверяет корректность hashcash, включая проверку наличия корректного случайного значения в кеше, а также проверку на протухание hashcash. Если всё верно, она вычисляет hashcash и возвращает клиенту случайную цитату.
- sendMsg(msg protocol.Message, conn net.Conn) error: Эта функция отправляет сообщение обратно клиенту через соединение conn. Сообщение перед отправкой преобразуется в строку и завершается символом новой строки.
- Определение структуры HashcashData, представляющей данные для создания хеш-наличия (hashcash).
- Метод Stringify(), который преобразует данные hashcash в строку.
- Функция sha256Hash(), вычисляющая хеш SHA-1 для заданной строки.
- Функция IsHashCorrect(), проверяющая, удовлетворяет ли хеш требованиям по количеству ведущих нулей.
- Метод ComputeHashcash(), который вычисляет hashcash методом перебора (брутфорса) до тех пор, пока не будет найдено подходящее значение хеша.
- Ссодержит определение простой структуры для системных часов (SystemClock), которая имеет метод Now(), возвращающий текущее время с использованием пакета time
- Этот код предоставляет абстракцию для получения текущего времени через системные часы, путем обращения к методу Now() структуры SystemClock
- Quit: Представляет тип сообщения, указывающий, что либо сервер, либо клиент хочет закрыть соединение.
- RequestChallenge: Указывает на сообщение от клиента к серверу, запрашивающее новый вызов от сервера.
- ResponseChallenge: Указывает на сообщение от сервера клиенту, содержащее вызов для клиента.
- RequestResource: Указывает на сообщение от клиента к серверу, содержащее решение вызова или запрос ресурса.
- ResponseResource: Указывает на сообщение от сервера клиенту, содержащее полезную информацию, если решение верное, или ошибку в противном случае.
- Message: Представляет структуру сообщения, содержащую два поля:
- Header: Целое число, указывающее тип сообщения.
- Payload: Строка, содержащая полезную нагрузку сообщения, которая может быть JSON, цитатой или быть пустой.
- Stringify(): Метод, ассоциированный с структурой Message, который преобразует сообщение в строковый формат для отправки через соединение TCP. Формат: "Header|Payload".
- ParseMessage(): Функция, которая принимает строку в качестве входных данных и пытается разобрать её в структуру Message. Она разделяет входную строку на части с использованием символа "|", а затем проверяет количество частей. Если частей одна или две, она пытается разобрать заголовок. В случае успешного разбора она создает структуру Message и возвращает её, включая разобранный заголовок и необязательную полезную нагрузку.
- B этом файле представлены тесты для различных сценариев разбора сообщений с использованием библиотеки testify/assert. Каждый тест проверяет разные аспекты функции ParseMessage и утверждает, что ожидаемые результаты соответствуют действительным. Эти тесты помогают убедиться, что функция ParseMessage работает правильно для различных случаев.
- InitRedisCache: Эта функция инициализирует экземпляр RedisCache и устанавливает соединение с Redis-сервером. Принимает контекст ctx, адрес host и порт port. Создает и конфигурирует клиент Redis. В конце функции, используя этот клиент, устанавливается тестовое значение в Redis для проверки соединения. Возвращает экземпляр RedisCache и ошибку (если есть).
- Add: Добавляет случайное значение с заданным временем жизни (в секундах) в кеш Redis. Принимает ключ key и время expiration. Использует метод Set клиента Redis для установки значения ключа.
- Get: Проверяет наличие ключа key в кеше Redis. Возвращает true, если ключ существует и не просрочен, и ошибку (если есть).
- Delete: Удаляет ключ key из кеша Redis, используя метод Del клиента Redis.
- MemoryCache: Создает экземпляр InMemoryCache, который представляет собой кеш в памяти. Используется для тестирования вместо Redis. Принимает в качестве параметра объект Clock, который используется для определения текущего времени (это позволяет легко заменить реальное время на тестовое).
- Add: Добавляет случайное значение с заданным временем жизни (в секундах) в кеш. Принимает ключ key и время expiration. Записывает внутренние данные с меткой времени и временем жизни.
- Get: Проверяет наличие ключа key в кеше. Возвращает true, если ключ существует и не просрочен, и ошибку (если есть).
- Delete: Удаляет ключ key из кеша.
install:
go mod download
build:
go build -o bin/server app/cmd/server/main.go
go build -o bin/client app/cmd/client/main.go
test:
go clean --testcache
go test ./...
start-server:
go run app/cmd/server/main.go
start-client:
go run app/cmd/client/main.go
start:
docker-compose up --abort-on-container-exit --force-recreate --build server --build client
.PHONY: .install-linter
.install-linter:
### INSTALL GOLANGCI-LINT ###
[ -f $(GOLANCI_LINT) ] || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(PROJECT_BIN) $(GOLANCI_LINT_VERSION)
.PHONY: lint
lint: .install-linter
### RUN GOLANGCI-LINT ###
$(GOLANGCI_LINT) run ./... --config=./.golangci.yml
.PHONY: lint-fast
lint-fast: .install-linter
$(GOLANGCI_LINT) run ./... --fast --config=./.golangci.yml
1.Run the tests:
go test