From 3fe6d254486f15bf60a99f378b79514e97abf8f7 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 13 Dec 2023 17:33:11 -0300 Subject: [PATCH] a bit better way to treah handlers --- CMakeLists.txt | 1 + handler/CMakeLists.txt | 13 +++++++ handler/env.cpp | 17 +++++++++ handler/env.h | 16 ++++++++ handler/handler.cpp | 11 ++++++ handler/handler.h | 26 +++++++++++++ handler/info.cpp | 18 +++++++++ handler/info.h | 17 +++++++++ request/request.cpp | 83 +++++++++++++++++++++++++++++++++++------- request/request.h | 23 ++++++++++-- response/response.cpp | 12 ++++-- response/response.h | 7 ++-- server/router.cpp | 73 +++++++++++++++++++++++++++---------- server/router.h | 14 ++++--- server/server.cpp | 52 +++++--------------------- server/server.h | 3 -- 16 files changed, 291 insertions(+), 95 deletions(-) create mode 100644 handler/CMakeLists.txt create mode 100644 handler/env.cpp create mode 100644 handler/env.h create mode 100644 handler/handler.cpp create mode 100644 handler/handler.h create mode 100644 handler/info.cpp create mode 100644 handler/info.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d90451..bca2822 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS}) add_subdirectory(server) +add_subdirectory(handler) add_subdirectory(request) add_subdirectory(response) add_subdirectory(stream) diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt new file mode 100644 index 0000000..a3dd084 --- /dev/null +++ b/handler/CMakeLists.txt @@ -0,0 +1,13 @@ +set(HEADERS + handler.h + info.h + env.h +) + +set(SOURCES + handler.cpp + info.cpp + env.cpp +) + +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/env.cpp b/handler/env.cpp new file mode 100644 index 0000000..a615e17 --- /dev/null +++ b/handler/env.cpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "env.h" + +Handler::Env::Env(): + Handler("env", Request::Method::get) +{} + +void Handler::Env::handle(Request& request) { + nlohmann::json body = nlohmann::json::object(); + request.printEnvironment(body); + + Response res(request); + res.setBody(body); + res.send(); +} diff --git a/handler/env.h b/handler/env.h new file mode 100644 index 0000000..b4a5ea4 --- /dev/null +++ b/handler/env.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "handler.h" + +namespace Handler { + +class Env : public Handler::Handler { +public: + Env(); + virtual void handle(Request& request); + +}; +} diff --git a/handler/handler.cpp b/handler/handler.cpp new file mode 100644 index 0000000..e0c39c1 --- /dev/null +++ b/handler/handler.cpp @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "handler.h" + +Handler::Handler::Handler(const std::string& path, Request::Method method): + path(path), + method(method) +{} + +Handler::Handler::~Handler() {} diff --git a/handler/handler.h b/handler/handler.h new file mode 100644 index 0000000..4e682c4 --- /dev/null +++ b/handler/handler.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "request/request.h" +#include "response/response.h" + +namespace Handler { + +class Handler { +protected: + Handler(const std::string& path, Request::Method method); + +public: + virtual ~Handler(); + + virtual void handle(Request& request) = 0; + + const std::string path; + const Request::Method method; +}; +} diff --git a/handler/info.cpp b/handler/info.cpp new file mode 100644 index 0000000..ba2dfec --- /dev/null +++ b/handler/info.cpp @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "info.h" + +Handler::Info::Info(): + Handler("info", Request::Method::get) +{} + +void Handler::Info::handle(Request& request) { + Response res(request); + nlohmann::json body = nlohmann::json::object(); + body["type"] = PROJECT_NAME; + body["version"] = PROJECT_VERSION; + + res.setBody(body); + res.send(); +} diff --git a/handler/info.h b/handler/info.h new file mode 100644 index 0000000..b1adc12 --- /dev/null +++ b/handler/info.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "handler.h" +#include "config.h" + +namespace Handler { + +class Info : public Handler { +public: + Info(); + void handle(Request& request) override; +}; + +} diff --git a/request/request.cpp b/request/request.cpp index 53fb88b..48e084b 100644 --- a/request/request.cpp +++ b/request/request.cpp @@ -3,6 +3,8 @@ #include "request.h" +#include "response/response.h" + constexpr static const char* GET("GET"); constexpr static const char* REQUEST_METHOD("REQUEST_METHOD"); @@ -16,9 +18,18 @@ constexpr static const char* SERVER_NAME("SERVER_NAME"); // // constexpr static const char* SCRIPT_NAME("SCRIPT_NAME"); +constexpr std::array< + std::pair, + static_cast(Request::Method::unknown) +> methods = {{ + {"GET", Request::Method::get}, + {"POST", Request::Method::post} +}}; + Request::Request (): state(State::initial), - raw() + raw(), + response(nullptr) {} Request::~Request() { @@ -36,12 +47,17 @@ void Request::terminate() { } } -bool Request::isGet() const { - if (state != State::accepted) - throw std::runtime_error("An attempt to read request type on a wrong request state"); +Request::Method Request::method() const { + if (state == State::initial) + throw std::runtime_error("An attempt to read request method on not accepted request"); std::string_view method(FCGX_GetParam(REQUEST_METHOD, raw.envp)); - return method == GET; + for (const auto& pair : methods) { + if (pair.first == method) + return pair.second; + } + + return Request::Method::unknown; } bool Request::wait(int socketDescriptor) { @@ -59,20 +75,61 @@ bool Request::wait(int socketDescriptor) { return result; } -OStream Request::getOutputStream() { - if (state != State::accepted) - throw std::runtime_error("An attempt to request output stream on a wrong request state"); - +OStream Request::getOutputStream(const Response* response) { + validateResponse(response); return OStream(raw.out); } -OStream Request::getErrorStream() { - if (state != State::accepted) - throw std::runtime_error("An attempt to request error stream on a wrong request state"); - +OStream Request::getErrorStream(const Response* response) { + validateResponse(response); return OStream(raw.err); } +void Request::responseIsComplete(const Response* response) { + switch (state) { + case State::initial: + throw std::runtime_error("An attempt to mark the request as complete, but it wasn't even accepted yet"); + break; + case State::accepted: + throw std::runtime_error("An attempt to mark the request as complete, but it wasn't responded"); + break; + case State::responding: + if (Request::response != response) + throw std::runtime_error("An attempt to mark the request as complete by the different response who actually started responding"); + + Request::response = nullptr; + state = State::responded; + break; + case State::responded: + throw std::runtime_error("An attempt to mark the request as a complete for the second time"); + break; + } +} + +void Request::validateResponse(const Response* response) { + switch (state) { + case State::initial: + throw std::runtime_error("An attempt to request stream while the request wasn't even accepted yet"); + break; + case State::accepted: + Request::response = response; + state = State::responding; + break; + case State::responding: + if (Request::response != response) + throw std::runtime_error("Error handling a request: first time one response started replying, then another continued"); + + break; + case State::responded: + throw std::runtime_error("An attempt to request stream on a request that was already done responding"); + break; + } +} + +Request::State Request::currentState() const { + return state; +} + std::string Request::getPath(const std::string& serverName) const { if (state != State::accepted) throw std::runtime_error("An attempt to request path on a wrong request state"); diff --git a/request/request.h b/request/request.h index 2fc9a32..9871106 100644 --- a/request/request.h +++ b/request/request.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include @@ -13,14 +15,22 @@ #include "stream/ostream.h" +class Response; class Request { public: enum class State { initial, accepted, + responding, responded }; + enum class Method { + get, + post, + unknown + }; + Request(); ~Request(); Request(const Request& other) = delete; @@ -30,17 +40,24 @@ public: bool wait(int socketDescriptor); void terminate(); - bool isGet() const; - OStream getOutputStream(); - OStream getErrorStream(); + Method method() const; + State currentState() const; + + OStream getOutputStream(const Response* response); + OStream getErrorStream(const Response* response); + void responseIsComplete(const Response* response); std::string getPath(const std::string& serverName) const; std::string getServerName() const; void printEnvironment(std::ostream& out); void printEnvironment(nlohmann::json& out); +private: + void validateResponse(const Response* response); + private: State state; FCGX_Request raw; + const Response* response; }; diff --git a/response/response.cpp b/response/response.cpp index 17be265..7d3d394 100644 --- a/response/response.cpp +++ b/response/response.cpp @@ -15,23 +15,25 @@ constexpr std::array(Response::ContentTyp "Content-type: application/json" }; -Response::Response(): +Response::Response(Request& request): + request(request), status(Status::ok), type(ContentType::text), body() {} -Response::Response(Status status): +Response::Response(Request& request, Status status): + request(request), status(status), type(ContentType::text), body() {} -void Response::replyTo(Request& request) const { +void Response::send() const { // OStream out = status == Status::ok ? // request.getOutputStream() : // request.getErrorStream(); - OStream out = request.getOutputStream(); + OStream out = request.getOutputStream(this); out << statusCodes[static_cast(status)]; if (!body.empty()) @@ -40,6 +42,8 @@ void Response::replyTo(Request& request) const { << '\n' << '\n' << body; + + request.responseIsComplete(this); } void Response::setBody(const std::string& body) { diff --git a/response/response.h b/response/response.h index 7cf0d59..946ecbb 100644 --- a/response/response.h +++ b/response/response.h @@ -27,14 +27,15 @@ public: json, __size }; - Response(); - Response(Status status); + Response(Request& request); + Response(Request& request, Status status); - void replyTo(Request& request) const; + void send() const; void setBody(const std::string& body); void setBody(const nlohmann::json& body); private: + Request& request; Status status; ContentType type; std::string body; diff --git a/server/router.cpp b/server/router.cpp index 24bb402..75fe696 100644 --- a/server/router.cpp +++ b/server/router.cpp @@ -4,41 +4,74 @@ #include "router.h" Router::Router(): - table() -{ + get(), + post() +{} -} +void Router::addRoute(Handler handler) { + std::pair::const_iterator, bool> result; + switch (handler->method) { + case Request::Method::get: + result = get.emplace(handler->path, std::move(handler)); + break; + case Request::Method::post: + result = post.emplace(handler->path, std::move(handler)); + break; + default: + throw std::runtime_error("An attempt to register handler with unsupported method type: " + std::to_string((int)handler->method)); + } -void Router::addRoute(const std::string& path, const Handler& handler) { - auto result = table.emplace(path, handler); if (!result.second) - std::cerr << "could'not add route " + path + " to the routing table"; + throw std::runtime_error("could'not add route " + handler->path + " to the routing table"); } -void Router::route(const std::string& path, std::unique_ptr request, Server* server) { - auto itr = table.find(path); - if (itr == table.end()) +void Router::route(const std::string& path, std::unique_ptr request) { + std::map::const_iterator itr, end; + switch (request->method()) { + case Request::Method::get: + itr = get.find(path); + end = get.end(); + break; + case Request::Method::post: + itr = post.find(path); + end = post.end(); + break; + default: + return handleMethodNotAllowed(path, std::move(request)); + } + + if (itr == end) return handleNotFound(path, std::move(request)); try { - bool result = itr->second(request.get(), server); - if (!result) - handleInternalError(std::runtime_error("handler failed to handle the request"), std::move(request)); + itr->second->handle(*request.get()); + + if (request->currentState() != Request::State::responded) + handleInternalError(path, std::runtime_error("handler failed to handle the request"), std::move(request)); + else + std::cout << "Success:\t" << path << std::endl; } catch (const std::exception& e) { - handleInternalError(e, std::move(request)); + handleInternalError(path, e, std::move(request)); } } void Router::handleNotFound(const std::string& path, std::unique_ptr request) { - Response notFound(Response::Status::notFound); + Response notFound(*request.get(), Response::Status::notFound); notFound.setBody(std::string("Path \"") + path + "\" was not found"); - notFound.replyTo(*request.get()); - std::cerr << "Not found: " << path << std::endl; + notFound.send(); + std::cerr << "Not found:\t" << path << std::endl; } -void Router::handleInternalError(const std::exception& exception, std::unique_ptr request) { - Response error(Response::Status::internalError); +void Router::handleInternalError(const std::string& path, const std::exception& exception, std::unique_ptr request) { + Response error(*request.get(), Response::Status::internalError); error.setBody(std::string(exception.what())); - error.replyTo(*request.get()); - std::cerr << "Internal error: " << exception.what() << std::endl; + error.send(); + std::cerr << "Internal error:\t" << path << "\n\t" << exception.what() << std::endl; +} + +void Router::handleMethodNotAllowed(const std::string& path, std::unique_ptr request) { + Response error(*request.get(), Response::Status::methodNotAllowed); + error.setBody(std::string("Method not allowed")); + error.send(); + std::cerr << "Method not allowed:\t" << path << std::endl; } diff --git a/server/router.h b/server/router.h index c423282..95fa8db 100644 --- a/server/router.h +++ b/server/router.h @@ -10,23 +10,25 @@ #include "request/request.h" #include "response/response.h" +#include "handler/handler.h" class Server; class Router { + using Handler = std::unique_ptr; public: - using Handler = std::function; - Router(); - void addRoute(const std::string& path, const Handler& handler); - void route(const std::string& path, std::unique_ptr request, Server* server); + void addRoute(Handler handler); + void route(const std::string& path, std::unique_ptr request); private: void handleNotFound(const std::string& path, std::unique_ptr request); - void handleInternalError(const std::exception& exception, std::unique_ptr request); + void handleInternalError(const std::string& path, const std::exception& exception, std::unique_ptr request); + void handleMethodNotAllowed(const std::string& path, std::unique_ptr request); private: - std::map table; + std::map get; + std::map post; }; diff --git a/server/server.cpp b/server/server.cpp index f08b408..744c27e 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -3,6 +3,9 @@ #include "server.h" +#include "handler/info.h" +#include "handler/env.h" + constexpr uint8_t currentDbVesion = 1; Server::Server(): @@ -23,8 +26,8 @@ Server::Server(): db->connect("/run/mysqld/mysqld.sock"); db->migrate(currentDbVesion); - router.addRoute("info", Server::info); - router.addRoute("env", Server::printEnvironment); + router.addRoute(std::make_unique()); + router.addRoute(std::make_unique()); } Server::~Server() {} @@ -49,49 +52,12 @@ void Server::handleRequest(std::unique_ptr request) { std::cout << "received server name " << serverName.value() << std::endl; } catch (...) { std::cerr << "failed to read server name" << std::endl; - Response error(Response::Status::internalError); - error.replyTo(*request.get()); + Response error(*request.get(), Response::Status::internalError); + error.send(); return; } } - if (!request->isGet()) { - static const Response methodNotAllowed(Response::Status::methodNotAllowed); - methodNotAllowed.replyTo(*request.get()); - return; - } - - try { - std::string path = request->getPath(serverName.value()); - router.route(path.data(), std::move(request), this); - } catch (const std::exception& e) { - Response error(Response::Status::internalError); - error.setBody(std::string(e.what())); - error.replyTo(*request.get()); - } -} - -bool Server::printEnvironment(Request* request, Server* server) { - UNUSED(server); - nlohmann::json body = nlohmann::json::object(); - request->printEnvironment(body); - - Response res; - res.setBody(body); - res.replyTo(*request); - - return true; -} - -bool Server::info(Request* request, Server* server) { - UNUSED(server); - Response res; - nlohmann::json body = nlohmann::json::object(); - body["type"] = PROJECT_NAME; - body["version"] = PROJECT_VERSION; - - res.setBody(body); - res.replyTo(*request); - - return true; + std::string path = request->getPath(serverName.value()); + router.route(path.data(), std::move(request)); } diff --git a/server/server.h b/server/server.h index f13df21..13d17d0 100644 --- a/server/server.h +++ b/server/server.h @@ -34,9 +34,6 @@ public: private: void handleRequest(std::unique_ptr request); - static bool info(Request* request, Server* server); - static bool printEnvironment(Request* request, Server* server); - private: bool terminating; uint64_t requestCount;