diff --git a/CMakeLists.txt b/CMakeLists.txt
index d2919ae..dd14aa6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,10 +1,16 @@
-project(DeepPic CXX)
-
-cmake_minimum_required(VERSION 3.6)
+cmake_minimum_required(VERSION 3.20)
+project(boost_asio_async_server)
set(CMAKE_CXX_STANDARD 20)
-file(GLOB_RECURSE HEADER_FILES project/include/*.h)
-file(GLOB_RECURSE SOURCE_FILES project/src/*.cpp)
+find_package(Boost 1.40.0 REQUIRED system)
+add_definitions(-DBOOST_LOG_DYN_LINK)
+find_package(Boost COMPONENTS log log_setup REQUIRED)
+find_package(nlohmann_json 3.2.0 REQUIRED)
+link_directories(${Boost_LIBRARY_DIR})
+
+include_directories(project/include ${Boost_INCLUDE_DIRS})
+aux_source_directory(project/src SRC)
+add_executable(boost_asio_async_server ${SRC})
-add_executable(main ${HEADER_FILES} ${SOURCE_FILES})
\ No newline at end of file
+target_link_libraries(boost_asio_async_server boost_thread pthread ${BOOST_LIBRARIES} nlohmann_json::nlohmann_json Boost::log Boost::log_setup)
diff --git a/about_json_api.md b/about_json_api.md
new file mode 100644
index 0000000..19abcbe
--- /dev/null
+++ b/about_json_api.md
@@ -0,0 +1,93 @@
+# Всевозможные сценарии комманд
+
+Клиент, чтобы расшарить документ:
+```json
+{
+ "target": "sharing_document"
+}
+```
+
+Сервер в ответ:
+```json
+{
+ "status": "OK", // или "FAIL"
+ "target": "sharing_document" // для указания цели, на которую пришел ответ
+ "auth_token": "dummy_aboba",
+ "address": "127.0.0.1",
+ "port": 5555
+}
+```
+
+
+
+Клиент, чтобы подключиться к документу:
+```json
+{
+ "target": "auth",
+ "auth_token": "dummy_aboba",
+ "address": "127.0.0.1",
+ "port": 5555
+}
+```
+
+Сервер в ответ:
+```json
+{
+ "status": "OK" // или FAIL
+ "target": "auth"
+}
+```
+
+
+
+Клиент, чтобы запросить после подключения копию документа
+```json
+{
+ "target": "get_document"
+}
+```
+Сервер клиенту, у которого есть копия документа:
+```json
+{
+ "target": "get_document"
+}
+```
+Клиент в ответ на запрос сервера на документ:
+```json
+{
+ "status": "OK", // или FAIL, тогда поле document не обязательно
+ "target": "get_document",
+ "document": "some document code"
+}
+```
+
+Сервер изначальному клиенту, который запрашивал документ:
+```json
+{
+ "status": "OK", // или FAIL, тогда поле document не обязательно
+ "target": "get_document",
+ "document": "some document code"
+}
+```
+
+
+
+Клиент, чтобы расшарить команду:
+```json
+{
+ "target": "sharing_command",
+ "command": "some command code"
+}
+```
+
+Сервер в ответ: решительное ничего
+
+Сервер всем остальным клиентам, подключенным к документу:
+```json
+{
+ "target": "sharing_command",
+ "command": "some command code"
+}
+```
+
+Клиенты в ответ после получения команды: решительное ничего
diff --git a/project/ServerConnection/include/ServerConnection.h b/project/ServerConnection/include/ServerConnection.h
new file mode 100644
index 0000000..6b0da1d
--- /dev/null
+++ b/project/ServerConnection/include/ServerConnection.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define BUFFER_LENGTH 1024
+#define END_STR "\r\n"
+
+
+/*
+ * Структура с колбеками, которые будут вызываться после успешного чтения и записи соответственно.
+ */
+struct ServerConnectionCallbacks {
+ std::function onReadCb;
+ std::function onWriteCb;
+};
+
+class ServerConnection {
+public:
+ ServerConnection(std::string &&url, int port, ServerConnectionCallbacks &&callbacks);
+
+ /*
+ * Выполняет подключение к серверу и начинает бесконечный цикл чтения сообщений с сервера.
+ * После того как прочитали сообщение, вызовется onReadCb из ServerConnectionCallbacks.
+ */
+ void start();
+
+ /**
+ * Пишет серверу какое-либо сообщение. После записи вызовет onWriteCb, переданный в структуре ServerConnectionCallbacks
+ * @param message само сообщение
+ */
+ void write(std::string &&message);
+
+private:
+ void connectionToServer();
+
+ void read();
+
+ void readHandler(boost::system::error_code &err, size_t bytes_transferred);
+
+ std::size_t checkEndOfRead(const boost::system::error_code &err, std::size_t bytes_transferred);
+
+ void writeHandler(boost::system::error_code &err);
+
+ void run_ioc();
+
+ std::string serverUrl_;
+ int serverPort_;
+
+ boost::asio::io_context service_;
+ boost::asio::ip::tcp::socket socket_;
+ ServerConnectionCallbacks callbacks_;
+
+ std::mutex writeMutex_;
+ std::condition_variable writeCv_;
+ std::atomic canWrite_ = true;
+
+ char readBuf_[BUFFER_LENGTH];
+ char sendBuf_[BUFFER_LENGTH];
+};
+
diff --git a/project/ServerConnection/src/ServerConnection.cpp b/project/ServerConnection/src/ServerConnection.cpp
new file mode 100644
index 0000000..9caa738
--- /dev/null
+++ b/project/ServerConnection/src/ServerConnection.cpp
@@ -0,0 +1,80 @@
+#include
+
+#include "../include/ServerConnection.h"
+
+ServerConnection::ServerConnection(std::string &&url, int port, ServerConnectionCallbacks &&callbacks) : socket_(service_),
+ serverUrl_(std::move(url)),
+ serverPort_(port),
+ callbacks_(std::move(callbacks)) {
+}
+
+void ServerConnection::connectionToServer() {
+ boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(serverUrl_), serverPort_);
+ socket_.connect(ep);
+ read();
+}
+
+void ServerConnection::read() {
+ boost::asio::async_read(socket_, boost::asio::buffer(readBuf_),
+ [this](boost::system::error_code err, std::size_t bytes_transferred) -> size_t {
+ return this->checkEndOfRead(err, bytes_transferred);
+ }, [this](boost::system::error_code err, std::size_t bytes_transferred) {
+ this->readHandler(err, bytes_transferred);
+ });
+}
+
+void ServerConnection::readHandler(boost::system::error_code &err, size_t bytes_transferred) {
+ if (err) {
+ return;
+ }
+
+ if (callbacks_.onReadCb) {
+ callbacks_.onReadCb(std::string(readBuf_, readBuf_ + bytes_transferred));
+ }
+
+ read();
+}
+
+void ServerConnection::run_ioc() {
+ service_.run();
+}
+
+void ServerConnection::start() {
+ connectionToServer();
+ run_ioc();
+}
+
+void ServerConnection::write(std::string &&message) {
+ std::unique_lock ulock(writeMutex_);
+ writeCv_.wait(ulock, [this]() { return bool(canWrite_); });
+ canWrite_ = false;
+
+ for (int i = 0; i < message.length(); ++i) {
+ sendBuf_[i] = message[i];
+ }
+
+ boost::asio::async_write(socket_, boost::asio::buffer(sendBuf_, message.length()), [this](boost::system::error_code err) {
+ this->writeHandler(err);
+ });
+}
+
+void ServerConnection::writeHandler(boost::system::error_code &err) {
+ if (err) {
+ return;
+ }
+
+ if (callbacks_.onWriteCb) {
+ callbacks_.onWriteCb(err);
+ }
+
+ canWrite_ = true;
+ writeCv_.notify_one();
+}
+
+std::size_t ServerConnection::checkEndOfRead(const boost::system::error_code &err, std::size_t bytes_transferred) {
+ if (bytes_transferred > 0 &&
+ std::string(readBuf_ + bytes_transferred - 1 - std::strlen(END_STR), readBuf_ + bytes_transferred - 1) == std::string(END_STR)) {
+ return 0;
+ }
+ return 1;
+}
diff --git a/project/include/Command.h b/project/include/Command.h
new file mode 100644
index 0000000..40624e1
--- /dev/null
+++ b/project/include/Command.h
@@ -0,0 +1,101 @@
+#pragma once
+
+#include
+
+#include "Settings.h"
+#include "SharedDocumentServer.h"
+#include "Connection.h"
+
+using json = nlohmann::json;
+
+typedef enum {
+ SHARING_COMMAND = 0x01,
+ GET_DOCUMENT = 0x02,
+ CREATE_DOCUMENT = 0x04
+} command_t;
+
+class Command {
+public:
+ Command(command_t type_command, std::vector> *connection = nullptr);
+
+ Command(const Command &) = delete;
+
+ Command &operator=(Command &) = delete;
+
+ virtual bool do_command(json &command, std::shared_ptr author);
+
+ ~Command();
+
+protected:
+ Command() : letter_(nullptr) {}
+
+private:
+ Command *letter_;
+};
+
+class GetDocument : public Command {
+public:
+ virtual bool do_command(json &command, std::shared_ptr author) override;
+
+ GetDocument(const GetDocument &) = delete;
+
+ GetDocument &operator=(GetDocument &) = delete;
+
+ ~GetDocument() = default;
+
+private:
+ friend class Command;
+
+ explicit GetDocument(std::vector> *connections);
+
+ void getDocumentFromClient(const std::shared_ptr& author);
+
+ void handleGetDocumentFromClient(std::shared_ptr &connection, const std::shared_ptr& author);
+
+ void sendDocumentToNewClients(std::string &&document);
+
+ std::vector> clientsToGetDocument_;
+
+ std::vector> *connections_ = nullptr;
+};
+
+class SharingCommand : public Command {
+public:
+ virtual bool do_command(json &command, std::shared_ptr author) override;
+
+ SharingCommand(const SharingCommand &) = delete;
+
+ SharingCommand &operator=(SharingCommand &) = delete;
+
+ ~SharingCommand() = default;
+
+private:
+ friend class Command;
+
+ explicit SharingCommand(std::vector> *connections);
+
+ std::vector> *connections_ = nullptr;
+};
+
+class CreateNewDocumentCommand : public Command {
+public:
+ virtual bool do_command(json &command, std::shared_ptr author) override;
+
+ ~CreateNewDocumentCommand();
+
+private:
+ friend class Command;
+
+ CreateNewDocumentCommand() = default;
+
+ std::vector> sharedDocuments_;
+};
+
+class DocumentCommandBus {
+public:
+ explicit DocumentCommandBus(std::vector> *connection);
+ bool do_command(std::string &&command, std::shared_ptr author);
+private:
+ Command sharingCommand_;
+ Command getDocumentCommand_;
+};
\ No newline at end of file
diff --git a/project/include/CommandConstructor.h b/project/include/CommandConstructor.h
new file mode 100644
index 0000000..fd2f7b2
--- /dev/null
+++ b/project/include/CommandConstructor.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include
+
+using json = nlohmann::json;
+
+class CommandConstructor {
+public:
+ static json sharingDocumentClient();
+
+ static json sharingDocumentServer(std::string &&status, std::string &&auth_token, std::string &&address, int port);
+
+ static json authClient(std::string &&auth_token);
+
+ static json authServer(std::string &&status, std::string &&token, std::string &&address, int port);
+
+ static json getDocumentClient();
+
+ static json getDocumentServer(std::string &&status, std::string &&document);
+
+ static json sharingCommandClient(std::string &&command_);
+};
\ No newline at end of file
diff --git a/project/include/Connection.h b/project/include/Connection.h
new file mode 100644
index 0000000..f57e153
--- /dev/null
+++ b/project/include/Connection.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include "Settings.h"
+
+class Connection : public std::enable_shared_from_this {
+public:
+ Connection(boost::asio::io_context &service,
+ std::function, std::string &&)> on_read_cb,
+ std::function)> on_delete_cb = {});
+
+ boost::asio::ip::tcp::socket &getSock();
+
+ void afterConnect();
+
+ void write(std::string &command, std::function)> onWriteCb = {});
+
+ void stop();
+
+ void setAuth(bool value);
+
+ bool getAuth();
+
+ ~Connection();
+
+private:
+ void read();
+
+ void readHandler(const boost::system::error_code &err, std::size_t bytes_transferred);
+
+ void writeHandler(const boost::system::error_code &err, std::size_t bytes_transferred,
+ std::function)> onWriteCb);
+
+ boost::asio::ip::tcp::socket sock_;
+
+ std::function, std::string &&)> onReadCb_;
+ std::function)> onDeleteCb_;
+
+ boost::beast::flat_buffer buffer_{BUFFER_LENGTH};
+
+ boost::beast::http::request request_;
+ boost::beast::http::response response_;
+
+ std::mutex writeMutex_;
+ std::condition_variable writeCv_;
+ std::atomic canWrite_ = true;
+
+ bool isAuth_ = false;
+};
\ No newline at end of file
diff --git a/project/include/MainServer.h b/project/include/MainServer.h
new file mode 100644
index 0000000..ed9f8a1
--- /dev/null
+++ b/project/include/MainServer.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "Server.h"
+#include "Connection.h"
+#include "Command.h"
+
+class MainServer {
+public:
+ MainServer(int port, int countThreads);
+ void runServer();
+
+private:
+ void handleAcceptConnection(std::shared_ptr connection);
+
+ void onReadCb(std::shared_ptr, std::string &&command);
+
+ boost::asio::io_context service_;
+ Server server_;
+ Command *createNewDocumentCommand_;
+ int countThreads_;
+};
\ No newline at end of file
diff --git a/project/include/Server.h b/project/include/Server.h
new file mode 100644
index 0000000..cf3b860
--- /dev/null
+++ b/project/include/Server.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include
+
+#include "Connection.h"
+#include "Settings.h"
+
+struct ServerCallbacks {
+ std::function)> onAcceptCb_;
+ std::function, std::string &&)> onReadCb_;
+ std::function)> onDeleteCb_;
+};
+
+class Server {
+public:
+ Server(int port, boost::asio::io_context &service, ServerCallbacks &&callbacks);
+
+ void runServer();
+
+ int getPort();
+
+private:
+ void startAcceptConnections();
+
+ void handleAcceptConnection(std::shared_ptr connection, const boost::system::error_code &err);
+
+ boost::asio::ip::tcp::acceptor acceptor_;
+ int port_;
+
+ ServerCallbacks callbacks_;
+
+ char readBuf_[BUFFER_LENGTH];
+ char sendBuf_[BUFFER_LENGTH];
+};
\ No newline at end of file
diff --git a/project/include/Settings.h b/project/include/Settings.h
new file mode 100644
index 0000000..b4a14a0
--- /dev/null
+++ b/project/include/Settings.h
@@ -0,0 +1,3 @@
+#pragma once
+
+constexpr int BUFFER_LENGTH = 10000;
\ No newline at end of file
diff --git a/project/include/SharedDocumentServer.h b/project/include/SharedDocumentServer.h
new file mode 100644
index 0000000..86981e0
--- /dev/null
+++ b/project/include/SharedDocumentServer.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "Server.h"
+#include "Connection.h"
+#include "Settings.h"
+
+class DocumentCommandBus;
+
+class SharedDocumentServer {
+public:
+ static int start_since_port; // начиная с какого порта начнут создаваться объекты SharedDocumentServer
+
+ SharedDocumentServer(boost::asio::io_context &service);
+
+ void startShared();
+
+ int getPort();
+
+ std::string getAuthToken();
+
+ ~SharedDocumentServer();
+
+private:
+ void generateAuthToken();
+
+ void handleAcceptConnection(std::shared_ptr connection);
+
+ void onDeleteConnection(std::shared_ptr connection);
+
+ bool checkAuthToken(std::string &&token);
+
+ void onReadCb(std::shared_ptr author, std::string &&command);
+
+
+ Server server_;
+ std::string authToken_;
+ std::vector> connections_;
+
+ DocumentCommandBus *documentCommandBus_;
+};
diff --git a/project/src/Command.cpp b/project/src/Command.cpp
new file mode 100644
index 0000000..880b310
--- /dev/null
+++ b/project/src/Command.cpp
@@ -0,0 +1,139 @@
+#include
+#include
+#include
+
+#include "Command.h"
+#include "CommandConstructor.h"
+
+using json = nlohmann::json;
+
+Command::Command(command_t type_command, std::vector> *connections) {
+ switch (type_command) {
+ case GET_DOCUMENT:
+ letter_ = new GetDocument(connections);
+ break;
+ case SHARING_COMMAND:
+ letter_ = new SharingCommand(connections);
+ break;
+ case CREATE_DOCUMENT:
+ letter_ = new CreateNewDocumentCommand();
+ break;
+ }
+}
+
+bool Command::do_command(json &command, std::shared_ptr author) {
+ letter_->do_command(command, author);
+
+ return true;
+}
+
+Command::~Command() {
+ delete letter_;
+}
+
+bool GetDocument::do_command(json &command, std::shared_ptr author) {
+ if (command["target"] == "get_document") {
+ if (command.contains("status") && command["status"] == "OK") { // если пришел документ от одного из пользователей
+ sendDocumentToNewClients(std::move(command["document"])); // рассылаем его всем нуждающимся
+ return true;
+ } else if (!command.contains("status")) { // если пришел запрос на получение документа
+ clientsToGetDocument_.push_back(author); // добавляем автора запроса в вектор тех, кому нужен документ
+ getDocumentFromClient(author); // и запрашиваем документ у одного из участников
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void GetDocument::getDocumentFromClient(const std::shared_ptr& author) {
+ std::string command_dump = CommandConstructor::getDocumentClient().dump();
+ if (!connections_->empty()) {
+ auto it = connections_->begin();
+ while (*it == author) {
+ it++;
+ }
+ if (it != connections_->end()) {
+ (*(connections_->begin()))->write(command_dump,
+ [this, author](std::shared_ptr connection) {
+ this->handleGetDocumentFromClient(connection, author);
+ });
+ }
+ }
+}
+
+void GetDocument::handleGetDocumentFromClient(std::shared_ptr &connection, const std::shared_ptr& author) {
+ if (connection == nullptr) {
+ getDocumentFromClient(author);
+ }
+}
+
+void GetDocument::sendDocumentToNewClients(std::string &&document) {
+ std::string command_dump = CommandConstructor::getDocumentServer("OK", std::move(document)).dump();
+ for (auto &client: clientsToGetDocument_) {
+ client->write(command_dump);
+ }
+ clientsToGetDocument_.clear();
+}
+
+GetDocument::GetDocument(std::vector> *connections) {
+ connections_ = connections;
+}
+
+SharingCommand::SharingCommand(std::vector> *connections) {
+ connections_ = connections;
+}
+
+bool SharingCommand::do_command(json &command, std::shared_ptr author) {
+ for (auto &connection: *connections_) {
+ if (author != connection) {
+ std::cerr << "SharingCommand::do_command()" << std::endl;
+ std::string command_dump = command.dump();
+ connection->write(command_dump);
+ }
+ }
+
+ return true;
+}
+
+bool CreateNewDocumentCommand::do_command(json &command, std::shared_ptr author) {
+ BOOST_LOG_TRIVIAL(info) << "Create new document";
+
+ std::shared_ptr shared_document(
+ new SharedDocumentServer(static_cast(author->getSock().get_executor().context())));
+ sharedDocuments_.push_back(shared_document);
+ shared_document->startShared();
+
+ std::string command_dump = CommandConstructor::sharingDocumentServer("OK", shared_document->getAuthToken(), "127.0.0.1",
+ shared_document->getPort()).dump();
+ author->write(command_dump);
+
+ return true;
+}
+
+CreateNewDocumentCommand::~CreateNewDocumentCommand() {
+}
+
+DocumentCommandBus::DocumentCommandBus(std::vector> *connection) : getDocumentCommand_(GET_DOCUMENT,
+ connection),
+ sharingCommand_(SHARING_COMMAND,
+ connection) {
+
+}
+
+bool DocumentCommandBus::do_command(std::string &&command, std::shared_ptr author) {
+ try {
+ auto command_json = json::parse(command);
+ if (command_json.contains("target")) {
+ if (command_json["target"] == "sharing_command" && command_json.contains("command")) {
+ return sharingCommand_.do_command(command_json, author);
+ } else if (command_json["target"] == "get_document") {
+ return getDocumentCommand_.do_command(command_json, author);
+ }
+ }
+ return false;
+ } catch (...) {
+ return false;
+ }
+
+}
diff --git a/project/src/CommandConstructor.cpp b/project/src/CommandConstructor.cpp
new file mode 100644
index 0000000..ae38d02
--- /dev/null
+++ b/project/src/CommandConstructor.cpp
@@ -0,0 +1,54 @@
+#include "CommandConstructor.h"
+
+json CommandConstructor::sharingDocumentClient() {
+ static json command = {{"target", "sharing_document"}};
+ return command;
+}
+
+json CommandConstructor::sharingDocumentServer(std::string &&status, std::string &&auth_token, std::string &&address, int port) {
+ json command = {{"status", status},
+ {"target", "sharing_document"},
+ {"auth_token", auth_token},
+ {"address", address},
+ {"port", port}};
+
+ return command;
+}
+
+json CommandConstructor::authClient(std::string &&auth_token) {
+ json command = {{"target", "auth"},
+ {"auth_token", auth_token}};
+
+ return command;
+}
+
+json CommandConstructor::authServer(std::string &&status, std::string &&token, std::string &&address, int port) {
+ json command = {{"status", status},
+ {"target", "auth"},
+ {"auth_token", token},
+ {"port", port},
+ {"address", address}};
+
+ return command;
+}
+
+json CommandConstructor::getDocumentClient() {
+ static json command = {{"target", "get_document"}};
+
+ return command;
+}
+
+json CommandConstructor::getDocumentServer(std::string &&status, std::string &&document) {
+ json command = {{"status", status},
+ {"target", "get_document"},
+ {"document", document}};
+
+ return command;
+}
+
+json CommandConstructor::sharingCommandClient(std::string &&command_) {
+ json command = {{"target", "sharing_command"},
+ {"command", command_}};
+
+ return command;
+}
diff --git a/project/src/Connection.cpp b/project/src/Connection.cpp
new file mode 100644
index 0000000..b5270fd
--- /dev/null
+++ b/project/src/Connection.cpp
@@ -0,0 +1,105 @@
+#include
+#include
+#include
+#include
+
+#include "Connection.h"
+
+Connection::Connection(boost::asio::io_context &service, std::function, std::string &&)> on_read_cb,
+ std::function)> on_delete_cb) : sock_(service),
+ onReadCb_(std::move(on_read_cb)),
+ onDeleteCb_(std::move(on_delete_cb)) {
+
+}
+
+Connection::~Connection() {
+ BOOST_LOG_TRIVIAL(info) << "~Connection()";
+}
+
+void Connection::afterConnect() {
+ read();
+}
+
+void Connection::read() {
+ boost::beast::http::async_read(sock_, buffer_, request_,
+ [self = shared_from_this()](boost::system::error_code err, size_t bytes_transferred) {
+ self->readHandler(err, bytes_transferred);
+ });
+}
+
+void Connection::write(std::string &command, std::function)> onWriteCb) {
+ // функция write может быть вызвана из разных потоков, поэтому блокируем ее до тех пор, пока другой поток не закончит запись
+ std::unique_lock ulock(writeMutex_);
+ writeCv_.wait(ulock, [this]() { return bool(canWrite_); });
+ canWrite_ = false;
+
+ response_.clear();
+
+ response_.keep_alive(request_.keep_alive());
+ response_.version(request_.version());
+ response_.result(boost::beast::http::status::ok);
+ response_.body() = command;
+
+ response_.prepare_payload();
+
+ boost::beast::http::async_write(sock_, response_,
+ [self = shared_from_this(), onWriteCb](boost::system::error_code err, size_t bytes_transferred) {
+ self->writeHandler(err, bytes_transferred, onWriteCb);
+ });
+}
+
+
+void Connection::readHandler(const boost::system::error_code &err, std::size_t bytes_transferred) {
+ if (err) {
+ BOOST_LOG_TRIVIAL(error) << "Error while read from client";
+ if (onDeleteCb_) {
+ onDeleteCb_(shared_from_this());
+ }
+ return;
+ }
+
+ onReadCb_(shared_from_this(), request_.body().data());
+
+ request_.clear();
+ request_.body().clear();
+
+ read();
+}
+
+
+void Connection::writeHandler(const boost::system::error_code &err, std::size_t bytes_transferred,
+ std::function)> onWriteCb) {
+ if (err) {
+ if (onDeleteCb_) {
+ onDeleteCb_(shared_from_this());
+ }
+ if (onWriteCb) {
+ onWriteCb(nullptr);
+ }
+ return;
+ }
+
+ if (onWriteCb) {
+ onWriteCb(shared_from_this());
+ }
+
+ canWrite_ = true;
+ writeCv_.notify_one();
+}
+
+void Connection::stop() {
+ sock_.close();
+}
+
+boost::asio::ip::tcp::socket &Connection::getSock() {
+ return sock_;
+}
+
+void Connection::setAuth(bool) {
+ isAuth_ = true;
+}
+
+bool Connection::getAuth() {
+ return isAuth_;
+}
+
diff --git a/project/src/MainServer.cpp b/project/src/MainServer.cpp
new file mode 100644
index 0000000..b4365f1
--- /dev/null
+++ b/project/src/MainServer.cpp
@@ -0,0 +1,53 @@
+#include "MainServer.h"
+#include "Command.h"
+
+#include
+#include
+#include
+#include
+#include
+
+using json = nlohmann::json;
+
+MainServer::MainServer(int port, int countThreads) : service_(), server_(port, service_,
+ ServerCallbacks{
+ [this](std::shared_ptr connection) {
+ this->handleAcceptConnection(std::move(connection));
+ },
+ [this](std::shared_ptr connection,
+ std::string &&command) {
+ this->onReadCb(std::move(connection),
+ std::move(command));
+ }, {}}
+),
+ countThreads_(countThreads) {
+ createNewDocumentCommand_ = new Command(CREATE_DOCUMENT);
+}
+
+void MainServer::runServer() {
+ server_.runServer();
+
+ std::vector threads;
+ threads.reserve(countThreads_);
+ for (int i = 0; i < countThreads_; ++i) {
+ threads.emplace_back([this]() {
+ service_.run();
+ });
+ }
+
+ for (auto &thread: threads) {
+ thread.join();
+ }
+}
+
+void MainServer::handleAcceptConnection(std::shared_ptr connection) {
+ BOOST_LOG_TRIVIAL(info) << "Accept new client to MainServer";
+ connection->afterConnect();
+}
+
+void MainServer::onReadCb(std::shared_ptr connection, std::string &&command) {
+ auto command_parse = json::parse(command);
+ if (command_parse["target"] == "sharing_document") {
+ createNewDocumentCommand_->do_command(command_parse, std::move(connection));
+ }
+}
diff --git a/project/src/Server.cpp b/project/src/Server.cpp
new file mode 100644
index 0000000..4eb540b
--- /dev/null
+++ b/project/src/Server.cpp
@@ -0,0 +1,49 @@
+#include
+#include
+#include
+#include
+#include
+
+#include "Server.h"
+
+
+Server::Server(int port, boost::asio::io_context &service, ServerCallbacks &&callbacks) : acceptor_(service),
+ port_(port),
+ callbacks_(std::move(callbacks)) {
+
+}
+
+void Server::runServer() {
+ BOOST_LOG_TRIVIAL(info) << "Server run";
+ boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port_);
+ acceptor_.open(endpoint.protocol());
+ acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
+ acceptor_.bind(endpoint);
+ acceptor_.listen(1024);
+
+ startAcceptConnections();
+}
+
+int Server::getPort() {
+ return port_;
+}
+
+void Server::startAcceptConnections() {
+ std::shared_ptr connection(
+ new Connection(static_cast(acceptor_.get_executor().context()), callbacks_.onReadCb_, callbacks_.onDeleteCb_));
+ acceptor_.async_accept(connection->getSock(), boost::bind(&Server::handleAcceptConnection, this,
+ connection, boost::asio::placeholders::error));
+ BOOST_LOG_TRIVIAL(info) << "Start accept connections";
+}
+
+void Server::handleAcceptConnection(std::shared_ptr connection, const boost::system::error_code &err) {
+ if (err) {
+ startAcceptConnections();
+ }
+
+ callbacks_.onAcceptCb_(connection);
+ startAcceptConnections();
+}
+
+
+
diff --git a/project/src/SharedDocumentServer.cpp b/project/src/SharedDocumentServer.cpp
new file mode 100644
index 0000000..4048df4
--- /dev/null
+++ b/project/src/SharedDocumentServer.cpp
@@ -0,0 +1,105 @@
+#include
+#include
+#include
+
+#include "SharedDocumentServer.h"
+#include "Command.h"
+#include "CommandConstructor.h"
+#include
+
+SharedDocumentServer::SharedDocumentServer(boost::asio::io_context &service) : server_(start_since_port++, service,
+ ServerCallbacks{
+ [this](std::shared_ptr connection) {
+ this->handleAcceptConnection(
+ std::move(connection));
+ },
+ [this](std::shared_ptr author,
+ std::string &&command) {
+ this->onReadCb(std::move(author),
+ std::move(command));
+ },
+ [this](std::shared_ptr connection) {
+ this->onDeleteConnection(
+ std::move(connection));
+ }}) {
+
+ generateAuthToken();
+ documentCommandBus_ = new DocumentCommandBus(&connections_);
+}
+
+void SharedDocumentServer::generateAuthToken() {
+ static const std::vector syms = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z', '_', '-', '$', '@'};
+ static int token_length = 20;
+ for (int i = 0; i < token_length; ++i) {
+ authToken_ += syms[rand() % syms.size()];
+ }
+}
+
+void SharedDocumentServer::startShared() {
+ server_.runServer();
+}
+
+
+bool SharedDocumentServer::checkAuthToken(std::string &&token) {
+ return token == authToken_;
+}
+
+
+void SharedDocumentServer::onReadCb(std::shared_ptr author, std::string &&command) {
+ BOOST_LOG_TRIVIAL(info) << "command from user: " << command;
+ //std::string command_cpy = command;
+ //documentCommandBus_->do_command(std::move(command_cpy), author);
+ auto command_parse = json::parse(command);
+ if (command_parse["target"] == "auth" && !author->getAuth()) {
+ if (checkAuthToken(command_parse["auth_token"])) {
+ std::string response_dump = CommandConstructor::authServer("OK", getAuthToken(), std::string("127.0.0.1"), getPort()).dump();
+ author->setAuth(true);
+ author->write(response_dump);
+ BOOST_LOG_TRIVIAL(info) << "Connect new client to document";
+ } else {
+ std::string response_dump = CommandConstructor::authServer("FAIL", "", std::string("127.0.0.1"), getPort()).dump();
+ author->setAuth(false);
+ // если пользователь послал не верный аутентификационный токен - удаляем его
+ author->write(response_dump, [](std::shared_ptr connection) { connection->stop(); });
+ }
+ } else if (author->getAuth()) {
+ documentCommandBus_->do_command(std::move(command), author);
+ }
+}
+
+
+void SharedDocumentServer::onDeleteConnection(std::shared_ptr connection) {
+ auto pos = connections_.begin();
+ for (auto &cl: connections_) {
+ if (*pos == connection) {
+ break;
+ }
+ pos++;
+ }
+ try {
+ BOOST_LOG_TRIVIAL(info) << "Delete client connection";
+ (*pos)->stop();
+ connections_.erase(pos);
+ } catch (...) {
+ }
+}
+
+int SharedDocumentServer::getPort() {
+ return server_.getPort();
+}
+
+
+std::string SharedDocumentServer::getAuthToken() {
+ return authToken_;
+}
+
+SharedDocumentServer::~SharedDocumentServer() {
+ delete documentCommandBus_;
+}
+
+void SharedDocumentServer::handleAcceptConnection(std::shared_ptr connection) {
+ BOOST_LOG_TRIVIAL(info) << "Accept new client to document";
+ connections_.push_back(connection);
+ connection->afterConnect();
+}
diff --git a/project/src/main.cpp b/project/src/main.cpp
index f330cc7..91e8e73 100644
--- a/project/src/main.cpp
+++ b/project/src/main.cpp
@@ -1,7 +1,12 @@
-//
-// Created by tesserakt on 30.10.2021.
-//
+#include
+
+#include "SharedDocumentServer.h"
+#include "MainServer.h"
+
+int SharedDocumentServer::start_since_port = 6070;
int main() {
- return 0;
+ srand(time(NULL));
+ MainServer server(8080, 4);
+ server.runServer();
}
\ No newline at end of file
diff --git a/project/unit_tests/main.cpp b/project/unit_tests/main.cpp
new file mode 100644
index 0000000..5822dd7
--- /dev/null
+++ b/project/unit_tests/main.cpp
@@ -0,0 +1,125 @@
+#include
+#include
+#include
+#include
+
+
+#include "Server.h"
+#include "Settings.h"
+#include "SharedDocumentServer.h"
+
+using namespace boost::asio;
+
+#define SHARING_DOCUMENT_COMMAND "sharing document"
+#define AUTH_COMMAND "auth_token="
+
+class TestServer : public ::testing::Test {
+protected:
+ void SetUp() {
+ myServer_ = new Server(5555);
+ myServer_->runServer();
+ ip::tcp::endpoint ep(ip::address::from_string("127.0.0.1"), 5555);
+ sock_ = new ip::tcp::socket(service_);
+ sock_->connect(ep);
+ }
+
+ void TearDown() {
+ delete myServer_;
+ delete sock_;
+ }
+
+ Server *myServer_;
+ io_service service_;
+ ip::tcp::socket *sock_;
+ char buf[1024];
+};
+
+class TestSharingDocument : public ::testing::Test {
+protected:
+ void SetUp() {
+ SharedDocumentServer::start_since_port = 8001;
+ document_ = new SharedDocumentServer();
+ document_->startShared();
+ auth_token_ = document_->getAuthToken();
+ port_ = document_->getPort();
+ }
+
+ void TearDown() {
+ delete document_;
+ }
+
+ SharedDocumentServer *document_;
+ std::string auth_token_;
+ int port_;
+ io_service service_;
+ char buf[1024];
+};
+
+size_t read_complete(char *buf, const std::error_code &err, size_t bytes_transferred) {
+ if (bytes_transferred > 0 && std::string(buf + bytes_transferred - 1 - std::strlen(END_STR), buf + bytes_transferred - 1) == std::string(END_STR)) {
+ return 0;
+ }
+ return 1;
+}
+
+TEST_F(TestServer, test) {
+ std::strcpy(buf, SHARING_DOCUMENT_COMMAND);
+ std::strcpy(buf + std::strlen(SHARING_DOCUMENT_COMMAND), END_STR);
+ sock_->write_some(buffer(buf, std::strlen(SHARING_DOCUMENT_COMMAND) + std::strlen(END_STR)));
+
+ int bytes = read(*sock_, buffer(buf), boost::bind(read_complete, buf, _1, _2));
+ std::string copy(buf, bytes - 1);
+ int port = -1;
+ EXPECT_NO_THROW([&]() { port = std::stoi(copy); });
+ EXPECT_GE(port, 0);
+ ip::tcp::socket to_shared_document_socket(service_);
+ ip::tcp::endpoint shared_document_ep(ip::address::from_string("127.0.0.1"), port);
+ EXPECT_NO_THROW(to_shared_document_socket.connect(shared_document_ep));
+}
+
+TEST_F(TestSharingDocument, test) {
+ EXPECT_EQ(port_, 8001);
+
+ ip::tcp::endpoint shared_doc_ep(ip::address::from_string("127.0.0.1"), port_);
+ ip::tcp::socket client1(service_);
+ EXPECT_NO_THROW(client1.connect(shared_doc_ep)); // подключаемся к расшаренному документы
+
+ // аутентификация в документе
+ std::strcpy(buf, AUTH_COMMAND);
+ std::strcpy(buf + std::strlen(AUTH_COMMAND), (document_->getAuthToken() + END_STR).c_str());
+ client1.write_some(buffer(buf, std::strlen(AUTH_COMMAND) + document_->getAuthToken().length() + std::strlen(END_STR)));
+
+ // проверка успешности аутентификации
+ int bytes = read(client1, buffer(buf), boost::bind(read_complete, buf, _1, _2));
+ EXPECT_STREQ(buf, AUTH_SUCCESS_COMMAND);
+
+ // подключение к документу второго клиента
+ ip::tcp::socket client2(service_);
+ EXPECT_NO_THROW(client2.connect(shared_doc_ep));
+
+ // аутентификация второго клиента в документе
+ std::strcpy(buf, AUTH_COMMAND);
+ std::strcpy(buf + std::strlen(AUTH_COMMAND), (document_->getAuthToken() + END_STR).c_str());
+ client2.write_some(buffer(buf, std::strlen(AUTH_COMMAND) + document_->getAuthToken().length() + std::strlen(END_STR)));
+
+ // проверка аутентификации
+ bytes = read(client1, buffer(buf), boost::bind(read_complete, buf, _1, _2));
+ EXPECT_STREQ(buf, AUTH_SUCCESS_COMMAND);
+
+ // отправка команды вторым клиентом
+ std::string command = "aboba welcomes you";
+ command += END_STR;
+ std::strcpy(buf, command.c_str());
+ client2.write_some(buffer(buf, command.length()));
+
+ // проверка получение первым клиентом команды второго клиента
+ bytes = read(client1, buffer(buf), boost::bind(read_complete, buf, _1, _2));
+ EXPECT_STREQ(buf, command.c_str());
+}
+
+
+int main(int argc, char *argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
\ No newline at end of file