Added Post Body Parsing

This commit is contained in:
Maurice Grönwoldt 2022-11-09 14:34:20 +01:00
parent afd8f4d999
commit 5c8c4e86b2
14 changed files with 139 additions and 49 deletions

View file

@ -17,13 +17,14 @@ set(SOURCE_FILES
Source/StringUtils.cpp Source/StringUtils.cpp
Source/Cookie.cpp Source/Cookie.cpp
Source/Session.cpp Source/Session.cpp
Source/Response.cpp Source/InbuildMiddleWare.cpp) Source/Response.cpp
Source/InbuildMiddleWare.cpp)
include_directories(${CMAKE_SOURCE_DIR}/) include_directories(${CMAKE_SOURCE_DIR}/)
add_library(VWeb ${SOURCE_FILES}) add_library(VWeb ${SOURCE_FILES})
set(mode release) set(mode Release)
if (CMAKE_BUILD_TYPE STREQUAL "Debug") if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(mode debug) set(mode Debug)
endif () endif ()
set(target_file ${CMAKE_SOURCE_DIR}/dist/libVWeb.${mode}.a) set(target_file ${CMAKE_SOURCE_DIR}/dist/libVWeb.${mode}.a)
add_custom_command(TARGET VWeb POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:VWeb> ${target_file}) add_custom_command(TARGET VWeb POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:VWeb> ${target_file})

View file

@ -103,5 +103,6 @@ void Cookies::CookieToString(std::stringstream &stream, Cookie &cookie) {
view << "; SameSite=" << sameSiteValue; view << "; SameSite=" << sameSiteValue;
} }
view << "\n"; view << "\n";
stream << view.str();
} }
} // namespace VWeb } // namespace VWeb

View file

@ -68,7 +68,7 @@ PreMiddleWareReturn SessionManager::PreHandle(Request &request) {
return {}; return {};
} }
bool SessionManager::PostHandle(const Request &request, Response &response) { bool SessionManager::PostHandle(Request &request, Response &response) {
if (response.SessionData->Id.empty() && if (response.SessionData->Id.empty() &&
response.SessionData->ContainsData()) { response.SessionData->ContainsData()) {
response.SessionData->Update(); response.SessionData->Update();
@ -80,8 +80,9 @@ bool SessionManager::PostHandle(const Request &request, Response &response) {
} }
m_Sessions[response.SessionData->Id] = response.SessionData; m_Sessions[response.SessionData->Id] = response.SessionData;
auto &sidCookie = response.CookieData->Get("sid"); auto &sidCookie = response.CookieData->Get("sid");
sidCookie.Name = "sid";
sidCookie.HttpOnly = true; sidCookie.HttpOnly = true;
sidCookie.Secure = true; sidCookie.SameSite = CookieSameSite::Strict;
sidCookie.Value = response.SessionData->Id; sidCookie.Value = response.SessionData->Id;
} }
return true; return true;
@ -101,7 +102,7 @@ void SessionManager::GC() {
#pragma endregion SESSION #pragma endregion SESSION
#pragma region COOKIES #pragma region COOKIES
PreMiddleWareReturn CookieManager::PreHandle(Request &request) { PreMiddleWareReturn CookieManager::PreHandle(Request &request) {
auto &cookieHeaders = request.Header("Cookie"); auto &cookieHeaders = request.Header("cookie");
auto &cookies = request.CookieData; auto &cookies = request.CookieData;
if (cookieHeaders.Size() > 0) { if (cookieHeaders.Size() > 0) {
auto &values = cookieHeaders.Values(); auto &values = cookieHeaders.Values();

View file

@ -27,6 +27,7 @@ bool ParseRequest(Ref<Request> &request) {
if (index != std::string::npos) { if (index != std::string::npos) {
auto key = line.substr(0, index); auto key = line.substr(0, index);
String::Trim(key); String::Trim(key);
String::ToLowerCase(key);
request->Header(key).Add(String::TrimCopy(line.substr(index + 1))); request->Header(key).Add(String::TrimCopy(line.substr(index + 1)));
} else if (line.find("HTTP")) { } else if (line.find("HTTP")) {
auto headers = String::Split(line, " "); auto headers = String::Split(line, " ");
@ -41,6 +42,49 @@ bool ParseRequest(Ref<Request> &request) {
return true; 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 { struct RequestJob : public WorkerJob {
Ref<Request> MRequest{}; Ref<Request> MRequest{};
Ref<Router> MRouter{}; Ref<Router> MRouter{};
@ -53,6 +97,7 @@ struct RequestJob : public WorkerJob {
SocketUtils::Close(MRequest->ID); SocketUtils::Close(MRequest->ID);
return; return;
} }
ParseParameters(*MRequest, *MRequestHandler);
MRequest->CookieData = CreateRef<Cookies>(); MRequest->CookieData = CreateRef<Cookies>();
MRequest->SessionData = CreateRef<Session>(); MRequest->SessionData = CreateRef<Session>();
Ref<Response> response; Ref<Response> response;
@ -72,7 +117,8 @@ struct RequestJob : public WorkerJob {
} }
}; };
RequestHandler::RequestHandler(const Ref<SocketManager> &manager) RequestHandler::RequestHandler(const Ref<SocketManager> &manager)
: m_SocketManager(manager), m_MiddleWare(CreateRef<MiddleWareHandler>()) { : m_SocketManager(manager),
m_MiddleWare(CreateRef<MiddleWareHandler>()) {
m_MiddleWare = CreateRef<MiddleWareHandler>(); m_MiddleWare = CreateRef<MiddleWareHandler>();
} }
void RequestHandler::InitThreads(int count) { void RequestHandler::InitThreads(int count) {
@ -89,12 +135,8 @@ void RequestHandler::AddRequest(Ref<Request> &request) {
m_Pool.Dispatch(job); m_Pool.Dispatch(job);
} }
} }
void RequestHandler::Stop() { void RequestHandler::Stop() { m_Pool.Stop(); }
m_Pool.Stop(); void RequestHandler::SetRouter(Ref<Router> &router) { m_Router = router; }
}
void RequestHandler::SetRouter(Ref<Router> &router) {
m_Router = router;
}
void RequestHandler::AddSendResponse(SendData sendData) { void RequestHandler::AddSendResponse(SendData sendData) {
auto id = sendData.SocketID; auto id = sendData.SocketID;
m_Server->m_OutRequest.Add(id, sendData); m_Server->m_OutRequest.Add(id, sendData);

View file

@ -19,12 +19,14 @@ static void StringRightTrim(std::string &s) {
} }
std::vector<std::string> String::Split(const std::string &s, std::vector<std::string> 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(); size_t pos_start = 0, pos_end, delim_len = delimiter.length();
std::string token; std::string token;
std::vector<std::string> res; std::vector<std::string> 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); token = s.substr(pos_start, pos_end - pos_start);
pos_start = pos_end + delim_len; pos_start = pos_end + delim_len;
res.push_back(token); res.push_back(token);

View file

@ -7,7 +7,8 @@ namespace VWeb {
class String { class String {
public: public:
static std::vector<std::string> Split(const std::string& s, static std::vector<std::string> Split(const std::string& s,
const std::string& delimiter); const std::string& delimiter,
int limit = -1);
static std::string Join(const std::vector<std::string>& items, const std::string& delimiter); static std::string Join(const std::vector<std::string>& items, const std::string& delimiter);
static int ToNumber(std::string& string, int def); static int ToNumber(std::string& string, int def);
static void ToLowerCase(std::string& value); static void ToLowerCase(std::string& value);

83
VWeb.h
View file

@ -3,6 +3,7 @@
#include <cassert> #include <cassert>
#include <condition_variable> #include <condition_variable>
#include <fcntl.h> #include <fcntl.h>
#include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -275,7 +276,8 @@ public:
void AddRoute(const std::string &path, const Ref<Route> &route); void AddRoute(const std::string &path, const Ref<Route> &route);
void RemoveRoute(const std::string &path); void RemoveRoute(const std::string &path);
Ref<MiddleWareHandler>& Middleware(); Ref<MiddleWareHandler> &Middleware();
protected: protected:
void Execute(); void Execute();
void OutgoingExecute(epoll_event &event); void OutgoingExecute(epoll_event &event);
@ -300,6 +302,7 @@ protected:
using ms = std::chrono::duration<double, std::milli>; using ms = std::chrono::duration<double, std::milli>;
struct SessionData { struct SessionData {
virtual ~SessionData() = default; virtual ~SessionData() = default;
template <class T> T *As() { return reinterpret_cast<T *>(this); }
}; };
struct Session { struct Session {
std::string Id; std::string Id;
@ -309,7 +312,9 @@ struct Session {
void Remove(const std::string &key); void Remove(const std::string &key);
bool Has(const std::string &key); bool Has(const std::string &key);
Ref<SessionData> &operator[](const std::string &key) { return m_Data[key]; } Ref<SessionData> &operator[](const std::string &key) { return m_Data[key]; }
void SetSessionData(const std::string& key, const Ref<SessionData>& data) { m_Data[key] = data; } void SetSessionData(const std::string &key, const Ref<SessionData> &data) {
m_Data[key] = data;
}
bool ContainsData() { return !m_Data.empty(); } bool ContainsData() { return !m_Data.empty(); }
protected: protected:
@ -391,15 +396,15 @@ enum class HttpMethod {
}; };
enum class CookieSameSite { Invalid = 0, Strict, Lax, None }; enum class CookieSameSite { Invalid = 0, Strict, Lax, None };
struct Cookie { struct Cookie {
std::string Name; std::string Name{};
std::string Value; std::string Value{};
std::string Expires; std::string Expires{};
int MaxAge{}; int MaxAge{};
std::string Domain; std::string Domain{};
std::string Path; std::string Path{"/"};
bool Secure{}; bool Secure{};
bool HttpOnly{}; bool HttpOnly{};
CookieSameSite SameSite{CookieSameSite::Invalid}; CookieSameSite SameSite{CookieSameSite::Strict};
bool IsOld = false; bool IsOld = false;
}; };
struct Cookies { struct Cookies {
@ -442,9 +447,13 @@ public:
Ref<Cookies> CookieData; Ref<Cookies> CookieData;
Cookie &GetCookie(const std::string &key) { return CookieData->Get(key); }; Cookie &GetCookie(const std::string &key) { return CookieData->Get(key); };
ParameterValue &Parameter(const std::string &key) { return Parameters[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); } 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]; } ParameterValue &Header(const std::string &key) { return Headers[key]; }
std::unordered_map<std::string, ParameterValue> Parameters; std::unordered_map<std::string, ParameterValue> Parameters;
std::unordered_map<std::string, ParameterValue> Headers; std::unordered_map<std::string, ParameterValue> Headers;
@ -483,6 +492,7 @@ protected:
std::unordered_map<std::string, ParameterValue> m_Headers; std::unordered_map<std::string, ParameterValue> m_Headers;
}; };
class MiddleWareHandler; class MiddleWareHandler;
typedef std::function<const void(Request &, const std::string& body)> ParseFunction;
class RequestHandler { class RequestHandler {
public: public:
explicit RequestHandler(const Ref<SocketManager> &manager); explicit RequestHandler(const Ref<SocketManager> &manager);
@ -492,8 +502,15 @@ public:
void SetRouter(Ref<Router> &router); void SetRouter(Ref<Router> &router);
void AddSendResponse(SendData); void AddSendResponse(SendData);
Ref<MiddleWareHandler> &Middleware() { return m_MiddleWare; } 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: protected:
std::unordered_map<std::string, ParseFunction> m_ParseFunctions;
Ref<Router> m_Router{nullptr}; Ref<Router> m_Router{nullptr};
Ref<SocketManager> m_SocketManager{nullptr}; Ref<SocketManager> m_SocketManager{nullptr};
Server *m_Server{nullptr}; Server *m_Server{nullptr};
@ -503,7 +520,7 @@ protected:
friend Server; friend Server;
}; };
typedef std::function<bool(Request&,Response&)> RouteFunction; typedef std::function<bool(Request &, Response &)> RouteFunction;
class Route { class Route {
public: public:
Route() = default; Route() = default;
@ -540,20 +557,20 @@ public:
static void AddToArgs(Ref<Request> &request, Vector<std::string> &items); static void AddToArgs(Ref<Request> &request, Vector<std::string> &items);
public: public:
void Get(const std::string& path, RouteFunction); void Get(const std::string &path, RouteFunction);
void Post(const std::string& path, RouteFunction); void Post(const std::string &path, RouteFunction);
void Put(const std::string& path, RouteFunction); void Put(const std::string &path, RouteFunction);
void Patch(const std::string& path, RouteFunction); void Patch(const std::string &path, RouteFunction);
void Delete(const std::string& path, RouteFunction); void Delete(const std::string &path, RouteFunction);
std::unordered_map<std::string, Ref<Route>> m_Routes; std::unordered_map<std::string, Ref<Route>> m_Routes;
}; };
typedef std::optional<Ref<Response>> PreMiddleWareReturn; typedef std::optional<Ref<Response>> PreMiddleWareReturn;
struct MiddleWare { struct MiddleWare {
int Pos{0}; int Pos{0};
virtual PreMiddleWareReturn PreHandle(Request &){ return {}; } virtual PreMiddleWareReturn PreHandle(Request &) { return {}; }
virtual bool PostHandle(const Request &, Response &){ return true; } virtual bool PostHandle(Request &, Response &) { return true; }
virtual void Shutdown(const Request &, const Response &){}; virtual void Shutdown(Request &, const Response &){};
bool operator<(const MiddleWare *rhs) const { return Pos < rhs->Pos; } bool operator<(const MiddleWare *rhs) const { return Pos < rhs->Pos; }
}; };
@ -564,17 +581,22 @@ public:
void Shutdown(Ref<Request> &, Ref<Response> &); void Shutdown(Ref<Request> &, Ref<Response> &);
public: public:
template <class T> Ref<MiddleWare> GetRef() { return GetById(typeid(T).name()); } template <class T> Ref<MiddleWare> GetRef() {
template <class T> T& Get() { return static_cast<T&>(*GetById(typeid(T).name())); } return GetById(typeid(T).name());
}
template <class T> T &Get() {
return static_cast<T &>(*GetById(typeid(T).name()));
}
template <class T> void Set(Ref<MiddleWare> &instance) { template <class T> void Set(Ref<MiddleWare> &instance) {
auto &type = typeid(T); auto &type = typeid(T);
if (type.before(typeid(MiddleWare))) if (type.before(typeid(MiddleWare)))
SetById(type.name(), instance); SetById(type.name(), instance);
} }
template <class T> T& Create() { template <class T> T &Create() {
return static_cast<T&>(*CreateMiddleWare<T>()); return static_cast<T &>(*CreateMiddleWare<T>());
} }
template <class T> void Remove() { RemoveById(typeid(T).name()); } template <class T> void Remove() { RemoveById(typeid(T).name()); }
protected: protected:
template <class T> Ref<MiddleWare> CreateMiddleWare() { template <class T> Ref<MiddleWare> CreateMiddleWare() {
return SetById(typeid(T).name(), CreateRef<T>()); return SetById(typeid(T).name(), CreateRef<T>());
@ -585,13 +607,16 @@ protected:
std::map<std::string, Ref<MiddleWare>> m_MiddleWares; std::map<std::string, Ref<MiddleWare>> m_MiddleWares;
}; };
typedef std::function<PreMiddleWareReturn(Request& req)> AuthFunction; typedef std::function<PreMiddleWareReturn(Request &req)> AuthFunction;
class AuthWare : public MiddleWare { class AuthWare : public MiddleWare {
public: public:
AuthWare() = default; AuthWare() = default;
~AuthWare() = default; ~AuthWare() = default;
PreMiddleWareReturn PreHandle(Request &request) override; PreMiddleWareReturn PreHandle(Request &request) override;
void SetAuthMethod(AuthFunction function) { m_AuthFunction = std::move(function); } void SetAuthMethod(AuthFunction function) {
m_AuthFunction = std::move(function);
}
protected: protected:
AuthFunction m_AuthFunction{nullptr}; AuthFunction m_AuthFunction{nullptr};
}; };
@ -600,10 +625,12 @@ class SessionManager : public MiddleWare {
public: public:
SessionManager(); SessionManager();
~SessionManager(); ~SessionManager();
PreMiddleWareReturn PreHandle(Request& request) override; PreMiddleWareReturn PreHandle(Request &request) override;
bool PostHandle(const Request& request, Response& response) override; bool PostHandle(Request &request, Response &response) override;
protected: protected:
void GC(); void GC();
protected: protected:
Ref<std::thread> m_GCThread; Ref<std::thread> m_GCThread;
std::mutex m_Mutex; std::mutex m_Mutex;
@ -616,7 +643,7 @@ class CookieManager : public MiddleWare {
public: public:
CookieManager() = default; CookieManager() = default;
~CookieManager() = default; ~CookieManager() = default;
PreMiddleWareReturn PreHandle(Request&) override; PreMiddleWareReturn PreHandle(Request &) override;
}; };
#pragma endregion VWEB_ROUTING #pragma endregion VWEB_ROUTING

BIN
dist/libVWeb.Debug.a vendored Normal file

Binary file not shown.

BIN
dist/libVWeb.Release.a vendored Normal file

Binary file not shown.

BIN
dist/libVWeb.debug.a vendored

Binary file not shown.

BIN
dist/libVWeb.release.a vendored

Binary file not shown.

View file

@ -1,5 +1,20 @@
#include <VWeb.h> #include <VWeb.h>
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) { bool Ping(const VWeb::Request&, VWeb::Response& response) {
response << "Pong"; response << "Pong";
return true; return true;