#pragma once #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(const 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; }; 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::Invalid}; 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; 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; } protected: 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(const Request &, Response &){ return true; } virtual void Shutdown(const 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(const 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