a bit better way to treah handlers

This commit is contained in:
Blue 2023-12-13 17:33:11 -03:00
parent f0d205dee7
commit 3fe6d25448
Signed by: blue
GPG Key ID: 9B203B252A63EE38
16 changed files with 291 additions and 95 deletions

View File

@ -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)

13
handler/CMakeLists.txt Normal file
View File

@ -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})

17
handler/env.cpp Normal file
View File

@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
// 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();
}

16
handler/env.h Normal file
View File

@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
// 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);
};
}

11
handler/handler.cpp Normal file
View File

@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
// 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() {}

26
handler/handler.h Normal file
View File

@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <string>
#include <memory>
#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;
};
}

18
handler/info.cpp Normal file
View File

@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
// 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();
}

17
handler/info.h Normal file
View File

@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
// 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;
};
}

View File

@ -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<std::string_view, Request::Method>,
static_cast<uint8_t>(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");

View File

@ -6,6 +6,8 @@
#include <memory>
#include <stdexcept>
#include <string_view>
#include <array>
#include <map>
#include <fcgiapp.h>
@ -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;
};

View File

@ -15,23 +15,25 @@ constexpr std::array<std::string_view, static_cast<uint8_t>(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<uint8_t>(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) {

View File

@ -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;

View File

@ -4,41 +4,74 @@
#include "router.h"
Router::Router():
table()
{
get(),
post()
{}
}
void Router::addRoute(Handler handler) {
std::pair<std::map<std::string, Handler>::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> request, Server* server) {
auto itr = table.find(path);
if (itr == table.end())
void Router::route(const std::string& path, std::unique_ptr<Request> request) {
std::map<std::string, Handler>::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> 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> request) {
Response error(Response::Status::internalError);
void Router::handleInternalError(const std::string& path, const std::exception& exception, std::unique_ptr<Request> 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> 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;
}

View File

@ -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<Handler::Handler>;
public:
using Handler = std::function<bool(Request*, Server*)>;
Router();
void addRoute(const std::string& path, const Handler& handler);
void route(const std::string& path, std::unique_ptr<Request> request, Server* server);
void addRoute(Handler handler);
void route(const std::string& path, std::unique_ptr<Request> request);
private:
void handleNotFound(const std::string& path, std::unique_ptr<Request> request);
void handleInternalError(const std::exception& exception, std::unique_ptr<Request> request);
void handleInternalError(const std::string& path, const std::exception& exception, std::unique_ptr<Request> request);
void handleMethodNotAllowed(const std::string& path, std::unique_ptr<Request> request);
private:
std::map<std::string, Handler> table;
std::map<std::string, Handler> get;
std::map<std::string, Handler> post;
};

View File

@ -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<Handler::Info>());
router.addRoute(std::make_unique<Handler::Env>());
}
Server::~Server() {}
@ -49,49 +52,12 @@ void Server::handleRequest(std::unique_ptr<Request> 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;
router.route(path.data(), std::move(request));
}

View File

@ -34,9 +34,6 @@ public:
private:
void handleRequest(std::unique_ptr<Request> request);
static bool info(Request* request, Server* server);
static bool printEnvironment(Request* request, Server* server);
private:
bool terminating;
uint64_t requestCount;