a bit better way to treah handlers
This commit is contained in:
parent
f0d205dee7
commit
3fe6d25448
@ -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
13
handler/CMakeLists.txt
Normal 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
17
handler/env.cpp
Normal 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
16
handler/env.h
Normal 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
11
handler/handler.cpp
Normal 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
26
handler/handler.h
Normal 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
18
handler/info.cpp
Normal 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
17
handler/info.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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");
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
@ -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;
|
||||
std::string path = request->getPath(serverName.value());
|
||||
router.route(path.data(), std::move(request));
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user