diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a42bde..b24834b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,13 +17,14 @@ set(SOURCE_FILES Source/StringUtils.cpp Source/Cookie.cpp Source/Session.cpp - Source/Response.cpp Source/InbuildMiddleWare.cpp) + Source/Response.cpp + Source/InbuildMiddleWare.cpp) include_directories(${CMAKE_SOURCE_DIR}/) add_library(VWeb ${SOURCE_FILES}) -set(mode release) +set(mode Release) if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(mode debug) + set(mode Debug) endif () set(target_file ${CMAKE_SOURCE_DIR}/dist/libVWeb.${mode}.a) add_custom_command(TARGET VWeb POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${target_file}) diff --git a/Source/Cookie.cpp b/Source/Cookie.cpp index a849d93..76f593d 100644 --- a/Source/Cookie.cpp +++ b/Source/Cookie.cpp @@ -103,5 +103,6 @@ void Cookies::CookieToString(std::stringstream &stream, Cookie &cookie) { view << "; SameSite=" << sameSiteValue; } view << "\n"; + stream << view.str(); } } // namespace VWeb \ No newline at end of file diff --git a/Source/InbuildMiddleWare.cpp b/Source/InbuildMiddleWare.cpp index f4d70ca..9d4f2d1 100644 --- a/Source/InbuildMiddleWare.cpp +++ b/Source/InbuildMiddleWare.cpp @@ -68,7 +68,7 @@ PreMiddleWareReturn SessionManager::PreHandle(Request &request) { return {}; } -bool SessionManager::PostHandle(const Request &request, Response &response) { +bool SessionManager::PostHandle(Request &request, Response &response) { if (response.SessionData->Id.empty() && response.SessionData->ContainsData()) { response.SessionData->Update(); @@ -80,8 +80,9 @@ bool SessionManager::PostHandle(const Request &request, Response &response) { } m_Sessions[response.SessionData->Id] = response.SessionData; auto &sidCookie = response.CookieData->Get("sid"); + sidCookie.Name = "sid"; sidCookie.HttpOnly = true; - sidCookie.Secure = true; + sidCookie.SameSite = CookieSameSite::Strict; sidCookie.Value = response.SessionData->Id; } return true; @@ -101,7 +102,7 @@ void SessionManager::GC() { #pragma endregion SESSION #pragma region COOKIES PreMiddleWareReturn CookieManager::PreHandle(Request &request) { - auto &cookieHeaders = request.Header("Cookie"); + auto &cookieHeaders = request.Header("cookie"); auto &cookies = request.CookieData; if (cookieHeaders.Size() > 0) { auto &values = cookieHeaders.Values(); @@ -118,4 +119,4 @@ PreMiddleWareReturn CookieManager::PreHandle(Request &request) { return {}; } #pragma endregion COOKIES -} // namespace VWeb \ No newline at end of file +} // namespace VWeb diff --git a/Source/RequestHandler.cpp b/Source/RequestHandler.cpp index e309b46..a098130 100644 --- a/Source/RequestHandler.cpp +++ b/Source/RequestHandler.cpp @@ -27,6 +27,7 @@ bool ParseRequest(Ref &request) { if (index != std::string::npos) { auto key = line.substr(0, index); String::Trim(key); + String::ToLowerCase(key); request->Header(key).Add(String::TrimCopy(line.substr(index + 1))); } else if (line.find("HTTP")) { auto headers = String::Split(line, " "); @@ -41,6 +42,49 @@ bool ParseRequest(Ref &request) { return true; } +void ParseParameterString(Request &req, const std::string &toParse) { + auto data = String::Split(toParse, "&"); + for (auto &item : data) { + auto d = String::Split(item, "=", 1); + req.Parameter(d[0]).Add(d.size() > 1 ? d[1] : "true"); + } +} + +std::string GetPostBody(const std::string& originalBody) +{ + auto body = String::Split(originalBody, "\r\n\r\n", 1); + if (body.size() > 1 && ! body[body.size() - 1].empty()) + return String::TrimCopy(String::UrlDecode(body[body.size() - 1])); + return {}; +} + +void ParseParameters(Request &request, RequestHandler &requestHandler) { + auto &uri = request.URI; + size_t hasURLParameters = uri.find('?'); + if (hasURLParameters != std::string::npos) { + ParseParameterString(request, uri.substr(hasURLParameters + 1)); + request.URI = uri.substr (0, hasURLParameters); + } + + if (request.Method == HttpMethod::HEAD || request.Method == HttpMethod::GET || + request.Method == HttpMethod::OPTIONS) + return; + + // POST :D + auto &contentType = request.Header("content-type"); + if (contentType.Size() == 0) + return; + auto &key = contentType.GetFirst(); + if (key == "application/x-www-form-urlencoded") { + ParseParameterString(request, GetPostBody(request.Body)); + } else { + if (requestHandler.HasBodyParser(key)) { + auto &func = requestHandler.GetBodyParser(key); + func(request, GetPostBody(request.Body)); + } + } +} + struct RequestJob : public WorkerJob { Ref MRequest{}; Ref MRouter{}; @@ -53,6 +97,7 @@ struct RequestJob : public WorkerJob { SocketUtils::Close(MRequest->ID); return; } + ParseParameters(*MRequest, *MRequestHandler); MRequest->CookieData = CreateRef(); MRequest->SessionData = CreateRef(); Ref response; @@ -72,7 +117,8 @@ struct RequestJob : public WorkerJob { } }; RequestHandler::RequestHandler(const Ref &manager) - : m_SocketManager(manager), m_MiddleWare(CreateRef()) { + : m_SocketManager(manager), + m_MiddleWare(CreateRef()) { m_MiddleWare = CreateRef(); } void RequestHandler::InitThreads(int count) { @@ -89,12 +135,8 @@ void RequestHandler::AddRequest(Ref &request) { m_Pool.Dispatch(job); } } -void RequestHandler::Stop() { - m_Pool.Stop(); -} -void RequestHandler::SetRouter(Ref &router) { - m_Router = router; -} +void RequestHandler::Stop() { m_Pool.Stop(); } +void RequestHandler::SetRouter(Ref &router) { m_Router = router; } void RequestHandler::AddSendResponse(SendData sendData) { auto id = sendData.SocketID; m_Server->m_OutRequest.Add(id, sendData); @@ -103,4 +145,4 @@ void RequestHandler::AddSendResponse(SendData sendData) { m_Server->m_OutRequest.Remove(id); } } -} // namespace VWeb \ No newline at end of file +} // namespace VWeb diff --git a/Source/Route.cpp b/Source/Route.cpp index fc0a49f..2faa98d 100644 --- a/Source/Route.cpp +++ b/Source/Route.cpp @@ -84,4 +84,4 @@ void Route::AllowMethod(HttpMethod method) { if (std::find(m_AllowedMethods.begin(), m_AllowedMethods.end(), method) == m_AllowedMethods.end()) m_AllowedMethods.push_back(method); } -} // namespace VWeb \ No newline at end of file +} // namespace VWeb diff --git a/Source/Router.cpp b/Source/Router.cpp index 60bfc66..bca3c5c 100644 --- a/Source/Router.cpp +++ b/Source/Router.cpp @@ -157,4 +157,4 @@ void Router::Delete(const std::string &path, RouteFunction func) { if (route) route->DeleteFunc = std::move(func); } -} // namespace VWeb \ No newline at end of file +} // namespace VWeb diff --git a/Source/StringUtils.cpp b/Source/StringUtils.cpp index 215ba85..07e066e 100644 --- a/Source/StringUtils.cpp +++ b/Source/StringUtils.cpp @@ -19,12 +19,14 @@ static void StringRightTrim(std::string &s) { } std::vector String::Split(const std::string &s, - const std::string &delimiter) { + const std::string &delimiter, + int limit) { size_t pos_start = 0, pos_end, delim_len = delimiter.length(); std::string token; std::vector res; - while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { + while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos && + (limit == -1 || res.size() < (size_t)limit)) { token = s.substr(pos_start, pos_end - pos_start); pos_start = pos_end + delim_len; res.push_back(token); diff --git a/Source/StringUtils.h b/Source/StringUtils.h index 5319915..b395048 100644 --- a/Source/StringUtils.h +++ b/Source/StringUtils.h @@ -7,7 +7,8 @@ namespace VWeb { class String { public: static std::vector Split(const std::string& s, - const std::string& delimiter); + const std::string& delimiter, + int limit = -1); static std::string Join(const std::vector& items, const std::string& delimiter); static int ToNumber(std::string& string, int def); static void ToLowerCase(std::string& value); diff --git a/VWeb.h b/VWeb.h index 599835d..f5681ce 100644 --- a/VWeb.h +++ b/VWeb.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -275,7 +276,8 @@ public: void AddRoute(const std::string &path, const Ref &route); void RemoveRoute(const std::string &path); - Ref& Middleware(); + Ref &Middleware(); + protected: void Execute(); void OutgoingExecute(epoll_event &event); @@ -300,6 +302,7 @@ protected: using ms = std::chrono::duration; struct SessionData { virtual ~SessionData() = default; + template T *As() { return reinterpret_cast(this); } }; struct Session { std::string Id; @@ -309,7 +312,9 @@ struct Session { 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; } + void SetSessionData(const std::string &key, const Ref &data) { + m_Data[key] = data; + } bool ContainsData() { return !m_Data.empty(); } protected: @@ -391,15 +396,15 @@ enum class HttpMethod { }; enum class CookieSameSite { Invalid = 0, Strict, Lax, None }; struct Cookie { - std::string Name; - std::string Value; - std::string Expires; + std::string Name{}; + std::string Value{}; + std::string Expires{}; int MaxAge{}; - std::string Domain; - std::string Path; + std::string Domain{}; + std::string Path{"/"}; bool Secure{}; bool HttpOnly{}; - CookieSameSite SameSite{CookieSameSite::Invalid}; + CookieSameSite SameSite{CookieSameSite::Strict}; bool IsOld = false; }; struct Cookies { @@ -442,9 +447,13 @@ public: 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 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(); } + 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; @@ -483,6 +492,7 @@ protected: std::unordered_map m_Headers; }; class MiddleWareHandler; +typedef std::function ParseFunction; class RequestHandler { public: explicit RequestHandler(const Ref &manager); @@ -492,8 +502,15 @@ public: 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}; @@ -503,7 +520,7 @@ protected: friend Server; }; -typedef std::function RouteFunction; +typedef std::function RouteFunction; class Route { public: Route() = default; @@ -540,20 +557,20 @@ public: 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); + 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 &){}; + 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; } }; @@ -564,17 +581,22 @@ public: void Shutdown(Ref &, Ref &); public: - template Ref GetRef() { return GetById(typeid(T).name()); } - template T& Get() { return static_cast(*GetById(typeid(T).name())); } + 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 T &Create() { + return static_cast(*CreateMiddleWare()); } template void Remove() { RemoveById(typeid(T).name()); } + protected: template Ref CreateMiddleWare() { return SetById(typeid(T).name(), CreateRef()); @@ -585,13 +607,16 @@ protected: std::map> m_MiddleWares; }; -typedef std::function AuthFunction; +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); } + void SetAuthMethod(AuthFunction function) { + m_AuthFunction = std::move(function); + } + protected: AuthFunction m_AuthFunction{nullptr}; }; @@ -600,10 +625,12 @@ class SessionManager : public MiddleWare { public: SessionManager(); ~SessionManager(); - PreMiddleWareReturn PreHandle(Request& request) override; - bool PostHandle(const Request& request, Response& response) override; + PreMiddleWareReturn PreHandle(Request &request) override; + bool PostHandle(Request &request, Response &response) override; + protected: void GC(); + protected: Ref m_GCThread; std::mutex m_Mutex; @@ -616,9 +643,9 @@ class CookieManager : public MiddleWare { public: CookieManager() = default; ~CookieManager() = default; - PreMiddleWareReturn PreHandle(Request&) override; + PreMiddleWareReturn PreHandle(Request &) override; }; #pragma endregion VWEB_ROUTING #pragma endregion VWEB -} // namespace VWeb +} // namespace VWeb \ No newline at end of file diff --git a/dist/libVWeb.Debug.a b/dist/libVWeb.Debug.a new file mode 100644 index 0000000..b4002cb Binary files /dev/null and b/dist/libVWeb.Debug.a differ diff --git a/dist/libVWeb.Release.a b/dist/libVWeb.Release.a new file mode 100644 index 0000000..64dfcf7 Binary files /dev/null and b/dist/libVWeb.Release.a differ diff --git a/dist/libVWeb.debug.a b/dist/libVWeb.debug.a deleted file mode 100644 index 547e15c..0000000 Binary files a/dist/libVWeb.debug.a and /dev/null differ diff --git a/dist/libVWeb.release.a b/dist/libVWeb.release.a deleted file mode 100644 index 701b56c..0000000 Binary files a/dist/libVWeb.release.a and /dev/null differ diff --git a/example/main.cpp b/example/main.cpp index 5584582..59584d6 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -1,5 +1,20 @@ #include +class MyCompleteController : public VWeb::Route { +public: + bool Get(const VWeb::Request&, VWeb::Response& response) { + response << "MyCompleteController: GET"; + return true; + } + bool Post(const VWeb::Request&, VWeb::Response& response) { + response << "MyCompleteController: POST"; + return true; + } + + bool IsAllowed(const VWeb::Request& request) { + return request.HasHeader("Auth"); + } +}; bool Ping(const VWeb::Request&, VWeb::Response& response) { response << "Pong"; return true;