diff --git a/apps/service.cpp b/apps/service.cpp index 66f7da59e..15acf4f23 100644 --- a/apps/service.cpp +++ b/apps/service.cpp @@ -1,99 +1,38 @@ -/* Copyright (c) 2015-2024, EPFL/Blue Brain Project +/* Copyright (c) 2015-2024 EPFL/Blue Brain Project * All rights reserved. Do not distribute without permission. - * Responsible Author: Cyrille Favreau + * + * Responsible Author: adrien.fleury@epfl.ch * * This file is part of Brayns * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include +#include #include #include -#include using namespace brayns::experimental; -using brayns::createConsoleLogger; using brayns::getCopyright; -namespace brayns::experimental -{ -struct Settings -{ - bool help = false; - bool version = false; - std::string host; - std::uint16_t port = 0; - std::size_t maxClient = 0; - std::size_t maxFrameSize = 0; - bool sslEnabled = false; - // SslSettings ssl = {}; -}; - -template<> -struct ArgvSettingsReflector -{ - static auto reflect() - { - auto builder = ArgvBuilder(); - builder.description(getCopyright()); - builder.option("help", [](auto &settings) { return &settings.help; }) - .description("Display this help message") - .defaultValue(false); - builder.option("version", [](auto &settings) { return &settings.version; }) - .description("Display brayns copyright with version") - .defaultValue(false); - builder.option("host", [](auto &settings) { return &settings.host; }) - .description("Websocket server hostname, use 0.0.0.0 to allow any host to connect") - .defaultValue("localhost"); - builder.option("port", [](auto &settings) { return &settings.port; }) - .description("Websocket server port") - .defaultValue(5000); - builder.option("max-client", [](auto &settings) { return &settings.maxClient; }) - .description("Maximum number of simultaneously connected clients") - .defaultValue(2); - builder.option("max-frame-size", [](auto &settings) { return &settings.maxFrameSize; }) - .description("Maximum frame size the websocket server accepts") - .defaultValue(std::numeric_limits::max()); - builder.option("ssl", [](auto &settings) { return &settings.sslEnabled; }) - .description("Enable SSL for websocket server, requires a certificate and a private key") - .defaultValue(false); - /*builder.option("private-key-file", [](auto &settings) { return &settings.ssl.privateKeyFile; }) - .description("SSL private key used by the websocket server") - .defaultValue(""); - builder.option("certificate-file", [](auto &settings) { return &settings.ssl.certificateFile; }) - .description("SSL certificate the websocket server will provide to clients") - .defaultValue(""); - builder.option("ca-location", [](auto &settings) { return &settings.ssl.caLocation; }) - .description("Path to a certificate file to use as certification authority or a CA directory") - .defaultValue(""); - builder.option("private-key-passphrase", [](auto &settings) { return &settings.ssl.privateKeyPassphrase; }) - .description("Passphrase for the private key if encrypted") - .defaultValue("");*/ - return builder.build(); - } -}; -} - int main(int argc, const char **argv) { - auto logger = createConsoleLogger("Brayns"); - try { - auto settings = parseArgvAs(argc, argv); + auto settings = parseArgvAs(argc, argv); if (settings.version) { @@ -103,18 +42,19 @@ int main(int argc, const char **argv) if (settings.help) { - std::cout << getArgvHelp() << '\n'; + std::cout << getArgvHelp() << '\n'; return 0; } + + runService(settings); } catch (const std::exception &e) { - logger.fatal("Fatal error: '{}'.", e.what()); - return 1; + std::cout << "Fatal error: " << e.what() << ".\n"; } catch (...) { - logger.fatal("Unknown fatal error."); + std::cout << "Unknown fatal error."; return 1; } diff --git a/src/brayns/core/Launcher.cpp b/src/brayns/core/Launcher.cpp new file mode 100644 index 000000000..b620b3659 --- /dev/null +++ b/src/brayns/core/Launcher.cpp @@ -0,0 +1,72 @@ +/* Copyright (c) 2015-2024 EPFL/Blue Brain Project + * All rights reserved. Do not distribute without permission. + * + * Responsible Author: adrien.fleury@epfl.ch + * + * This file is part of Brayns + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Launcher.h" + +#include +#include + +namespace brayns::experimental +{ +void runService(const ServiceSettings &settings) +{ + auto level = brayns::EnumInfo::getValue(settings.logLevel); + + auto logger = createConsoleLogger("brayns"); + logger.setLevel(level); + + auto ssl = std::optional(); + + if (settings.ssl) + { + ssl = SslSettings{ + .privateKeyFile = settings.privateKeyFile, + .certificateFile = settings.certificateFile, + .caLocation = settings.caLocation, + .privateKeyPassphrase = settings.privateKeyPassphrase, + }; + } + + auto server = WebSocketServerSettings{ + .host = settings.host, + .port = settings.port, + .maxThreadCount = settings.maxThreadCount, + .maxQueueSize = settings.maxQueueSize, + .maxFrameSize = settings.maxFrameSize, + .ssl = std::move(ssl), + }; + + auto endpoints = EndpointRegistry({}); + + auto tasks = TaskManager(); + + auto context = std::make_unique(ServiceContext{ + .logger = std::move(logger), + .server = std::move(server), + .endpoints = std::move(endpoints), + .tasks = std::move(tasks), + }); + + auto service = Service(std::move(context)); + + service.run(); +} +} diff --git a/src/brayns/core/Launcher.h b/src/brayns/core/Launcher.h new file mode 100644 index 000000000..76d3f6432 --- /dev/null +++ b/src/brayns/core/Launcher.h @@ -0,0 +1,105 @@ +/* Copyright (c) 2015-2024 EPFL/Blue Brain Project + * All rights reserved. Do not distribute without permission. + * + * Responsible Author: adrien.fleury@epfl.ch + * + * This file is part of Brayns + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include +#include + +namespace brayns::experimental +{ +struct ServiceSettings +{ + bool help; + bool version; + std::string logLevel; + std::string host; + std::uint16_t port; + std::size_t maxThreadCount; + std::size_t maxQueueSize; + std::size_t maxFrameSize; + bool ssl; + std::string privateKeyFile; + std::string certificateFile; + std::string caLocation; + std::string privateKeyPassphrase; +}; + +template<> +struct ArgvSettingsReflector +{ + static auto reflect() + { + auto builder = ArgvBuilder(); + + builder.description(getCopyright()); + + builder.option("help", [](auto &settings) { return &settings.help; }) + .description("Display this help message and exit") + .defaultValue(false); + + builder.option("version", [](auto &settings) { return &settings.version; }) + .description("Display brayns copyright with version and exit") + .defaultValue(false); + + builder.option("log-level", [](auto &settings) { return &settings.logLevel; }) + .description("Log level among [trace, debug, info, warn, error, fatal]") + .defaultValue("info"); + + builder.option("host", [](auto &settings) { return &settings.host; }) + .description("Websocket server hostname, use 0.0.0.0 to allow any host to connect") + .defaultValue("localhost"); + builder.option("port", [](auto &settings) { return &settings.port; }) + .description("Websocket server port") + .defaultValue(5000); + builder.option("max-thread-count", [](auto &settings) { return &settings.maxThreadCount; }) + .description("Maximum number of threads for the websocket server") + .defaultValue(2); + builder.option("max-queue-size", [](auto &settings) { return &settings.maxQueueSize; }) + .description("Maximum number of queued connections before they are rejected") + .defaultValue(64); + builder.option("max-frame-size", [](auto &settings) { return &settings.maxFrameSize; }) + .description("Maximum frame size the websocket server accepts") + .defaultValue(std::numeric_limits::max()); + + builder.option("ssl", [](auto &settings) { return &settings.ssl; }) + .description("Enable SSL for websocket server, requires a certificate and a private key") + .defaultValue(false); + + builder.option("private-key-file", [](auto &settings) { return &settings.privateKeyFile; }) + .description("SSL private key used by the websocket server") + .defaultValue(""); + builder.option("certificate-file", [](auto &settings) { return &settings.certificateFile; }) + .description("SSL certificate the websocket server will provide to clients") + .defaultValue(""); + builder.option("ca-location", [](auto &settings) { return &settings.caLocation; }) + .description("Path to an additional certification authority (file or directory)") + .defaultValue(""); + builder.option("private-key-passphrase", [](auto &settings) { return &settings.privateKeyPassphrase; }) + .description("Passphrase for the private key if encrypted") + .defaultValue(""); + + return builder.build(); + } +}; + +void runService(const ServiceSettings &settings); +} diff --git a/src/brayns/core/api/Task.cpp b/src/brayns/core/api/Task.cpp index 6e12c8ea9..73a67f390 100644 --- a/src/brayns/core/api/Task.cpp +++ b/src/brayns/core/api/Task.cpp @@ -44,6 +44,14 @@ const RawTask &getTask(const std::map &tasks, TaskId id) namespace brayns::experimental { +TaskManager::~TaskManager() +{ + for (const auto &[id, task] : _tasks) + { + task.cancel(); + } +} + std::vector TaskManager::getTasks() const { auto infos = std::vector(); diff --git a/src/brayns/core/api/Task.h b/src/brayns/core/api/Task.h index 9f4a64999..c1dbdd4c8 100644 --- a/src/brayns/core/api/Task.h +++ b/src/brayns/core/api/Task.h @@ -72,6 +72,14 @@ struct TaskInfo class TaskManager { public: + explicit TaskManager() = default; + ~TaskManager(); + + TaskManager(const TaskManager &) = delete; + TaskManager(TaskManager &&) = default; + TaskManager &operator=(const TaskManager &) = delete; + TaskManager &operator=(TaskManager &&) = default; + std::vector getTasks() const; TaskId add(RawTask task); ProgressInfo getProgress(TaskId id) const; diff --git a/src/brayns/core/jsonrpc/Parser.cpp b/src/brayns/core/jsonrpc/Parser.cpp index 95d609b96..48c363010 100644 --- a/src/brayns/core/jsonrpc/Parser.cpp +++ b/src/brayns/core/jsonrpc/Parser.cpp @@ -55,7 +55,7 @@ JsonRpcRequest parseJsonRpcRequest(const std::string &text) return deserializeAs(json); } -JsonRpcRequest parseBinaryJsonRpcRequest(std::string binary) +JsonRpcRequest parseBinaryJsonRpcRequest(const std::string &binary) { auto data = std::string_view(binary); @@ -75,9 +75,7 @@ JsonRpcRequest parseBinaryJsonRpcRequest(std::string binary) auto request = parseJsonRpcRequest(std::string(text)); - binary.erase(0, 4 + textSize); - - request.binary = std::move(binary); + request.binary = binary.substr(4 + textSize); return request; } diff --git a/src/brayns/core/jsonrpc/Parser.h b/src/brayns/core/jsonrpc/Parser.h index 6d9bf07dc..92b219ec1 100644 --- a/src/brayns/core/jsonrpc/Parser.h +++ b/src/brayns/core/jsonrpc/Parser.h @@ -29,7 +29,7 @@ namespace brayns::experimental { JsonRpcRequest parseJsonRpcRequest(const std::string &text); -JsonRpcRequest parseBinaryJsonRpcRequest(std::string binary); +JsonRpcRequest parseBinaryJsonRpcRequest(const std::string &binary); std::string composeAsText(const JsonRpcResponse &response); std::string composeAsBinary(const JsonRpcResponse &response); std::string composeError(const JsonRpcErrorResponse &response); diff --git a/src/brayns/core/service/RequestHandler.cpp b/src/brayns/core/service/RequestHandler.cpp index 5be253fdc..9849b254c 100644 --- a/src/brayns/core/service/RequestHandler.cpp +++ b/src/brayns/core/service/RequestHandler.cpp @@ -108,18 +108,19 @@ RawResult getResult(RawTask task, const Endpoint &endpoint, TaskManager &tasks, { if (!endpoint.schema.async) { - logger.info("Request handled synchronously"); + logger.info("Result already available as request is not async"); return task.wait(); } auto id = tasks.add(std::move(task)); + logger.info("Task created with ID {}", id); return {serializeToJson(TaskResult{id})}; } RawResponse executeRequest(JsonRpcRequest request, const EndpointRegistry &endpoints, TaskManager &tasks, Logger &logger) { - logger.info("Looking for an endpoint '{}'", request.method); + logger.info("Searching endpoint '{}'", request.method); const auto &endpoint = findEndpoint(request, endpoints); logger.info("Endpoint found"); @@ -181,9 +182,9 @@ RequestHandler::RequestHandler(const EndpointRegistry &endpoints, TaskManager &t { } -void RequestHandler::handle(RawRequest request) +void RequestHandler::handle(const RawRequest &request) { - auto jsonRpcRequest = tryParseRequest(std::move(request), *_logger); + auto jsonRpcRequest = tryParseRequest(request, *_logger); if (!jsonRpcRequest) { diff --git a/src/brayns/core/service/RequestHandler.h b/src/brayns/core/service/RequestHandler.h index cefe5b73a..add89657f 100644 --- a/src/brayns/core/service/RequestHandler.h +++ b/src/brayns/core/service/RequestHandler.h @@ -32,7 +32,7 @@ class RequestHandler public: explicit RequestHandler(const EndpointRegistry &endpoints, TaskManager &tasks, Logger &logger); - void handle(RawRequest request); + void handle(const RawRequest &request); private: const EndpointRegistry *_endpoints; diff --git a/src/brayns/core/service/Service.cpp b/src/brayns/core/service/Service.cpp new file mode 100644 index 000000000..1b44810dd --- /dev/null +++ b/src/brayns/core/service/Service.cpp @@ -0,0 +1,64 @@ +/* Copyright (c) 2015-2024 EPFL/Blue Brain Project + * All rights reserved. Do not distribute without permission. + * + * Responsible Author: adrien.fleury@epfl.ch + * + * This file is part of Brayns + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Service.h" + +#include "RequestHandler.h" + +namespace brayns::experimental +{ +bool StopToken::isStopped() const +{ + return _stopped; +} + +void StopToken::stop() +{ + _stopped = true; +} + +Service::Service(std::unique_ptr context): + _context(std::move(context)) +{ +} + +void Service::run() +{ + auto handler = RequestHandler(_context->endpoints, _context->tasks, _context->logger); + + auto server = startServer(_context->server, _context->logger); + + while (true) + { + auto requests = server.waitForRequests(); + + for (const auto &request : requests) + { + handler.handle(request); + + if (_context->token.isStopped()) + { + return; + } + } + } +} +} diff --git a/src/brayns/core/service/Service.h b/src/brayns/core/service/Service.h new file mode 100644 index 000000000..195efcb86 --- /dev/null +++ b/src/brayns/core/service/Service.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2015-2024 EPFL/Blue Brain Project + * All rights reserved. Do not distribute without permission. + * + * Responsible Author: adrien.fleury@epfl.ch + * + * This file is part of Brayns + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include +#include +#include +#include + +namespace brayns::experimental +{ +class StopToken +{ +public: + bool isStopped() const; + void stop(); + +private: + bool _stopped = false; +}; + +struct ServiceContext +{ + Logger logger; + WebSocketServerSettings server; + EndpointRegistry endpoints; + TaskManager tasks; + StopToken token = {}; +}; + +class Service +{ +public: + explicit Service(std::unique_ptr context); + + void run(); + +private: + std::unique_ptr _context; +}; +} diff --git a/src/brayns/core/utils/EnumReflector.h b/src/brayns/core/utils/EnumReflector.h index 9037631bb..c5c4ca120 100644 --- a/src/brayns/core/utils/EnumReflector.h +++ b/src/brayns/core/utils/EnumReflector.h @@ -98,7 +98,7 @@ template struct EnumReflector; template -concept ReflectedEnum = std::same_as::reflect()), EnumInfo>; +concept ReflectedEnum = std::same_as, decltype(EnumReflector::reflect())>; template const EnumInfo &reflectEnum() diff --git a/src/brayns/core/websocket/WebSocketHandler.cpp b/src/brayns/core/websocket/WebSocketHandler.cpp index 687d279b4..5eedc599d 100644 --- a/src/brayns/core/websocket/WebSocketHandler.cpp +++ b/src/brayns/core/websocket/WebSocketHandler.cpp @@ -209,10 +209,9 @@ void WebSocketHandler::handle(WebSocket &websocket) { auto clientId = _clientIds.next(); - _logger->info("New client connected with ID {}", clientId); - try { + _logger->info("New client connected with ID {}", clientId); runClientLoop(clientId, websocket, *_requests, *_logger); } catch (const WebSocketClosed &e) diff --git a/src/brayns/core/websocket/WebSocketServer.cpp b/src/brayns/core/websocket/WebSocketServer.cpp index af4974710..5c2a44e4f 100644 --- a/src/brayns/core/websocket/WebSocketServer.cpp +++ b/src/brayns/core/websocket/WebSocketServer.cpp @@ -214,7 +214,7 @@ Poco::Net::HTTPServerParams::Ptr extractServerParams(const WebSocketServerSettin auto params = Poco::makeAuto(); params->setMaxThreads(static_cast(settings.maxThreadCount)); - params->setMaxQueued(static_cast(settings.queueSize)); + params->setMaxQueued(static_cast(settings.maxQueueSize)); return params; } @@ -236,7 +236,7 @@ WebSocketServer::~WebSocketServer() } } -std::vector WebSocketServer::waitForRequest() +std::vector WebSocketServer::waitForRequests() { return _requests->wait(); } diff --git a/src/brayns/core/websocket/WebSocketServer.h b/src/brayns/core/websocket/WebSocketServer.h index 0b864da20..41e1c998f 100644 --- a/src/brayns/core/websocket/WebSocketServer.h +++ b/src/brayns/core/websocket/WebSocketServer.h @@ -47,7 +47,7 @@ struct WebSocketServerSettings std::string host = "localhost"; std::uint16_t port = 5000; std::size_t maxThreadCount = 1; - std::size_t queueSize = 0; + std::size_t maxQueueSize = 64; std::size_t maxFrameSize = std::numeric_limits::max(); std::optional ssl = std::nullopt; }; @@ -63,7 +63,7 @@ class WebSocketServer WebSocketServer &operator=(const WebSocketServer &) = delete; WebSocketServer &operator=(WebSocketServer &&) = default; - std::vector waitForRequest(); + std::vector waitForRequests(); private: std::unique_ptr _server;