From 5bb68a7d02a4bfc23c6cd9b235e4c9e07269ecc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maurice=20Gr=C3=B6nwoldt?= Date: Sat, 16 Sep 2023 16:29:03 +0200 Subject: [PATCH] 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. --- .gitattributes | 5 + .gitignore | 1 + CMakeLists.txt | 26 +- CMakeMacros.txt | 25 ++ Includes/CMakeLists.txt | 24 ++ Includes/Cookie.h | 35 ++ Includes/EPollManager.h | 17 + Includes/Http.h | 76 ++++ Includes/Map.h | 59 ++++ Includes/MiddleWare.h | 16 + Includes/MiddleWareHandler.h | 43 +++ Includes/ParameterValue.h | 21 ++ Includes/PreMiddleWare.h | 48 +++ Includes/Queue.h | 87 +++++ Includes/Request.h | 34 ++ Includes/RequestHandler.h | 42 +++ Includes/Response.h | 43 +++ Includes/Route.h | 34 ++ Includes/Router.h | 29 ++ Includes/Server.h | 49 +++ Includes/ServerConfig.h | 22 ++ Includes/Session.h | 31 ++ Includes/Socket.h | 22 ++ Includes/SocketManager.h | 41 +++ Includes/ThreadPool.h | 27 ++ Includes/Types.h | 11 + Includes/VWeb.h | 7 + Source/CMakeLists.txt | 15 + Source/Cookie.cpp | 3 +- Source/EPollManager.cpp | 5 +- Source/InbuildMiddleWare.cpp | 2 +- Source/MiddleWare.cpp | 2 +- Source/RequestHandler.cpp | 3 +- Source/Response.cpp | 4 +- Source/Route.cpp | 4 +- Source/Router.cpp | 4 +- Source/Server.cpp | 4 +- Source/Session.cpp | 2 +- Source/SocketManager.cpp | 7 +- Source/ThreadPool.cpp | 3 +- VWeb.h | 651 ----------------------------------- example/CMakeLists.txt | 1 + example/main.cpp | 2 +- 43 files changed, 902 insertions(+), 685 deletions(-) create mode 100644 .gitattributes create mode 100644 CMakeMacros.txt create mode 100644 Includes/CMakeLists.txt create mode 100644 Includes/Cookie.h create mode 100644 Includes/EPollManager.h create mode 100644 Includes/Http.h create mode 100644 Includes/Map.h create mode 100644 Includes/MiddleWare.h create mode 100644 Includes/MiddleWareHandler.h create mode 100644 Includes/ParameterValue.h create mode 100644 Includes/PreMiddleWare.h create mode 100644 Includes/Queue.h create mode 100644 Includes/Request.h create mode 100644 Includes/RequestHandler.h create mode 100644 Includes/Response.h create mode 100644 Includes/Route.h create mode 100644 Includes/Router.h create mode 100644 Includes/Server.h create mode 100644 Includes/ServerConfig.h create mode 100644 Includes/Session.h create mode 100644 Includes/Socket.h create mode 100644 Includes/SocketManager.h create mode 100644 Includes/ThreadPool.h create mode 100644 Includes/Types.h create mode 100644 Includes/VWeb.h create mode 100644 Source/CMakeLists.txt delete mode 100644 VWeb.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..03c1c82 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto +*.txt text +*.cpp text +*.h text +*.hpp text \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1b7baf3..fd4b3be 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build-debug/ release-build/ build-release/ cmake-build-debug/ +cmake-build-debug-wsl/ .idea/ example/build diff --git a/CMakeLists.txt b/CMakeLists.txt index fc55a14..d0097dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,21 +8,15 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUGSOFT") set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -set(SOURCE_FILES - Source/EPollManager.cpp - Source/RequestHandler.cpp - Source/Server.cpp - Source/SocketManager.cpp - 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(CMakeMacros.txt) + +add_subdirectory(Source) +add_subdirectory(Includes) + include_directories(${CMAKE_SOURCE_DIR}/) +get_property(source_files GLOBAL PROPERTY SRCS) +set(SOURCE_FILES ${source_files}) add_library(VWeb ${SOURCE_FILES}) include(GNUInstallDirs) @@ -31,7 +25,9 @@ set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.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} EXPORT ${PROJECT_NAME}-targets LIBRARY DESTINATION lib/$ diff --git a/CMakeMacros.txt b/CMakeMacros.txt new file mode 100644 index 0000000..bea11db --- /dev/null +++ b/CMakeMacros.txt @@ -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() \ No newline at end of file diff --git a/Includes/CMakeLists.txt b/Includes/CMakeLists.txt new file mode 100644 index 0000000..bb7fd8b --- /dev/null +++ b/Includes/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/Includes/Cookie.h b/Includes/Cookie.h new file mode 100644 index 0000000..68be9a7 --- /dev/null +++ b/Includes/Cookie.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +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 Data; + Cookie &operator[](const std::string &name) { return Data[name]; } + +protected: + static void CookieToString(std::stringstream &stream, Cookie &cookie); +}; +} // namespace VWeb \ No newline at end of file diff --git a/Includes/EPollManager.h b/Includes/EPollManager.h new file mode 100644 index 0000000..273135c --- /dev/null +++ b/Includes/EPollManager.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +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}; +}; +} \ No newline at end of file diff --git a/Includes/Http.h b/Includes/Http.h new file mode 100644 index 0000000..4169996 --- /dev/null +++ b/Includes/Http.h @@ -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 \ No newline at end of file diff --git a/Includes/Map.h b/Includes/Map.h new file mode 100644 index 0000000..1243434 --- /dev/null +++ b/Includes/Map.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace VWeb { +template struct SafeMap { + explicit SafeMap(size_t maxSize = -1UL) : m_MaxSize(maxSize), m_End(false){}; + bool Add(const T &t, S &x) { + std::unique_lock 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 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 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 lck(m_Mutex); + std::unordered_map empty; + std::swap(m_Data, empty); + m_CVFull.notify_all(); + }; + bool Has(T t) { + std::unique_lock lck(m_Mutex); + return m_Data.contains(t); + }; + S &Get(T t) { + std::unique_lock lck(m_Mutex); + return m_Data[t]; + }; + int Size() { return m_Data.size(); }; + +private: + std::unordered_map m_Data{}; + std::mutex m_Mutex{}; + std::condition_variable m_CVFull{}; + const size_t m_MaxSize{}; + std::atomic m_End{}; +}; +} \ No newline at end of file diff --git a/Includes/MiddleWare.h b/Includes/MiddleWare.h new file mode 100644 index 0000000..a48fe53 --- /dev/null +++ b/Includes/MiddleWare.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "Response.h" +#include "Request.h" + +namespace VWeb { +typedef std::optional> 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; } +}; +} \ No newline at end of file diff --git a/Includes/MiddleWareHandler.h b/Includes/MiddleWareHandler.h new file mode 100644 index 0000000..a0032ac --- /dev/null +++ b/Includes/MiddleWareHandler.h @@ -0,0 +1,43 @@ +#pragma once + +#include "MiddleWare.h" +#include "Request.h" +#include "Response.h" + +#include + +namespace VWeb { + +class MiddleWareHandler { +public: + PreMiddleWareReturn HandlePre(Ref &); + void HandlePost(Ref &, Ref &); + void Shutdown(Ref &, Ref &); + +public: + template Ref GetRef() { + return GetById(typeid(T).name()); + } + template T &Get() { + return static_cast(*GetById(typeid(T).name())); + } + template void Set(Ref &instance) { + auto &type = typeid(T); + if (type.before(typeid(MiddleWare))) + SetById(type.name(), instance); + } + template T &Create() { + return static_cast(*CreateMiddleWare()); + } + template void Remove() { RemoveById(typeid(T).name()); } + +protected: + template Ref CreateMiddleWare() { + return SetById(typeid(T).name(), CreateRef()); + } + Ref GetById(const char *id); + Ref SetById(const char *id, const Ref &); + void RemoveById(const char *id); + std::map> m_MiddleWares; +}; +} // namespace VWeb \ No newline at end of file diff --git a/Includes/ParameterValue.h b/Includes/ParameterValue.h new file mode 100644 index 0000000..0fb3765 --- /dev/null +++ b/Includes/ParameterValue.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +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 &Values() { return m_Data; } + +protected: + std::vector m_Data{}; +}; +} \ No newline at end of file diff --git a/Includes/PreMiddleWare.h b/Includes/PreMiddleWare.h new file mode 100644 index 0000000..ed6bcb1 --- /dev/null +++ b/Includes/PreMiddleWare.h @@ -0,0 +1,48 @@ +#pragma once + +#include "MiddleWare.h" + +#include +#include + + +namespace VWeb { +typedef std::function 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 m_GCThread; + std::mutex m_Mutex; + std::unordered_map> 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 \ No newline at end of file diff --git a/Includes/Queue.h b/Includes/Queue.h new file mode 100644 index 0000000..aecd1c1 --- /dev/null +++ b/Includes/Queue.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace VWeb { +template struct SafeQueue { + explicit SafeQueue(size_t maxSize = -1UL) + : m_MaxSize(maxSize), + m_End(false){}; + void Push(const T &t) { + std::unique_lock 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 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 lck(m_Mutex); + m_CVEmpty.notify_all(); + m_CVFull.notify_all(); + }; + void Close() { + m_End = true; + std::lock_guard lck(m_Mutex); + m_CVEmpty.notify_all(); + m_CVFull.notify_all(); + }; + void Clear() { + std::unique_lock lck(m_Mutex); + std::queue empty; + std::swap(m_Queue, empty); + m_CVEmpty.notify_all(); + m_CVFull.notify_all(); + }; + std::optional Pop() { + std::unique_lock 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 WaitAndPop() { + std::unique_lock 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 &GetQueue() { return m_Queue; } + void Flush() { + while (!m_Queue.empty()) + m_CVEmpty.notify_one(); + m_End = true; + m_CVEmpty.notify_all(); + } + +private: + std::queue m_Queue; + std::mutex m_Mutex; + std::condition_variable m_CVEmpty, m_CVFull; + const size_t m_MaxSize; + std::atomic m_End; +}; +} \ No newline at end of file diff --git a/Includes/Request.h b/Includes/Request.h new file mode 100644 index 0000000..7815052 --- /dev/null +++ b/Includes/Request.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Cookie.h" +#include "Http.h" +#include "ParameterValue.h" +#include "Session.h" + +#include +#include + +namespace VWeb { +struct Request { +public: + int ID; + std::string Body; + HttpMethod Method{HttpMethod::GET}; + std::string URI; + Ref SessionData; + Ref 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 Parameters; + std::unordered_map Headers; + std::vector URLParameters; +}; +} // namespace VWeb \ No newline at end of file diff --git a/Includes/RequestHandler.h b/Includes/RequestHandler.h new file mode 100644 index 0000000..152f948 --- /dev/null +++ b/Includes/RequestHandler.h @@ -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 + +namespace VWeb { +class Server; +typedef std::function + ParseFunction; +class RequestHandler { +public: + explicit RequestHandler(const Ref &manager); + void InitThreads(int count); + void AddRequest(Ref &request); + void Stop(); + void SetRouter(Ref &router); + void AddSendResponse(SendData); + Ref &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 m_ParseFunctions; + Ref m_Router{nullptr}; + Ref m_SocketManager{nullptr}; + Server *m_Server{nullptr}; + SafeQueue> m_Requests{}; + ThreadPool m_Pool{"RequestHandler"}; + Ref m_MiddleWare{nullptr}; + friend Server; +}; +} // namespace VWeb \ No newline at end of file diff --git a/Includes/Response.h b/Includes/Response.h new file mode 100644 index 0000000..983f3d9 --- /dev/null +++ b/Includes/Response.h @@ -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 FromCode(HttpStatusCode code); + +public: + size_t Length{0}; + Ref CookieData{nullptr}; + Ref 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 &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 m_Headers; +}; +} // namespace VWeb \ No newline at end of file diff --git a/Includes/Route.h b/Includes/Route.h new file mode 100644 index 0000000..5f9aa9d --- /dev/null +++ b/Includes/Route.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Http.h" +#include +#include "Request.h" +#include "Response.h" +#include + +namespace VWeb { +class Router; +class Route { +public: + Route() = default; + virtual ~Route() = default; + Route(std::initializer_list); + 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 m_AllowedMethods; + friend Router; +}; +} \ No newline at end of file diff --git a/Includes/Router.h b/Includes/Router.h new file mode 100644 index 0000000..03088a9 --- /dev/null +++ b/Includes/Router.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Route.h" + +#include +#include + +namespace VWeb { +typedef std::function RouteFunction; +class Router { +public: + Router(); + void AddRoute(const std::string &name, const Ref &route); + Ref &GetRoute(const std::string &name); + void DeleteRoute(const std::string &name); + + Ref HandleRoute(Ref &request); + Ref FindRoute(Ref &request); + static void AddToArgs(Ref &request, std::vector &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> m_Routes; +}; +} // namespace VWeb \ No newline at end of file diff --git a/Includes/Server.h b/Includes/Server.h new file mode 100644 index 0000000..52675dd --- /dev/null +++ b/Includes/Server.h @@ -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 &GetRouter() { return m_Router; } + Ref &GetServerConfig() { return m_ServerConfig; } + void AddRoute(const std::string &path, const Ref &route); + void RemoveRoute(const std::string &path); + + Ref &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 m_Router; + Ref m_ServerConfig; + Ref m_RequestHandler; + SafeMap m_RawRequest{60000}; + SafeMap m_OutRequest{60000}; + Ref m_WorkerThread; + std::mutex m_Mutex; + bool m_IsExit{false}; + +protected: + friend RequestHandler; +}; +} \ No newline at end of file diff --git a/Includes/ServerConfig.h b/Includes/ServerConfig.h new file mode 100644 index 0000000..dcbe991 --- /dev/null +++ b/Includes/ServerConfig.h @@ -0,0 +1,22 @@ +#pragma once + +#include "EPollManager.h" +#include "SocketManager.h" +#include "Types.h" + +#include + +namespace VWeb { +struct ServerConfig { + int Port{9020}; + int MaxBufferSize{-1}; + int BufferSize{16384}; + int WorkerThreads{-1}; + int MaxEvents{5000}; + bool AllowSharedLibs{true}; + Ref EPoll{nullptr}; + Ref Socket{nullptr}; + std::string ErrorDir; + std::string MimeFiles; +}; +} \ No newline at end of file diff --git a/Includes/Session.h b/Includes/Session.h new file mode 100644 index 0000000..4a55146 --- /dev/null +++ b/Includes/Session.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include "Types.h" +#include + +namespace VWeb { +using ms = std::chrono::duration; +struct SessionData { + virtual ~SessionData() = default; + template T *As() { return reinterpret_cast(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 &operator[](const std::string &key) { return m_Data[key]; } + void SetSessionData(const std::string &key, const Ref &data) { + m_Data[key] = data; + } + bool ContainsData() { return !m_Data.empty(); } + +protected: + std::chrono::time_point m_LastCall = + std::chrono::system_clock::now(); + std::unordered_map> m_Data; +}; +} \ No newline at end of file diff --git a/Includes/Socket.h b/Includes/Socket.h new file mode 100644 index 0000000..2aa18cd --- /dev/null +++ b/Includes/Socket.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +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 \ No newline at end of file diff --git a/Includes/SocketManager.h b/Includes/SocketManager.h new file mode 100644 index 0000000..9015758 --- /dev/null +++ b/Includes/SocketManager.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Socket.h" + +#include + +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 &); + ~SocketManager(); + Accept Handle(); + bool SetSendListen(int socketID); + bool SetReadListen(int socketID); + Ref &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 m_ServerConfig{}; + struct sockaddr_in m_Address { + 0 + }; + int m_AddressLength{sizeof(m_Address)}; + bool m_IsErrored{false}; +}; +} // namespace VWeb \ No newline at end of file diff --git a/Includes/ThreadPool.h b/Includes/ThreadPool.h new file mode 100644 index 0000000..c995a6b --- /dev/null +++ b/Includes/ThreadPool.h @@ -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 &); + void Create(); + void Stop(); + void Execute(); + +protected: + std::string m_Name; + int m_ThreadCount{1}; + bool m_IsCreated{false}; + std::vector m_Threads{}; + SafeQueue> m_Queue; + bool m_IsDone{false}; +}; +} // namespace VWeb \ No newline at end of file diff --git a/Includes/Types.h b/Includes/Types.h new file mode 100644 index 0000000..6b291a9 --- /dev/null +++ b/Includes/Types.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace VWeb { +template using Ref = std::shared_ptr; +template +constexpr Ref CreateRef(Args &&...args) { + return std::make_shared(std::forward(args)...); +} +} // namespace VWeb \ No newline at end of file diff --git a/Includes/VWeb.h b/Includes/VWeb.h new file mode 100644 index 0000000..6305a7b --- /dev/null +++ b/Includes/VWeb.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Request.h" +#include "Response.h" +#include "Route.h" +#include "Server.h" +#include "ServerConfig.h" \ No newline at end of file diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt new file mode 100644 index 0000000..e2c98b6 --- /dev/null +++ b/Source/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/Source/Cookie.cpp b/Source/Cookie.cpp index 76f593d..9885f46 100644 --- a/Source/Cookie.cpp +++ b/Source/Cookie.cpp @@ -1,7 +1,6 @@ +#include "Includes/VWeb.h" #include "StringUtils.h" -#include - namespace VWeb { void Cookies::Remove(const std::string &name) { if (Data.contains(name)) diff --git a/Source/EPollManager.cpp b/Source/EPollManager.cpp index 3d76d38..7ae8894 100644 --- a/Source/EPollManager.cpp +++ b/Source/EPollManager.cpp @@ -1,4 +1,7 @@ -#include +#include "Includes/VWeb.h" + +#include + namespace VWeb { EPollManager::EPollManager() { m_EpollID = epoll_create1(0); diff --git a/Source/InbuildMiddleWare.cpp b/Source/InbuildMiddleWare.cpp index d6b068b..8d5aa60 100644 --- a/Source/InbuildMiddleWare.cpp +++ b/Source/InbuildMiddleWare.cpp @@ -1,5 +1,5 @@ +#include "Includes/PreMiddleWare.h" #include "StringUtils.h" -#include "VWeb.h" #include #include diff --git a/Source/MiddleWare.cpp b/Source/MiddleWare.cpp index 3b0e8b9..11af862 100644 --- a/Source/MiddleWare.cpp +++ b/Source/MiddleWare.cpp @@ -1,4 +1,4 @@ -#include +#include "Includes/VWeb.h" namespace VWeb { std::optional> diff --git a/Source/RequestHandler.cpp b/Source/RequestHandler.cpp index a098130..c76436b 100644 --- a/Source/RequestHandler.cpp +++ b/Source/RequestHandler.cpp @@ -1,7 +1,6 @@ +#include "Includes/VWeb.h" #include "StringUtils.h" -#include - namespace VWeb { HttpMethod StringToHTTPMethod(std::string &method) { diff --git a/Source/Response.cpp b/Source/Response.cpp index 6fd2a76..7a9451e 100644 --- a/Source/Response.cpp +++ b/Source/Response.cpp @@ -1,4 +1,4 @@ -#include +#include "Includes/VWeb.h" namespace VWeb { // clang-format off @@ -87,7 +87,7 @@ void Response::SetHeader(const std::string &key, ParameterValue &value) { m_Headers[key] = value; } void Response::AddHeaders(const std::string &key, - const Vector &values) { + const std::vector &values) { auto &element = m_Headers[key]; for (const auto &value : values) element.Add(value); diff --git a/Source/Route.cpp b/Source/Route.cpp index 2faa98d..c826ca2 100644 --- a/Source/Route.cpp +++ b/Source/Route.cpp @@ -1,6 +1,6 @@ -#include -#include +#include "Includes/VWeb.h" +#include namespace VWeb { diff --git a/Source/Router.cpp b/Source/Router.cpp index bca3c5c..8fba206 100644 --- a/Source/Router.cpp +++ b/Source/Router.cpp @@ -1,6 +1,6 @@ +#include "Includes/VWeb.h" #include "StringUtils.h" -#include #include #include @@ -119,7 +119,7 @@ Ref Router::FindRoute(Ref &request) { return nullptr; } -void Router::AddToArgs(Ref &request, Vector &items) { +void Router::AddToArgs(Ref &request, std::vector &items) { request->URLParameters.push_back(items[items.size() - 1]); items.pop_back(); } diff --git a/Source/Server.cpp b/Source/Server.cpp index 02f7268..6f0bf2e 100644 --- a/Source/Server.cpp +++ b/Source/Server.cpp @@ -1,4 +1,6 @@ -#include +#include "Includes/PreMiddleWare.h" +#include "Includes/VWeb.h" + #include namespace VWeb { diff --git a/Source/Session.cpp b/Source/Session.cpp index 1e4b983..52b8380 100644 --- a/Source/Session.cpp +++ b/Source/Session.cpp @@ -1,4 +1,4 @@ -#include +#include "Includes/VWeb.h" namespace VWeb { bool Session::IsValid() { diff --git a/Source/SocketManager.cpp b/Source/SocketManager.cpp index cd70a4a..c2b0cfd 100644 --- a/Source/SocketManager.cpp +++ b/Source/SocketManager.cpp @@ -1,4 +1,6 @@ -#include +#include "Includes/VWeb.h" + +#include namespace VWeb { #pragma region VWebSocketUtils @@ -89,7 +91,8 @@ void SocketManager::Init() { if (listen(m_SocketID, SOMAXCONN) < 0) 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"); } void SocketManager::Errored(const std::string &data) { diff --git a/Source/ThreadPool.cpp b/Source/ThreadPool.cpp index 1ad0e31..c60b5a5 100644 --- a/Source/ThreadPool.cpp +++ b/Source/ThreadPool.cpp @@ -1,4 +1,5 @@ -#include +#include "Includes/VWeb.h" + #include #include diff --git a/VWeb.h b/VWeb.h deleted file mode 100644 index 42e2790..0000000 --- a/VWeb.h +++ /dev/null @@ -1,651 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace VWeb { - -template using Ref = std::shared_ptr; -template using Vector = std::vector; -template -constexpr Ref CreateRef(Args &&...args) { - return std::make_shared(std::forward(args)...); -} -typedef std::mutex Mutex; - -#pragma region SAFESTRUCTS -template struct SafeMap { - explicit SafeMap(size_t maxSize = -1UL) : m_MaxSize(maxSize), m_End(false){}; - bool Add(const T &t, S &x) { - std::unique_lock 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 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 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 lck(m_Mutex); - std::unordered_map empty; - std::swap(m_Data, empty); - m_CVFull.notify_all(); - }; - bool Has(T t) { - std::unique_lock lck(m_Mutex); - return m_Data.contains(t); - }; - S &Get(T t) { - std::unique_lock lck(m_Mutex); - return m_Data[t]; - }; - int Size() { return m_Data.size(); }; - -private: - std::unordered_map m_Data{}; - std::mutex m_Mutex{}; - std::condition_variable m_CVFull{}; - const size_t m_MaxSize{}; - std::atomic m_End{}; -}; - -template struct SafeQueue { - explicit SafeQueue(size_t maxSize = -1UL) - : m_MaxSize(maxSize), - m_End(false){}; - void Push(const T &t) { - std::unique_lock 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 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 lck(m_Mutex); - m_CVEmpty.notify_all(); - m_CVFull.notify_all(); - }; - void Close() { - m_End = true; - std::lock_guard lck(m_Mutex); - m_CVEmpty.notify_all(); - m_CVFull.notify_all(); - }; - void Clear() { - std::unique_lock lck(m_Mutex); - std::queue empty; - std::swap(m_Queue, empty); - m_CVEmpty.notify_all(); - m_CVFull.notify_all(); - }; - std::optional Pop() { - std::unique_lock 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 WaitAndPop() { - std::unique_lock 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 &GetQueue() { return m_Queue; } - void Flush() { - while (!m_Queue.empty()) - m_CVEmpty.notify_one(); - m_End = true; - m_CVEmpty.notify_all(); - } - -private: - std::queue m_Queue; - std::mutex m_Mutex; - std::condition_variable m_CVEmpty, m_CVFull; - const size_t m_MaxSize; - std::atomic 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 &); - void Create(); - void Stop(); - void Execute(); - -protected: - std::string m_Name; - int m_ThreadCount{1}; - bool m_IsCreated{false}; - Vector m_Threads{}; - SafeQueue> 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 EPoll{nullptr}; - Ref 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 &); - ~SocketManager(); - Accept Handle(); - bool SetSendListen(int socketID); - bool SetReadListen(int socketID); - Ref &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 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 &GetRouter() { return m_Router; } - Ref &GetServerConfig() { return m_ServerConfig; } - void AddRoute(const std::string &path, const Ref &route); - void RemoveRoute(const std::string &path); - - Ref &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 m_Router; - Ref m_ServerConfig; - Ref m_RequestHandler; - SafeMap m_RawRequest{60000}; - SafeMap m_OutRequest{60000}; - Ref m_WorkerThread; - Mutex m_Mutex; - bool m_IsExit{false}; - -protected: - friend RequestHandler; -}; -#pragma region VWEB_ROUTING -using ms = std::chrono::duration; -struct SessionData { - virtual ~SessionData() = default; - template T *As() { return reinterpret_cast(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 &operator[](const std::string &key) { return m_Data[key]; } - void SetSessionData(const std::string &key, const Ref &data) { - m_Data[key] = data; - } - bool ContainsData() { return !m_Data.empty(); } - -protected: - std::chrono::time_point m_LastCall = - std::chrono::system_clock::now(); - std::unordered_map> 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 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 &Values() { return m_Data; } - -protected: - Vector m_Data{}; -}; - -struct Request { -public: - int ID; - std::string Body; - HttpMethod Method{HttpMethod::GET}; - std::string URI; - Ref SessionData; - Ref 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 Parameters; - std::unordered_map Headers; - Vector URLParameters; -}; -class Response { -public: - static Ref FromCode(HttpStatusCode code); -public: - size_t Length{0}; - Ref CookieData{nullptr}; - Ref 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 &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 m_Headers; -}; -class MiddleWareHandler; -typedef std::function ParseFunction; -class RequestHandler { -public: - explicit RequestHandler(const Ref &manager); - void InitThreads(int count); - void AddRequest(Ref &request); - void Stop(); - void SetRouter(Ref &router); - void AddSendResponse(SendData); - Ref &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 m_ParseFunctions; - Ref m_Router{nullptr}; - Ref m_SocketManager{nullptr}; - Server *m_Server{nullptr}; - SafeQueue> m_Requests{}; - ThreadPool m_Pool{"RequestHandler"}; - Ref m_MiddleWare{nullptr}; - friend Server; -}; - -typedef std::function RouteFunction; -class Route { -public: - Route() = default; - virtual ~Route() = default; - Route(std::initializer_list); - 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 m_AllowedMethods; - friend Router; -}; - -class Router { -public: - Router(); - void AddRoute(const std::string &name, const Ref &route); - Ref &GetRoute(const std::string &name); - void DeleteRoute(const std::string &name); - - Ref HandleRoute(Ref &request); - Ref FindRoute(Ref &request); - static void AddToArgs(Ref &request, Vector &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> m_Routes; -}; - -typedef std::optional> 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 &); - void HandlePost(Ref &, Ref &); - void Shutdown(Ref &, Ref &); - -public: - template Ref GetRef() { - return GetById(typeid(T).name()); - } - template T &Get() { - return static_cast(*GetById(typeid(T).name())); - } - template void Set(Ref &instance) { - auto &type = typeid(T); - if (type.before(typeid(MiddleWare))) - SetById(type.name(), instance); - } - template T &Create() { - return static_cast(*CreateMiddleWare()); - } - template void Remove() { RemoveById(typeid(T).name()); } - -protected: - template Ref CreateMiddleWare() { - return SetById(typeid(T).name(), CreateRef()); - } - Ref GetById(const char *id); - Ref SetById(const char *id, const Ref &); - void RemoveById(const char *id); - std::map> m_MiddleWares; -}; - -typedef std::function 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 m_GCThread; - std::mutex m_Mutex; - std::unordered_map> 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 \ No newline at end of file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index d4901ce..a704415 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -3,6 +3,7 @@ project(VWeb_Example) set(CMAKE_CXX_STANDARD 20) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) +find_package(VWeb 1.0 REQUIRED) add_executable(VWeb_Example main.cpp) include_directories(${CMAKE_SOURCE_DIR}/..) diff --git a/example/main.cpp b/example/main.cpp index de24dd5..c39a43c 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -1,4 +1,4 @@ -#include +#include "VWeb-1.0/VWeb.h" class MyCompleteController : public VWeb::Route { public: