This commit is contained in:
Maurice Grönwoldt 2022-08-23 16:40:57 +02:00
parent 792abbee93
commit 638276d8e0
12 changed files with 299 additions and 27 deletions

View file

@ -17,7 +17,7 @@ set(SOURCE_FILES
Source/StringUtils.cpp Source/StringUtils.cpp
Source/Cookie.cpp Source/Cookie.cpp
Source/Session.cpp Source/Session.cpp
Source/Response.cpp) Source/Response.cpp Source/InbuildMiddleWare.cpp)
include_directories(${CMAKE_SOURCE_DIR}/) include_directories(${CMAKE_SOURCE_DIR}/)
add_library(VWeb ${SOURCE_FILES}) add_library(VWeb ${SOURCE_FILES})

View file

@ -0,0 +1,121 @@
#include "StringUtils.h"
#include "VWeb.h"
#include <random>
#include <sstream>
namespace VWeb {
#pragma region AUTH
PreMiddleWareReturn AuthWare::PreHandle(Request &request) {
if (m_AuthFunction)
return m_AuthFunction(request);
return {};
}
#pragma endregion AUTH
#pragma region SESSION
static std::string GenerateSID() {
static std::random_device dev;
static std::mt19937 rng(dev());
std::uniform_int_distribution<int> dist(0, 15);
const char *v = "0123456789abcdef";
const bool dashArray[] = {false, false, false, false, true, false,
true, false, true, false, true, false,
false, false, false, false};
std::stringstream res;
for (bool dash : dashArray) {
if (dash)
res << "-";
res << v[dist(rng)];
res << v[dist(rng)];
}
return res.str();
}
SessionManager::SessionManager() {
m_GCThread = CreateRef<std::thread>([this]() {
while (m_IsRunning) {
if (m_Counter == 59) {
GC();
m_Counter = -1;
}
m_Counter++;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
});
}
SessionManager::~SessionManager() {
m_IsRunning = false;
m_GCThread->join();
}
PreMiddleWareReturn SessionManager::PreHandle(Request &request) {
auto &cookies = request.CookieData;
if (!cookies->Has("sid"))
return {};
auto &cookie = cookies->Get("sid");
std::lock_guard<std::mutex> lck(m_Mutex);
if (!m_Sessions.contains(cookie.Value))
return {};
auto &session = m_Sessions[cookie.Value];
session->Update();
request.SessionData = session;
return {};
}
bool SessionManager::PostHandle(const Request &request, Response &response) {
if (response.SessionData->Id.empty() &&
response.SessionData->ContainsData()) {
response.SessionData->Update();
{
std::lock_guard<std::mutex> lck(m_Mutex);
response.SessionData->Id = GenerateSID();
while (m_Sessions.contains(response.SessionData->Id))
response.SessionData->Id = GenerateSID();
}
m_Sessions[response.SessionData->Id] = response.SessionData;
auto &sidCookie = response.CookieData->Get("sid");
sidCookie.HttpOnly = true;
sidCookie.Secure = true;
sidCookie.Value = response.SessionData->Id;
}
return true;
}
void SessionManager::GC() {
std::lock_guard<std::mutex> lck(m_Mutex);
std::vector<std::string_view> mark_as_delete;
for (auto &itemToCollect : m_Sessions) {
if (!itemToCollect.second->IsValid())
mark_as_delete.push_back(itemToCollect.first);
}
for (auto &i : mark_as_delete)
m_Sessions.erase(i.data());
}
#pragma endregion SESSION
#pragma region COOKIES
PreMiddleWareReturn CookieManager::PreHandle(Request &request) {
auto &cookieHeaders = request.Header("Cookie");
auto &cookies = request.CookieData;
if (cookieHeaders.Size() > 0) {
auto &values = cookieHeaders.Values();
for (auto &rawCookie : cookieHeaders.Values()) {
auto splitCookies = String::Split(rawCookie, ";");
for (auto &cookie : splitCookies) {
auto split = String::Split(cookie, "=");
String::Trim(split[0]);
String::Trim(split[1]);
cookies->CreateOld(split[0], split[1]);
}
}
}
return {};
}
#pragma endregion COOKIES
} // namespace VWeb

View file

@ -1,15 +1,21 @@
#include <VWeb.h> #include <VWeb.h>
namespace VWeb { namespace VWeb {
void MiddleWareHandler::HandlePre(Ref<Request> &request) { std::optional<Ref<Response>>
MiddleWareHandler::HandlePre(Ref<Request> &request) {
for (auto &[key, middleWare] : m_MiddleWares) { for (auto &[key, middleWare] : m_MiddleWares) {
middleWare->PreHandle(*request); auto data = middleWare->PreHandle(*request);
if (data.has_value())
return data;
} }
return {};
} }
void MiddleWareHandler::HandlePost(Ref<Request> &request, void MiddleWareHandler::HandlePost(Ref<Request> &request,
Ref<Response> &response) { Ref<Response> &response) {
for (auto &[key, middleWare] : m_MiddleWares) { for (auto &[key, middleWare] : m_MiddleWares) {
middleWare->PostHandle(*request, *response); if (!middleWare->PostHandle(*request, *response)) {
break;
}
} }
} }
void MiddleWareHandler::Shutdown(Ref<Request> &request, void MiddleWareHandler::Shutdown(Ref<Request> &request,

View file

@ -55,8 +55,15 @@ struct RequestJob : public WorkerJob {
} }
MRequest->CookieData = CreateRef<Cookies>(); MRequest->CookieData = CreateRef<Cookies>();
MRequest->SessionData = CreateRef<Session>(); MRequest->SessionData = CreateRef<Session>();
MMiddleWareHandler->HandlePre(MRequest); Ref<Response> response;
auto response = MRouter->HandleRoute(MRequest); auto preValue = MMiddleWareHandler->HandlePre(MRequest);
if (preValue.has_value()) {
response = preValue.value();
response->SessionData = MRequest->SessionData;
response->CookieData = MRequest->CookieData;
} else {
response = MRouter->HandleRoute(MRequest);
}
MMiddleWareHandler->HandlePost(MRequest, response); MMiddleWareHandler->HandlePost(MRequest, response);
auto content = response->GetResponse(); auto content = response->GetResponse();
MRequestHandler->AddSendResponse( MRequestHandler->AddSendResponse(

View file

@ -65,6 +65,13 @@ static std::unordered_map<HttpStatusCode, std::string> s_HTTPCodeToString = {
{HttpStatusCode::NetworkAuthenticationRequired,"511 Network Authentication Required"} {HttpStatusCode::NetworkAuthenticationRequired,"511 Network Authentication Required"}
}; };
// clang-format on // clang-format on
Ref<Response> Response::FromCode(HttpStatusCode code) {
auto response = CreateRef<Response>();
response->SetStatus(code);
return response;
}
std::string Response::GetResponse() { std::string Response::GetResponse() {
std::string content = m_Content.str(); std::string content = m_Content.str();
auto headData = TransformHeaders(content); auto headData = TransformHeaders(content);

View file

@ -80,4 +80,8 @@ bool Route::SupportsMethod(const Request &request) {
std::find(m_AllowedMethods.begin(), m_AllowedMethods.end(), std::find(m_AllowedMethods.begin(), m_AllowedMethods.end(),
request.Method) != m_AllowedMethods.end(); request.Method) != m_AllowedMethods.end();
} }
void Route::AllowMethod(HttpMethod method) {
if (std::find(m_AllowedMethods.begin(), m_AllowedMethods.end(), method) == m_AllowedMethods.end())
m_AllowedMethods.push_back(method);
}
} // namespace VWeb } // namespace VWeb

View file

@ -2,6 +2,7 @@
#include <VWeb.h> #include <VWeb.h>
#include <type_traits> #include <type_traits>
#include <utility>
namespace VWeb { namespace VWeb {
@ -20,6 +21,33 @@ public:
} }
}; };
class InstanceHandleRoute : public Route {
public:
bool Get(const Request &request, Response &response) override {
return GetFunc && GetFunc(request, response);
}
bool Post(const Request &request, Response &response) override {
return PostFunc && PostFunc(request, response);
}
bool Put(const Request &request, Response &response) override {
return PutFunc && PutFunc(request, response);
}
bool Patch(const Request &request, Response &response) override {
return PatchFunc && PatchFunc(request, response);
}
bool Delete(const Request &request, Response &response) override {
return DeleteFunc && DeleteFunc(request, response);
}
protected:
RouteFunction GetFunc{nullptr};
RouteFunction PostFunc{nullptr};
RouteFunction PutFunc{nullptr};
RouteFunction PatchFunc{nullptr};
RouteFunction DeleteFunc{nullptr};
friend Router;
};
Router::Router() { m_Routes["@"] = CreateRef<ErrorRoute>(); } Router::Router() { m_Routes["@"] = CreateRef<ErrorRoute>(); }
void Router::AddRoute(const std::string &name, const Ref<Route> &route) { void Router::AddRoute(const std::string &name, const Ref<Route> &route) {
@ -38,6 +66,7 @@ Ref<Response> Router::HandleRoute(Ref<Request> &request) {
auto response = CreateRef<Response>(); auto response = CreateRef<Response>();
auto route = FindRoute(request); auto route = FindRoute(request);
response->CookieData = request->CookieData; response->CookieData = request->CookieData;
response->SessionData = request->SessionData;
response->Method = request->Method; response->Method = request->Method;
if (!route) { if (!route) {
response->SetStatus(HttpStatusCode::NotFound); response->SetStatus(HttpStatusCode::NotFound);
@ -94,4 +123,38 @@ void Router::AddToArgs(Ref<Request> &request, Vector<std::string> &items) {
request->URLParameters.push_back(items[items.size() - 1]); request->URLParameters.push_back(items[items.size() - 1]);
items.pop_back(); items.pop_back();
} }
InstanceHandleRoute* GetOrNull(const std::string& path, Router* router) {
InstanceHandleRoute* route;
if (!router->m_Routes.contains(path)) {
router->AddRoute(path, CreateRef<InstanceHandleRoute>());
}
route = dynamic_cast<InstanceHandleRoute*>(router->m_Routes[path].get());
return route;
}
void Router::Get(const std::string &path, RouteFunction func) {
auto* route = GetOrNull(path, this);
if (route)
route->GetFunc = std::move(func);
}
void Router::Post(const std::string &path, RouteFunction func) {
auto* route = GetOrNull(path, this);
if (route)
route->PostFunc = std::move(func);
}
void Router::Put(const std::string &path, RouteFunction func) {
auto* route = GetOrNull(path, this);
if (route)
route->PutFunc = std::move(func);
}
void Router::Patch(const std::string &path, RouteFunction func) {
auto* route = GetOrNull(path, this);
if (route)
route->PatchFunc = std::move(func);
}
void Router::Delete(const std::string &path, RouteFunction func) {
auto* route = GetOrNull(path, this);
if (route)
route->DeleteFunc = std::move(func);
}
} // namespace VWeb } // namespace VWeb

View file

@ -8,6 +8,10 @@ Server::Server() {
m_ServerConfig->EPoll = CreateRef<EPollManager>(); m_ServerConfig->EPoll = CreateRef<EPollManager>();
m_ServerConfig->Socket = CreateRef<SocketManager>(m_ServerConfig); m_ServerConfig->Socket = CreateRef<SocketManager>(m_ServerConfig);
m_RequestHandler = CreateRef<RequestHandler>(m_ServerConfig->Socket); m_RequestHandler = CreateRef<RequestHandler>(m_ServerConfig->Socket);
auto& middleWare = m_RequestHandler->Middleware();
middleWare->Create<CookieManager>();
middleWare->Create<SessionManager>();
middleWare->Create<AuthWare>();
m_RequestHandler->m_Server = this; m_RequestHandler->m_Server = this;
}; };
void Server::LoadSharedLibs() { void Server::LoadSharedLibs() {
@ -132,4 +136,8 @@ void Server::CreateRequest(int sockID) {
m_RawRequest.Remove(sockID); m_RawRequest.Remove(sockID);
m_RequestHandler->AddRequest(request); m_RequestHandler->AddRequest(request);
} }
Ref<MiddleWareHandler> &Server::Middleware() {
return m_RequestHandler->Middleware();
}
} // namespace VWeb } // namespace VWeb

75
VWeb.h
View file

@ -14,7 +14,9 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
#include <utility>
#include <vector> #include <vector>
#include <functional>
namespace VWeb { namespace VWeb {
@ -253,6 +255,7 @@ protected:
bool m_IsErrored{false}; bool m_IsErrored{false};
}; };
class MiddleWareHandler;
class RequestHandler; class RequestHandler;
class Response; class Response;
class Router; class Router;
@ -272,6 +275,7 @@ public:
void AddRoute(const std::string &path, const Ref<Route> &route); void AddRoute(const std::string &path, const Ref<Route> &route);
void RemoveRoute(const std::string &path); void RemoveRoute(const std::string &path);
Ref<MiddleWareHandler>& Middleware();
protected: protected:
void Execute(); void Execute();
void OutgoingExecute(epoll_event &event); void OutgoingExecute(epoll_event &event);
@ -305,6 +309,8 @@ struct Session {
void Remove(const std::string &key); void Remove(const std::string &key);
bool Has(const std::string &key); bool Has(const std::string &key);
Ref<SessionData> &operator[](const std::string &key) { return m_Data[key]; } Ref<SessionData> &operator[](const std::string &key) { return m_Data[key]; }
void SetSessionData(const std::string& key, const Ref<SessionData>& data) { m_Data[key] = data; }
bool ContainsData() { return !m_Data.empty(); }
protected: protected:
std::chrono::time_point<std::chrono::system_clock, ms> m_LastCall = std::chrono::time_point<std::chrono::system_clock, ms> m_LastCall =
@ -439,6 +445,9 @@ public:
bool HasParameter(const std::string &key) const { bool HasParameter(const std::string &key) const {
return Parameters.contains(key); return Parameters.contains(key);
} }
bool HasHeader(const std::string &key) const {
return Headers.contains(key);
}
std::string &FirstOf(const std::string &key) { std::string &FirstOf(const std::string &key) {
return Parameters[key].GetFirst(); return Parameters[key].GetFirst();
} }
@ -448,6 +457,8 @@ public:
Vector<std::string> URLParameters; Vector<std::string> URLParameters;
}; };
class Response { class Response {
public:
static Ref<Response> FromCode(HttpStatusCode code);
public: public:
size_t Length{0}; size_t Length{0};
Ref<Cookies> CookieData{nullptr}; Ref<Cookies> CookieData{nullptr};
@ -498,6 +509,7 @@ protected:
friend Server; friend Server;
}; };
typedef std::function<bool(const Request&,Response&)> RouteFunction;
class Route { class Route {
public: public:
Route() = default; Route() = default;
@ -514,6 +526,8 @@ public:
bool SupportsMethod(const Request &request); bool SupportsMethod(const Request &request);
virtual bool IsAllowed(const Request &request); virtual bool IsAllowed(const Request &request);
void AllowMethod(HttpMethod method);
protected: protected:
bool m_AllowAll{true}; bool m_AllowAll{true};
Vector<HttpMethod> m_AllowedMethods; Vector<HttpMethod> m_AllowedMethods;
@ -531,43 +545,86 @@ public:
Ref<Route> FindRoute(Ref<Request> &request); Ref<Route> FindRoute(Ref<Request> &request);
static void AddToArgs(Ref<Request> &request, Vector<std::string> &items); static void AddToArgs(Ref<Request> &request, Vector<std::string> &items);
protected: public:
void Get(const std::string& path, RouteFunction);
void Post(const std::string& path, RouteFunction);
void Put(const std::string& path, RouteFunction);
void Patch(const std::string& path, RouteFunction);
void Delete(const std::string& path, RouteFunction);
std::unordered_map<std::string, Ref<Route>> m_Routes; std::unordered_map<std::string, Ref<Route>> m_Routes;
}; };
typedef std::optional<Ref<Response>> PreMiddleWareReturn;
struct MiddleWare { struct MiddleWare {
int Pos{0}; int Pos{0};
virtual void PreHandle(Request &){}; virtual PreMiddleWareReturn PreHandle(Request &){ return {}; }
virtual void PostHandle(Request &, Response &){}; virtual bool PostHandle(const Request &, Response &){ return true; }
virtual void Shutdown(Request &, Response &){}; virtual void Shutdown(const Request &, const Response &){};
bool operator<(const MiddleWare *rhs) const { return Pos < rhs->Pos; } bool operator<(const MiddleWare *rhs) const { return Pos < rhs->Pos; }
}; };
class MiddleWareHandler { class MiddleWareHandler {
public: public:
void HandlePre(Ref<Request> &); PreMiddleWareReturn HandlePre(Ref<Request> &);
void HandlePost(Ref<Request> &, Ref<Response> &); void HandlePost(Ref<Request> &, Ref<Response> &);
void Shutdown(Ref<Request> &, Ref<Response> &); void Shutdown(Ref<Request> &, Ref<Response> &);
public: public:
template <class T> Ref<MiddleWare> Get() { return GetById(typeid(T).name()); } template <class T> Ref<MiddleWare> GetRef() { return GetById(typeid(T).name()); }
template <class T> T& Get() { return static_cast<T&>(*GetById(typeid(T).name())); }
template <class T> void Set(Ref<MiddleWare> &instance) { template <class T> void Set(Ref<MiddleWare> &instance) {
auto &type = typeid(T); auto &type = typeid(T);
if (type.before(typeid(MiddleWare))) if (type.before(typeid(MiddleWare)))
SetById(type.name(), instance); SetById(type.name(), instance);
} }
template <class T> Ref<MiddleWare> Create() { template <class T> T& Create() {
return SetById(typeid(T).name(), CreateRef<T>()); return static_cast<T&>(*CreateMiddleWare<T>());
} }
template <class T> void Remove() { RemoveById(typeid(T).name()); } template <class T> void Remove() { RemoveById(typeid(T).name()); }
protected: protected:
template <class T> Ref<MiddleWare> CreateMiddleWare() {
return SetById(typeid(T).name(), CreateRef<T>());
}
Ref<MiddleWare> GetById(const char *id); Ref<MiddleWare> GetById(const char *id);
Ref<MiddleWare> SetById(const char *id, const Ref<MiddleWare> &); Ref<MiddleWare> SetById(const char *id, const Ref<MiddleWare> &);
void RemoveById(const char *id); void RemoveById(const char *id);
std::map<std::string, Ref<MiddleWare>> m_MiddleWares; std::map<std::string, Ref<MiddleWare>> m_MiddleWares;
}; };
typedef std::function<PreMiddleWareReturn(Request& req)> AuthFunction;
class AuthWare : public MiddleWare {
public:
AuthWare() = default;
~AuthWare() = default;
PreMiddleWareReturn PreHandle(Request &request) override;
void SetAuthMethod(AuthFunction function) { m_AuthFunction = std::move(function); }
protected:
AuthFunction m_AuthFunction{nullptr};
};
class SessionManager : public MiddleWare {
public:
SessionManager();
~SessionManager();
PreMiddleWareReturn PreHandle(Request& request) override;
bool PostHandle(const Request& request, Response& response) override;
protected:
void GC();
protected:
Ref<std::thread> m_GCThread;
std::mutex m_Mutex;
std::unordered_map<std::string, Ref<Session>> m_Sessions;
int m_Counter{-1};
bool m_IsRunning{true};
};
class CookieManager : public MiddleWare {
public:
CookieManager() = default;
~CookieManager() = default;
PreMiddleWareReturn PreHandle(Request&) override;
};
#pragma endregion VWEB_ROUTING #pragma endregion VWEB_ROUTING
#pragma endregion VWEB #pragma endregion VWEB
} // namespace VWeb } // namespace VWeb

BIN
dist/libVWeb.debug.a vendored

Binary file not shown.

BIN
dist/libVWeb.release.a vendored

Binary file not shown.

View file

@ -1,19 +1,18 @@
#include <iostream>
#include <VWeb.h> #include <VWeb.h>
class BigDataRoute : public VWeb::Route { bool Ping(const VWeb::Request&, VWeb::Response& response) {
public: response << "Pong";
bool Execute(const VWeb::Request &request, VWeb::Response &response) override { return true;
response << "<pre>\n"; }
for (int i = 0; i < 100; ++i)
response << "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n";
response << "</pre>";
return true;
}
};
int main() { int main() {
using namespace VWeb;
VWeb::Server server; VWeb::Server server;
server.AddRoute("/test", VWeb::CreateRef<BigDataRoute>()); auto& router = server.GetRouter();
router->Get("/test", [](const Request&, Response& response) {
response << "NICE";
return true;
});
router->Get("/ping", &Ping);
server.Start(); server.Start();
server.Join(); server.Join();
return 0; return 0;