Split VWeb into smaller headers

We have no make install support... so we don't need to have everything as a single-header and lib file.
This commit is contained in:
Maurice Grönwoldt 2023-09-16 16:29:03 +02:00
parent 4367534a33
commit 5bb68a7d02
43 changed files with 902 additions and 685 deletions

5
.gitattributes vendored Normal file
View file

@ -0,0 +1,5 @@
* text=auto
*.txt text
*.cpp text
*.h text
*.hpp text

1
.gitignore vendored
View file

@ -4,6 +4,7 @@ build-debug/
release-build/ release-build/
build-release/ build-release/
cmake-build-debug/ cmake-build-debug/
cmake-build-debug-wsl/
.idea/ .idea/
example/build example/build

View file

@ -8,21 +8,15 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUGSOFT") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUGSOFT")
set(THREADS_PREFER_PTHREAD_FLAG ON) set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
set(SOURCE_FILES
Source/EPollManager.cpp include(CMakeMacros.txt)
Source/RequestHandler.cpp
Source/Server.cpp add_subdirectory(Source)
Source/SocketManager.cpp add_subdirectory(Includes)
Source/ThreadPool.cpp
Source/Router.cpp
Source/MiddleWare.cpp
Source/Route.cpp
Source/StringUtils.cpp
Source/Cookie.cpp
Source/Session.cpp
Source/Response.cpp
Source/InbuildMiddleWare.cpp)
include_directories(${CMAKE_SOURCE_DIR}/) include_directories(${CMAKE_SOURCE_DIR}/)
get_property(source_files GLOBAL PROPERTY SRCS)
set(SOURCE_FILES ${source_files})
add_library(VWeb ${SOURCE_FILES}) add_library(VWeb ${SOURCE_FILES})
include(GNUInstallDirs) include(GNUInstallDirs)
@ -31,7 +25,9 @@ set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated")
set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.cmake") set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.cmake")
set(project_config "${generated_dir}/${PROJECT_NAME}Config.cmake") set(project_config "${generated_dir}/${PROJECT_NAME}Config.cmake")
install(FILES VWeb.h DESTINATION include/VWeb-${version}) get_property(header_files GLOBAL PROPERTY Headers)
install(FILES ${header_files} DESTINATION include/VWeb-${version})
install(TARGETS ${PROJECT_NAME} install(TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}-targets EXPORT ${PROJECT_NAME}-targets
LIBRARY DESTINATION lib/$<CONFIG> LIBRARY DESTINATION lib/$<CONFIG>

25
CMakeMacros.txt Normal file
View file

@ -0,0 +1,25 @@
macro(add_sources)
get_property(tmp GLOBAL PROPERTY SRCS)
file(RELATIVE_PATH _relPath "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
foreach (_src ${ARGN})
if (_relPath)
list(APPEND tmp "${_relPath}/${_src}")
else ()
list(APPEND tmp "${_src}")
endif ()
endforeach ()
set_property(GLOBAL PROPERTY SRCS ${tmp})
endmacro()
macro(add_headers)
get_property(tmp GLOBAL PROPERTY Headers)
file(RELATIVE_PATH _relPath "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
foreach (_src ${ARGN})
if (_relPath)
list(APPEND tmp "${_relPath}/${_src}")
else ()
list(APPEND tmp "${_src}")
endif ()
endforeach ()
set_property(GLOBAL PROPERTY Headers ${tmp})
endmacro()

24
Includes/CMakeLists.txt Normal file
View file

@ -0,0 +1,24 @@
add_headers(
Cookie.h
EPollManager.h
Http.h
Map.h
MiddleWare.h
MiddleWareHandler.h
ParameterValue.h
PreMiddleWare.h
Queue.h
Request.h
RequestHandler.h
Response.h
Route.h
Router.h
Server.h
ServerConfig.h
Session.h
Socket.h
SocketManager.h
ThreadPool.h
Types.h
VWeb.h
)

35
Includes/Cookie.h Normal file
View file

@ -0,0 +1,35 @@
#pragma once
#include <string>
#include <unordered_map>
namespace VWeb {
enum class CookieSameSite { Invalid = 0, Strict, Lax, None };
struct Cookie {
std::string Name{};
std::string Value{};
std::string Expires{};
int MaxAge{};
std::string Domain{};
std::string Path{"/"};
bool Secure{};
bool HttpOnly{};
CookieSameSite SameSite{CookieSameSite::Strict};
bool IsOld = false;
};
struct Cookies {
public:
void Remove(const std::string &name);
bool Has(const std::string &name) const;
Cookie &Get(const std::string &name) { return Data[name]; };
std::string TransformToHeader();
void CreateCookieFromString(const std::string &data);
void CreateOld(const std::string &name, const std::string &value);
std::unordered_map<std::string, Cookie> Data;
Cookie &operator[](const std::string &name) { return Data[name]; }
protected:
static void CookieToString(std::stringstream &stream, Cookie &cookie);
};
} // namespace VWeb

17
Includes/EPollManager.h Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <sys/epoll.h>
namespace VWeb {
class EPollManager {
public:
EPollManager();
~EPollManager();
[[nodiscard]] bool Dispatch(int sock, uint32_t eventType = EPOLLIN) const;
[[nodiscard]] bool UpdateEvents(int sock, uint32_t eventType = EPOLLIN) const;
int Wait(int idx, epoll_event *events) const;
protected:
int m_EpollID{-1};
};
}

76
Includes/Http.h Normal file
View file

@ -0,0 +1,76 @@
#pragma once
namespace VWeb {
enum class HttpStatusCode : int {
Continue = 100,
SwitchingProtocols = 101,
Processing = 102,
EarlyHints = 103,
OK = 200,
Created = 201,
Accepted = 202,
NonAuthoritativeInformation = 203,
NoContent = 204,
ResetContent = 205,
PartialContent = 206,
MultiStatus = 207,
AlreadyReported = 208,
IMUsed = 226,
MultipleChoices = 300,
MovedPermanently = 301,
Found = 302,
SeeOther = 303,
NotModified = 304,
UseProxy = 305,
TemporaryRedirect = 307,
PermanentRedirect = 308,
BadRequest = 400,
Unauthorized = 401,
PaymentRequired = 402,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
NotAcceptable = 406,
ProxyAuthenticationRequired = 407,
RequestTimeout = 408,
Conflict = 409,
Gone = 410,
LengthRequired = 411,
PreconditionFailed = 412,
PayloadTooLarge = 413,
URITooLong = 414,
UnsupportedMediaType = 415,
RangeNotSatisfiable = 416,
ExpectationFailed = 417,
ImATeapot = 418,
UnprocessableEntity = 422,
Locked = 423,
FailedDependency = 424,
UpgradeRequired = 426,
PreconditionRequired = 428,
TooManyRequests = 429,
RequestHeaderFieldsTooLarge = 431,
UnavailableForLegalReasons = 451,
InternalServerError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
GatewayTimeout = 504,
HTTPVersionNotSupported = 505,
VariantAlsoNegotiates = 506,
InsufficientStorage = 507,
LoopDetected = 508,
NotExtended = 510,
NetworkAuthenticationRequired = 511
};
enum class HttpMethod {
GET = 0,
HEAD,
OPTIONS,
POST,
PUT,
PATCH,
DELETE,
FALLBACK
};
} // namespace VWeb

59
Includes/Map.h Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#include <cassert>
#include <mutex>
#include <unordered_map>
#include <condition_variable>
#include <atomic>
namespace VWeb {
template <typename T, typename S> struct SafeMap {
explicit SafeMap(size_t maxSize = -1UL) : m_MaxSize(maxSize), m_End(false){};
bool Add(const T &t, S &x) {
std::unique_lock<std::mutex> lck(m_Mutex);
while (m_Data.size() == m_MaxSize && !m_End)
return false;
assert(!m_End);
m_Data.emplace(t, std::move(x));
return true;
};
bool Add(T &&t, S &&x) {
std::unique_lock<std::mutex> lck(m_Mutex);
while (m_Data.size() == m_MaxSize && !m_End)
return false;
assert(!m_End);
m_Data.push(std::move(t));
return true;
};
void Remove(T t) {
std::unique_lock<std::mutex> lck(m_Mutex);
if (m_Data.empty() || m_End)
return;
if (m_Data.contains(t))
m_Data.erase(t);
m_CVFull.notify_one();
};
void Clear() {
std::unique_lock<std::mutex> lck(m_Mutex);
std::unordered_map<T, S> empty;
std::swap(m_Data, empty);
m_CVFull.notify_all();
};
bool Has(T t) {
std::unique_lock<std::mutex> lck(m_Mutex);
return m_Data.contains(t);
};
S &Get(T t) {
std::unique_lock<std::mutex> lck(m_Mutex);
return m_Data[t];
};
int Size() { return m_Data.size(); };
private:
std::unordered_map<T, S> m_Data{};
std::mutex m_Mutex{};
std::condition_variable m_CVFull{};
const size_t m_MaxSize{};
std::atomic<bool> m_End{};
};
}

16
Includes/MiddleWare.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include <optional>
#include "Response.h"
#include "Request.h"
namespace VWeb {
typedef std::optional<Ref<Response>> PreMiddleWareReturn;
struct MiddleWare {
int Pos{0};
virtual PreMiddleWareReturn PreHandle(Request &) { return {}; }
virtual bool PostHandle(Request &, Response &) { return true; }
virtual void Shutdown(Request &, const Response &){};
bool operator<(const MiddleWare *rhs) const { return Pos < rhs->Pos; }
};
}

View file

@ -0,0 +1,43 @@
#pragma once
#include "MiddleWare.h"
#include "Request.h"
#include "Response.h"
#include <map>
namespace VWeb {
class MiddleWareHandler {
public:
PreMiddleWareReturn HandlePre(Ref<Request> &);
void HandlePost(Ref<Request> &, Ref<Response> &);
void Shutdown(Ref<Request> &, Ref<Response> &);
public:
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) {
auto &type = typeid(T);
if (type.before(typeid(MiddleWare)))
SetById(type.name(), instance);
}
template <class T> T &Create() {
return static_cast<T &>(*CreateMiddleWare<T>());
}
template <class T> void Remove() { RemoveById(typeid(T).name()); }
protected:
template <class T> Ref<MiddleWare> CreateMiddleWare() {
return SetById(typeid(T).name(), CreateRef<T>());
}
Ref<MiddleWare> GetById(const char *id);
Ref<MiddleWare> SetById(const char *id, const Ref<MiddleWare> &);
void RemoveById(const char *id);
std::map<std::string, Ref<MiddleWare>> m_MiddleWares;
};
} // namespace VWeb

21
Includes/ParameterValue.h Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include <string>
#include <vector>
namespace VWeb {
struct ParameterValue {
void Add(const std::string &item) { m_Data.push_back(item); }
void Set(const std::string &item) {
m_Data.clear();
m_Data.push_back(item);
}
std::string &Get(int item) { return m_Data[item]; }
std::string &GetFirst() { return Get(0); }
size_t Size() { return m_Data.size(); }
std::vector<std::string> &Values() { return m_Data; }
protected:
std::vector<std::string> m_Data{};
};
}

48
Includes/PreMiddleWare.h Normal file
View file

@ -0,0 +1,48 @@
#pragma once
#include "MiddleWare.h"
#include <functional>
#include <thread>
namespace VWeb {
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(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;
};
} // namespace VWeb

87
Includes/Queue.h Normal file
View file

@ -0,0 +1,87 @@
#pragma once
#include <mutex>
#include <queue>
#include <optional>
#include <condition_variable>
#include <atomic>
#include <cassert>
namespace VWeb {
template <typename T> struct SafeQueue {
explicit SafeQueue(size_t maxSize = -1UL)
: m_MaxSize(maxSize),
m_End(false){};
void Push(const T &t) {
std::unique_lock<std::mutex> lck(m_Mutex);
while (m_Queue.size() == m_MaxSize && !m_End)
m_CVFull.wait(lck);
assert(!m_End);
m_Queue.push(std::move(t));
m_CVEmpty.notify_one();
};
void Push(T &&t) {
std::unique_lock<std::mutex> lck(m_Mutex);
while (m_Queue.size() == m_MaxSize && !m_End)
m_CVFull.wait(lck);
assert(!m_End);
m_Queue.push(std::move(t));
m_CVEmpty.notify_one();
};
void Open() {
m_End = false;
std::lock_guard<std::mutex> lck(m_Mutex);
m_CVEmpty.notify_all();
m_CVFull.notify_all();
};
void Close() {
m_End = true;
std::lock_guard<std::mutex> lck(m_Mutex);
m_CVEmpty.notify_all();
m_CVFull.notify_all();
};
void Clear() {
std::unique_lock<std::mutex> lck(m_Mutex);
std::queue<T> empty;
std::swap(m_Queue, empty);
m_CVEmpty.notify_all();
m_CVFull.notify_all();
};
std::optional<T> Pop() {
std::unique_lock<std::mutex> lck(m_Mutex);
if (m_Queue.empty() || m_End)
return {};
T t = std::move(m_Queue.front());
m_Queue.pop();
m_CVFull.notify_one();
return t;
};
std::optional<T> WaitAndPop() {
std::unique_lock<std::mutex> lck(m_Mutex);
while (m_Queue.empty() && !m_End)
m_CVEmpty.wait(lck);
if (m_Queue.empty() || m_End)
return {};
T t = std::move(m_Queue.front());
m_Queue.pop();
m_CVFull.notify_one();
return t;
};
bool IsClosed() { return m_End; }
int Size() { return m_Queue.size(); }
std::queue<T> &GetQueue() { return m_Queue; }
void Flush() {
while (!m_Queue.empty())
m_CVEmpty.notify_one();
m_End = true;
m_CVEmpty.notify_all();
}
private:
std::queue<T> m_Queue;
std::mutex m_Mutex;
std::condition_variable m_CVEmpty, m_CVFull;
const size_t m_MaxSize;
std::atomic<bool> m_End;
};
}

34
Includes/Request.h Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include "Cookie.h"
#include "Http.h"
#include "ParameterValue.h"
#include "Session.h"
#include <string>
#include <vector>
namespace VWeb {
struct Request {
public:
int ID;
std::string Body;
HttpMethod Method{HttpMethod::GET};
std::string URI;
Ref<Session> SessionData;
Ref<Cookies> CookieData;
Cookie &GetCookie(const std::string &key) { return CookieData->Get(key); };
ParameterValue &Parameter(const std::string &key) { return Parameters[key]; }
bool HasParameter(const std::string &key) const {
return Parameters.contains(key);
}
bool HasHeader(const std::string &key) const { return Headers.contains(key); }
std::string &FirstOf(const std::string &key) {
return Parameters[key].GetFirst();
}
ParameterValue &Header(const std::string &key) { return Headers[key]; }
std::unordered_map<std::string, ParameterValue> Parameters;
std::unordered_map<std::string, ParameterValue> Headers;
std::vector<std::string> URLParameters;
};
} // namespace VWeb

42
Includes/RequestHandler.h Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include "MiddleWareHandler.h"
#include "Queue.h"
#include "Request.h"
#include "Router.h"
#include "SocketManager.h"
#include "ThreadPool.h"
#include <functional>
namespace VWeb {
class Server;
typedef std::function<const void(Request &, const std::string &body)>
ParseFunction;
class RequestHandler {
public:
explicit RequestHandler(const Ref<SocketManager> &manager);
void InitThreads(int count);
void AddRequest(Ref<Request> &request);
void Stop();
void SetRouter(Ref<Router> &router);
void AddSendResponse(SendData);
Ref<MiddleWareHandler> &Middleware() { return m_MiddleWare; }
bool HasBodyParser(const std::string &key) {
return m_ParseFunctions.contains(key);
}
ParseFunction &GetBodyParser(const std::string &key) {
return m_ParseFunctions.at(key);
}
protected:
std::unordered_map<std::string, ParseFunction> m_ParseFunctions;
Ref<Router> m_Router{nullptr};
Ref<SocketManager> m_SocketManager{nullptr};
Server *m_Server{nullptr};
SafeQueue<Ref<Request>> m_Requests{};
ThreadPool m_Pool{"RequestHandler"};
Ref<MiddleWareHandler> m_MiddleWare{nullptr};
friend Server;
};
} // namespace VWeb

43
Includes/Response.h Normal file
View file

@ -0,0 +1,43 @@
#pragma once
#include "Cookie.h"
#include "Http.h"
#include "Session.h"
#include "Types.h"
#include "ParameterValue.h"
namespace VWeb {
class Response {
public:
static Ref<Response> FromCode(HttpStatusCode code);
public:
size_t Length{0};
Ref<Cookies> CookieData{nullptr};
Ref<Session> SessionData{nullptr};
HttpStatusCode Status{HttpStatusCode::OK};
HttpMethod Method{HttpMethod::GET};
public:
std::string GetResponse();
void SetHeader(const std::string &key, ParameterValue &value);
void SetHeader(const std::string &key, const std::string &value);
void AddHeaders(const std::string &key, const std::vector<std::string> &values);
void AddHeader(const std::string &key, const std::string &value);
void SetType(const std::string &type);
void AddContent(const std::string &data);
void SetStatus(HttpStatusCode);
void SetMethod(HttpMethod);
void Reset();
Response &operator<<(const std::string &data) {
m_Content << data;
return *this;
}
protected:
std::string TransformHeaders(const std::string &content);
std::string m_Type{"text/html"};
std::stringstream m_Content;
std::unordered_map<std::string, ParameterValue> m_Headers;
};
} // namespace VWeb

34
Includes/Route.h Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include "Http.h"
#include <initializer_list>
#include "Request.h"
#include "Response.h"
#include <vector>
namespace VWeb {
class Router;
class Route {
public:
Route() = default;
virtual ~Route() = default;
Route(std::initializer_list<HttpMethod>);
virtual bool Execute(Request &request, Response &response);
virtual bool Get(Request &request, Response &response);
virtual bool Post(Request &request, Response &response);
virtual bool Put(Request &request, Response &response);
virtual bool Patch(Request &request, Response &response);
virtual bool Delete(Request &request, Response &response);
bool Options(Request &request, Response &response);
virtual bool Fallback(Request &request, Response &response);
bool SupportsMethod(Request &request);
virtual bool IsAllowed(Request &request);
void AllowMethod(HttpMethod method);
protected:
bool m_AllowAll{true};
std::vector<HttpMethod> m_AllowedMethods;
friend Router;
};
}

29
Includes/Router.h Normal file
View file

@ -0,0 +1,29 @@
#pragma once
#include "Route.h"
#include <functional>
#include <vector>
namespace VWeb {
typedef std::function<bool(Request &, Response &)> RouteFunction;
class Router {
public:
Router();
void AddRoute(const std::string &name, const Ref<Route> &route);
Ref<Route> &GetRoute(const std::string &name);
void DeleteRoute(const std::string &name);
Ref<Response> HandleRoute(Ref<Request> &request);
Ref<Route> FindRoute(Ref<Request> &request);
static void AddToArgs(Ref<Request> &request, std::vector<std::string> &items);
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;
};
} // namespace VWeb

49
Includes/Server.h Normal file
View file

@ -0,0 +1,49 @@
#pragma once
#include "EPollManager.h"
#include "SocketManager.h"
#include "Map.h"
#include "Router.h"
#include "RequestHandler.h"
#include "MiddleWareHandler.h"
#include "Route.h"
namespace VWeb {
class Server {
public:
Server();
// Will Load SharedLibs from subdirectory
// This will allow that VWeb will run as Standalone and will load .so files
// without recompiling itself
void LoadSharedLibs();
void Start();
void Join() { m_WorkerThread->join(); }
void Stop() { m_IsExit = true; }
Ref<Router> &GetRouter() { return m_Router; }
Ref<ServerConfig> &GetServerConfig() { return m_ServerConfig; }
void AddRoute(const std::string &path, const Ref<Route> &route);
void RemoveRoute(const std::string &path);
Ref<MiddleWareHandler> &Middleware();
protected:
void Execute();
void OutgoingExecute(epoll_event &event);
void IncomingExecute(epoll_event &event);
void HandleRequestReading(epoll_event &event);
void CreateRequest(int sockID);
protected:
Ref<Router> m_Router;
Ref<ServerConfig> m_ServerConfig;
Ref<RequestHandler> m_RequestHandler;
SafeMap<int, Accept> m_RawRequest{60000};
SafeMap<int, SendData> m_OutRequest{60000};
Ref<std::thread> m_WorkerThread;
std::mutex m_Mutex;
bool m_IsExit{false};
protected:
friend RequestHandler;
};
}

22
Includes/ServerConfig.h Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include "EPollManager.h"
#include "SocketManager.h"
#include "Types.h"
#include <string>
namespace VWeb {
struct ServerConfig {
int Port{9020};
int MaxBufferSize{-1};
int BufferSize{16384};
int WorkerThreads{-1};
int MaxEvents{5000};
bool AllowSharedLibs{true};
Ref<EPollManager> EPoll{nullptr};
Ref<SocketManager> Socket{nullptr};
std::string ErrorDir;
std::string MimeFiles;
};
}

31
Includes/Session.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include <chrono>
#include "Types.h"
#include <unordered_map>
namespace VWeb {
using ms = std::chrono::duration<double, std::milli>;
struct SessionData {
virtual ~SessionData() = default;
template <class T> T *As() { return reinterpret_cast<T *>(this); }
};
struct Session {
std::string Id;
long TTLSeconds = 1440; // 24 minutes 1440 seconds
bool IsValid();
void Update();
void Remove(const std::string &key);
bool Has(const std::string &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:
std::chrono::time_point<std::chrono::system_clock, ms> m_LastCall =
std::chrono::system_clock::now();
std::unordered_map<std::string, Ref<SessionData>> m_Data;
};
}

22
Includes/Socket.h Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <sstream>
namespace VWeb {
enum class EPollReturns { OK = 0, BREAK, FAILURE };
enum class WriteState { OK = 0, EPOLL, ERRORED };
struct Accept {
EPollReturns ReturnValue{EPollReturns::OK};
std::stringstream Data{};
ssize_t CurrentBytes;
int SockId{-1};
};
struct SendData {
ssize_t Offset{0};
ssize_t Size{0};
int SocketID{0};
std::string Content;
};
} // namespace VWeb

41
Includes/SocketManager.h Normal file
View file

@ -0,0 +1,41 @@
#pragma once
#include "Socket.h"
#include <netinet/in.h>
namespace VWeb {
class SocketUtils {
public:
static bool MakeAsync(int socketId);
static bool Add(int socketId);
static bool Close(int socketId);
static bool WriteDone(int socketId);
static WriteState Write(SendData &);
};
class ServerConfig;
class SocketManager {
public:
explicit SocketManager(const Ref<ServerConfig> &);
~SocketManager();
Accept Handle();
bool SetSendListen(int socketID);
bool SetReadListen(int socketID);
Ref<ServerConfig> &GetServerConfig() { return m_ServerConfig; }
[[nodiscard]] int ID() const { return m_SocketID; }
[[nodiscard]] bool IsErrored() const { return m_IsErrored; }
void Init();
protected:
void Errored(const std::string &data);
int m_SocketID{};
Ref<ServerConfig> m_ServerConfig{};
struct sockaddr_in m_Address {
0
};
int m_AddressLength{sizeof(m_Address)};
bool m_IsErrored{false};
};
} // namespace VWeb

27
Includes/ThreadPool.h Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include "Queue.h"
#include "Types.h"
namespace VWeb {
struct WorkerJob {
virtual ~WorkerJob() = default;
virtual void Execute() = 0;
};
class ThreadPool {
public:
explicit ThreadPool(std::string name);
void SetThreadCount(int);
void Dispatch(const Ref<WorkerJob> &);
void Create();
void Stop();
void Execute();
protected:
std::string m_Name;
int m_ThreadCount{1};
bool m_IsCreated{false};
std::vector<std::thread> m_Threads{};
SafeQueue<Ref<WorkerJob>> m_Queue;
bool m_IsDone{false};
};
} // namespace VWeb

11
Includes/Types.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include <memory>
namespace VWeb {
template <typename T> using Ref = std::shared_ptr<T>;
template <typename T, typename... Args>
constexpr Ref<T> CreateRef(Args &&...args) {
return std::make_shared<T>(std::forward<Args>(args)...);
}
} // namespace VWeb

7
Includes/VWeb.h Normal file
View file

@ -0,0 +1,7 @@
#pragma once
#include "Request.h"
#include "Response.h"
#include "Route.h"
#include "Server.h"
#include "ServerConfig.h"

15
Source/CMakeLists.txt Normal file
View file

@ -0,0 +1,15 @@
add_sources(
EPollManager.cpp
RequestHandler.cpp
Server.cpp
SocketManager.cpp
ThreadPool.cpp
Router.cpp
MiddleWare.cpp
Route.cpp
StringUtils.cpp
Cookie.cpp
Session.cpp
Response.cpp
InbuildMiddleWare.cpp
)

View file

@ -1,7 +1,6 @@
#include "Includes/VWeb.h"
#include "StringUtils.h" #include "StringUtils.h"
#include <VWeb.h>
namespace VWeb { namespace VWeb {
void Cookies::Remove(const std::string &name) { void Cookies::Remove(const std::string &name) {
if (Data.contains(name)) if (Data.contains(name))

View file

@ -1,4 +1,7 @@
#include <VWeb.h> #include "Includes/VWeb.h"
#include <fcntl.h>
namespace VWeb { namespace VWeb {
EPollManager::EPollManager() { EPollManager::EPollManager() {
m_EpollID = epoll_create1(0); m_EpollID = epoll_create1(0);

View file

@ -1,5 +1,5 @@
#include "Includes/PreMiddleWare.h"
#include "StringUtils.h" #include "StringUtils.h"
#include "VWeb.h"
#include <random> #include <random>
#include <sstream> #include <sstream>

View file

@ -1,4 +1,4 @@
#include <VWeb.h> #include "Includes/VWeb.h"
namespace VWeb { namespace VWeb {
std::optional<Ref<Response>> std::optional<Ref<Response>>

View file

@ -1,7 +1,6 @@
#include "Includes/VWeb.h"
#include "StringUtils.h" #include "StringUtils.h"
#include <VWeb.h>
namespace VWeb { namespace VWeb {
HttpMethod StringToHTTPMethod(std::string &method) { HttpMethod StringToHTTPMethod(std::string &method) {

View file

@ -1,4 +1,4 @@
#include <VWeb.h> #include "Includes/VWeb.h"
namespace VWeb { namespace VWeb {
// clang-format off // clang-format off
@ -87,7 +87,7 @@ void Response::SetHeader(const std::string &key, ParameterValue &value) {
m_Headers[key] = value; m_Headers[key] = value;
} }
void Response::AddHeaders(const std::string &key, void Response::AddHeaders(const std::string &key,
const Vector<std::string> &values) { const std::vector<std::string> &values) {
auto &element = m_Headers[key]; auto &element = m_Headers[key];
for (const auto &value : values) for (const auto &value : values)
element.Add(value); element.Add(value);

View file

@ -1,6 +1,6 @@
#include <VWeb.h> #include "Includes/VWeb.h"
#include <algorithm>
#include <algorithm>
namespace VWeb { namespace VWeb {

View file

@ -1,6 +1,6 @@
#include "Includes/VWeb.h"
#include "StringUtils.h" #include "StringUtils.h"
#include <VWeb.h>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
@ -119,7 +119,7 @@ Ref<Route> Router::FindRoute(Ref<Request> &request) {
return nullptr; return nullptr;
} }
void Router::AddToArgs(Ref<Request> &request, Vector<std::string> &items) { void Router::AddToArgs(Ref<Request> &request, std::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();
} }

View file

@ -1,4 +1,6 @@
#include <VWeb.h> #include "Includes/PreMiddleWare.h"
#include "Includes/VWeb.h"
#include <cstring> #include <cstring>
namespace VWeb { namespace VWeb {

View file

@ -1,4 +1,4 @@
#include <VWeb.h> #include "Includes/VWeb.h"
namespace VWeb { namespace VWeb {
bool Session::IsValid() { bool Session::IsValid() {

View file

@ -1,4 +1,6 @@
#include <VWeb.h> #include "Includes/VWeb.h"
#include <fcntl.h>
namespace VWeb { namespace VWeb {
#pragma region VWebSocketUtils #pragma region VWebSocketUtils
@ -89,7 +91,8 @@ void SocketManager::Init() {
if (listen(m_SocketID, SOMAXCONN) < 0) if (listen(m_SocketID, SOMAXCONN) < 0)
return Errored("Socket Failed to Listen"); return Errored("Socket Failed to Listen");
if (!m_ServerConfig->EPoll->Dispatch(m_SocketID,EPOLLIN | EPOLLET | EPOLLOUT)) if (!m_ServerConfig->EPoll->Dispatch(m_SocketID,
EPOLLIN | EPOLLET | EPOLLOUT))
return Errored("Cannot Add Event"); return Errored("Cannot Add Event");
} }
void SocketManager::Errored(const std::string &data) { void SocketManager::Errored(const std::string &data) {

View file

@ -1,4 +1,5 @@
#include <VWeb.h> #include "Includes/VWeb.h"
#include <thread> #include <thread>
#include <utility> #include <utility>

651
VWeb.h
View file

@ -1,651 +0,0 @@
#pragma once
#include <cassert>
#include <condition_variable>
#include <fcntl.h>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <netinet/in.h>
#include <optional>
#include <queue>
#include <sstream>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>
#include <functional>
namespace VWeb {
template <typename T> using Ref = std::shared_ptr<T>;
template <typename T> using Vector = std::vector<T>;
template <typename T, typename... Args>
constexpr Ref<T> CreateRef(Args &&...args) {
return std::make_shared<T>(std::forward<Args>(args)...);
}
typedef std::mutex Mutex;
#pragma region SAFESTRUCTS
template <typename T, typename S> struct SafeMap {
explicit SafeMap(size_t maxSize = -1UL) : m_MaxSize(maxSize), m_End(false){};
bool Add(const T &t, S &x) {
std::unique_lock<std::mutex> lck(m_Mutex);
while (m_Data.size() == m_MaxSize && !m_End)
return false;
assert(!m_End);
m_Data.emplace(t, std::move(x));
return true;
};
bool Add(T &&t, S &&x) {
std::unique_lock<std::mutex> lck(m_Mutex);
while (m_Data.size() == m_MaxSize && !m_End)
return false;
assert(!m_End);
m_Data.push(std::move(t));
return true;
};
void Remove(T t) {
std::unique_lock<std::mutex> lck(m_Mutex);
if (m_Data.empty() || m_End)
return;
if (m_Data.contains(t))
m_Data.erase(t);
m_CVFull.notify_one();
};
void Clear() {
std::unique_lock<std::mutex> lck(m_Mutex);
std::unordered_map<T, S> empty;
std::swap(m_Data, empty);
m_CVFull.notify_all();
};
bool Has(T t) {
std::unique_lock<std::mutex> lck(m_Mutex);
return m_Data.contains(t);
};
S &Get(T t) {
std::unique_lock<std::mutex> lck(m_Mutex);
return m_Data[t];
};
int Size() { return m_Data.size(); };
private:
std::unordered_map<T, S> m_Data{};
std::mutex m_Mutex{};
std::condition_variable m_CVFull{};
const size_t m_MaxSize{};
std::atomic<bool> m_End{};
};
template <typename T> struct SafeQueue {
explicit SafeQueue(size_t maxSize = -1UL)
: m_MaxSize(maxSize),
m_End(false){};
void Push(const T &t) {
std::unique_lock<std::mutex> lck(m_Mutex);
while (m_Queue.size() == m_MaxSize && !m_End)
m_CVFull.wait(lck);
assert(!m_End);
m_Queue.push(std::move(t));
m_CVEmpty.notify_one();
};
void Push(T &&t) {
std::unique_lock<std::mutex> lck(m_Mutex);
while (m_Queue.size() == m_MaxSize && !m_End)
m_CVFull.wait(lck);
assert(!m_End);
m_Queue.push(std::move(t));
m_CVEmpty.notify_one();
};
void Open() {
m_End = false;
std::lock_guard<std::mutex> lck(m_Mutex);
m_CVEmpty.notify_all();
m_CVFull.notify_all();
};
void Close() {
m_End = true;
std::lock_guard<std::mutex> lck(m_Mutex);
m_CVEmpty.notify_all();
m_CVFull.notify_all();
};
void Clear() {
std::unique_lock<std::mutex> lck(m_Mutex);
std::queue<T> empty;
std::swap(m_Queue, empty);
m_CVEmpty.notify_all();
m_CVFull.notify_all();
};
std::optional<T> Pop() {
std::unique_lock<std::mutex> lck(m_Mutex);
if (m_Queue.empty() || m_End)
return {};
T t = std::move(m_Queue.front());
m_Queue.pop();
m_CVFull.notify_one();
return t;
};
std::optional<T> WaitAndPop() {
std::unique_lock<std::mutex> lck(m_Mutex);
while (m_Queue.empty() && !m_End)
m_CVEmpty.wait(lck);
if (m_Queue.empty() || m_End)
return {};
T t = std::move(m_Queue.front());
m_Queue.pop();
m_CVFull.notify_one();
return t;
};
bool IsClosed() { return m_End; }
int Size() { return m_Queue.size(); }
std::queue<T> &GetQueue() { return m_Queue; }
void Flush() {
while (!m_Queue.empty())
m_CVEmpty.notify_one();
m_End = true;
m_CVEmpty.notify_all();
}
private:
std::queue<T> m_Queue;
std::mutex m_Mutex;
std::condition_variable m_CVEmpty, m_CVFull;
const size_t m_MaxSize;
std::atomic<bool> m_End;
};
#pragma endregion SAFESTRUCTS
#pragma region THREADING
struct WorkerJob {
virtual ~WorkerJob() = default;
virtual void Execute() = 0;
};
class ThreadPool {
public:
explicit ThreadPool(std::string name);
void SetThreadCount(int);
void Dispatch(const Ref<WorkerJob> &);
void Create();
void Stop();
void Execute();
protected:
std::string m_Name;
int m_ThreadCount{1};
bool m_IsCreated{false};
Vector<std::thread> m_Threads{};
SafeQueue<Ref<WorkerJob>> m_Queue;
bool m_IsDone{false};
};
#pragma endregion THREADING
#pragma region VWEB
enum class EPollReturns { OK = 0, BREAK, FAILURE };
class EPollManager;
class SocketManager;
struct ServerConfig {
int Port{9020};
int MaxBufferSize{-1};
int BufferSize{16384};
int WorkerThreads{-1};
int MaxEvents{5000};
bool AllowSharedLibs{true};
Ref<EPollManager> EPoll{nullptr};
Ref<SocketManager> Socket{nullptr};
std::string ErrorDir;
std::string MimeFiles;
};
struct Accept {
EPollReturns ReturnValue{EPollReturns::OK};
std::stringstream Data{};
ssize_t CurrentBytes;
int SockId{-1};
};
class EPollManager {
public:
EPollManager();
~EPollManager();
[[nodiscard]] bool Dispatch(int sock, uint32_t eventType = EPOLLIN) const;
[[nodiscard]] bool UpdateEvents(int sock, uint32_t eventType = EPOLLIN) const;
int Wait(int idx, epoll_event *events) const;
protected:
int m_EpollID{-1};
};
struct SendData {
ssize_t Offset{0};
ssize_t Size{0};
int SocketID{0};
std::string Content;
};
enum class WriteState { OK = 0, EPOLL, ERRORED };
class SocketUtils {
public:
static bool MakeAsync(int socketId);
static bool Add(int socketId);
static bool Close(int socketId);
static bool WriteDone(int socketId);
static WriteState Write(SendData &);
};
class SocketManager {
public:
explicit SocketManager(const Ref<ServerConfig> &);
~SocketManager();
Accept Handle();
bool SetSendListen(int socketID);
bool SetReadListen(int socketID);
Ref<ServerConfig> &GetServerConfig() { return m_ServerConfig; }
[[nodiscard]] int ID() const { return m_SocketID; }
[[nodiscard]] bool IsErrored() const { return m_IsErrored; }
void Init();
protected:
void Errored(const std::string &data);
int m_SocketID{};
Ref<ServerConfig> m_ServerConfig{};
struct sockaddr_in m_Address {
0
};
int m_AddressLength{sizeof(m_Address)};
bool m_IsErrored{false};
};
class MiddleWareHandler;
class RequestHandler;
class Response;
class Router;
class Route;
class Server {
public:
Server();
// Will Load SharedLibs from subdirectory
// This will allow that VWeb will run as Standalone and will load .so files
// without recompiling itself
void LoadSharedLibs();
void Start();
void Join() { m_WorkerThread->join(); }
void Stop() { m_IsExit = true; }
Ref<Router> &GetRouter() { return m_Router; }
Ref<ServerConfig> &GetServerConfig() { return m_ServerConfig; }
void AddRoute(const std::string &path, const Ref<Route> &route);
void RemoveRoute(const std::string &path);
Ref<MiddleWareHandler> &Middleware();
protected:
void Execute();
void OutgoingExecute(epoll_event &event);
void IncomingExecute(epoll_event &event);
void HandleRequestReading(epoll_event &event);
void CreateRequest(int sockID);
protected:
Ref<Router> m_Router;
Ref<ServerConfig> m_ServerConfig;
Ref<RequestHandler> m_RequestHandler;
SafeMap<int, Accept> m_RawRequest{60000};
SafeMap<int, SendData> m_OutRequest{60000};
Ref<std::thread> m_WorkerThread;
Mutex m_Mutex;
bool m_IsExit{false};
protected:
friend RequestHandler;
};
#pragma region VWEB_ROUTING
using ms = std::chrono::duration<double, std::milli>;
struct SessionData {
virtual ~SessionData() = default;
template <class T> T *As() { return reinterpret_cast<T *>(this); }
};
struct Session {
std::string Id;
long TTLSeconds = 1440; // 24 minutes 1440 seconds
bool IsValid();
void Update();
void Remove(const std::string &key);
bool Has(const std::string &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:
std::chrono::time_point<std::chrono::system_clock, ms> m_LastCall =
std::chrono::system_clock::now();
std::unordered_map<std::string, Ref<SessionData>> m_Data;
};
enum class HttpStatusCode : int {
Continue = 100,
SwitchingProtocols = 101,
Processing = 102,
EarlyHints = 103,
OK = 200,
Created = 201,
Accepted = 202,
NonAuthoritativeInformation = 203,
NoContent = 204,
ResetContent = 205,
PartialContent = 206,
MultiStatus = 207,
AlreadyReported = 208,
IMUsed = 226,
MultipleChoices = 300,
MovedPermanently = 301,
Found = 302,
SeeOther = 303,
NotModified = 304,
UseProxy = 305,
TemporaryRedirect = 307,
PermanentRedirect = 308,
BadRequest = 400,
Unauthorized = 401,
PaymentRequired = 402,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
NotAcceptable = 406,
ProxyAuthenticationRequired = 407,
RequestTimeout = 408,
Conflict = 409,
Gone = 410,
LengthRequired = 411,
PreconditionFailed = 412,
PayloadTooLarge = 413,
URITooLong = 414,
UnsupportedMediaType = 415,
RangeNotSatisfiable = 416,
ExpectationFailed = 417,
ImATeapot = 418,
UnprocessableEntity = 422,
Locked = 423,
FailedDependency = 424,
UpgradeRequired = 426,
PreconditionRequired = 428,
TooManyRequests = 429,
RequestHeaderFieldsTooLarge = 431,
UnavailableForLegalReasons = 451,
InternalServerError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
GatewayTimeout = 504,
HTTPVersionNotSupported = 505,
VariantAlsoNegotiates = 506,
InsufficientStorage = 507,
LoopDetected = 508,
NotExtended = 510,
NetworkAuthenticationRequired = 511
};
enum class HttpMethod {
GET = 0,
HEAD,
OPTIONS,
POST,
PUT,
PATCH,
DELETE,
FALLBACK
};
enum class CookieSameSite { Invalid = 0, Strict, Lax, None };
struct Cookie {
std::string Name{};
std::string Value{};
std::string Expires{};
int MaxAge{};
std::string Domain{};
std::string Path{"/"};
bool Secure{};
bool HttpOnly{};
CookieSameSite SameSite{CookieSameSite::Strict};
bool IsOld = false;
};
struct Cookies {
public:
void Remove(const std::string &name);
bool Has(const std::string &name) const;
Cookie &Get(const std::string &name) { return Data[name]; };
std::string TransformToHeader();
void CreateCookieFromString(const std::string &data);
void CreateOld(const std::string &name, const std::string &value);
std::unordered_map<std::string, Cookie> Data;
Cookie &operator[](const std::string &name) { return Data[name]; }
protected:
static void CookieToString(std::stringstream &stream, Cookie &cookie);
};
struct ParameterValue {
void Add(const std::string &item) { m_Data.push_back(item); }
void Set(const std::string &item) {
m_Data.clear();
m_Data.push_back(item);
}
std::string &Get(int item) { return m_Data[item]; }
std::string &GetFirst() { return Get(0); }
size_t Size() { return m_Data.size(); }
Vector<std::string> &Values() { return m_Data; }
protected:
Vector<std::string> m_Data{};
};
struct Request {
public:
int ID;
std::string Body;
HttpMethod Method{HttpMethod::GET};
std::string URI;
Ref<Session> SessionData;
Ref<Cookies> CookieData;
Cookie &GetCookie(const std::string &key) { return CookieData->Get(key); };
ParameterValue &Parameter(const std::string &key) { return Parameters[key]; }
bool HasParameter(const std::string &key) const {
return Parameters.contains(key);
}
bool HasHeader(const std::string &key) const { return Headers.contains(key); }
std::string &FirstOf(const std::string &key) {
return Parameters[key].GetFirst();
}
ParameterValue &Header(const std::string &key) { return Headers[key]; }
std::unordered_map<std::string, ParameterValue> Parameters;
std::unordered_map<std::string, ParameterValue> Headers;
Vector<std::string> URLParameters;
};
class Response {
public:
static Ref<Response> FromCode(HttpStatusCode code);
public:
size_t Length{0};
Ref<Cookies> CookieData{nullptr};
Ref<Session> SessionData{nullptr};
HttpStatusCode Status{HttpStatusCode::OK};
HttpMethod Method{HttpMethod::GET};
public:
std::string GetResponse();
void SetHeader(const std::string &key, ParameterValue &value);
void SetHeader(const std::string &key, const std::string &value);
void AddHeaders(const std::string &key, const Vector<std::string> &values);
void AddHeader(const std::string &key, const std::string &value);
void SetType(const std::string &type);
void AddContent(const std::string &data);
void SetStatus(HttpStatusCode);
void SetMethod(HttpMethod);
void Reset();
Response &operator<<(const std::string &data) {
m_Content << data;
return *this;
}
protected:
std::string TransformHeaders(const std::string &content);
std::string m_Type{"text/html"};
std::stringstream m_Content;
std::unordered_map<std::string, ParameterValue> m_Headers;
};
class MiddleWareHandler;
typedef std::function<const void(Request &, const std::string& body)> ParseFunction;
class RequestHandler {
public:
explicit RequestHandler(const Ref<SocketManager> &manager);
void InitThreads(int count);
void AddRequest(Ref<Request> &request);
void Stop();
void SetRouter(Ref<Router> &router);
void AddSendResponse(SendData);
Ref<MiddleWareHandler> &Middleware() { return m_MiddleWare; }
bool HasBodyParser(const std::string &key) {
return m_ParseFunctions.contains(key);
}
ParseFunction &GetBodyParser(const std::string &key) {
return m_ParseFunctions.at(key);
}
protected:
std::unordered_map<std::string, ParseFunction> m_ParseFunctions;
Ref<Router> m_Router{nullptr};
Ref<SocketManager> m_SocketManager{nullptr};
Server *m_Server{nullptr};
SafeQueue<Ref<Request>> m_Requests{};
ThreadPool m_Pool{"RequestHandler"};
Ref<MiddleWareHandler> m_MiddleWare{nullptr};
friend Server;
};
typedef std::function<bool(Request &, Response &)> RouteFunction;
class Route {
public:
Route() = default;
virtual ~Route() = default;
Route(std::initializer_list<HttpMethod>);
virtual bool Execute(Request &request, Response &response);
virtual bool Get(Request &request, Response &response);
virtual bool Post(Request &request, Response &response);
virtual bool Put(Request &request, Response &response);
virtual bool Patch(Request &request, Response &response);
virtual bool Delete(Request &request, Response &response);
bool Options(Request &request, Response &response);
virtual bool Fallback(Request &request, Response &response);
bool SupportsMethod(Request &request);
virtual bool IsAllowed(Request &request);
void AllowMethod(HttpMethod method);
protected:
bool m_AllowAll{true};
Vector<HttpMethod> m_AllowedMethods;
friend Router;
};
class Router {
public:
Router();
void AddRoute(const std::string &name, const Ref<Route> &route);
Ref<Route> &GetRoute(const std::string &name);
void DeleteRoute(const std::string &name);
Ref<Response> HandleRoute(Ref<Request> &request);
Ref<Route> FindRoute(Ref<Request> &request);
static void AddToArgs(Ref<Request> &request, Vector<std::string> &items);
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;
};
typedef std::optional<Ref<Response>> PreMiddleWareReturn;
struct MiddleWare {
int Pos{0};
virtual PreMiddleWareReturn PreHandle(Request &) { return {}; }
virtual bool PostHandle(Request &, Response &) { return true; }
virtual void Shutdown(Request &, const Response &){};
bool operator<(const MiddleWare *rhs) const { return Pos < rhs->Pos; }
};
class MiddleWareHandler {
public:
PreMiddleWareReturn HandlePre(Ref<Request> &);
void HandlePost(Ref<Request> &, Ref<Response> &);
void Shutdown(Ref<Request> &, Ref<Response> &);
public:
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) {
auto &type = typeid(T);
if (type.before(typeid(MiddleWare)))
SetById(type.name(), instance);
}
template <class T> T &Create() {
return static_cast<T &>(*CreateMiddleWare<T>());
}
template <class T> void Remove() { RemoveById(typeid(T).name()); }
protected:
template <class T> Ref<MiddleWare> CreateMiddleWare() {
return SetById(typeid(T).name(), CreateRef<T>());
}
Ref<MiddleWare> GetById(const char *id);
Ref<MiddleWare> SetById(const char *id, const Ref<MiddleWare> &);
void RemoveById(const char *id);
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(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
} // namespace VWeb

View file

@ -3,6 +3,7 @@ project(VWeb_Example)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(THREADS_PREFER_PTHREAD_FLAG ON) set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
find_package(VWeb 1.0 REQUIRED)
add_executable(VWeb_Example main.cpp) add_executable(VWeb_Example main.cpp)
include_directories(${CMAKE_SOURCE_DIR}/..) include_directories(${CMAKE_SOURCE_DIR}/..)

View file

@ -1,4 +1,4 @@
#include <VWeb.h> #include "VWeb-1.0/VWeb.h"
class MyCompleteController : public VWeb::Route { class MyCompleteController : public VWeb::Route {
public: public: