Split VWeb into smaller headers
We have no make install support... so we don't need to have everything as a single-header and lib file.
This commit is contained in:
parent
4367534a33
commit
5bb68a7d02
43 changed files with 902 additions and 685 deletions
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
* text=auto
|
||||
*.txt text
|
||||
*.cpp text
|
||||
*.h text
|
||||
*.hpp text
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,6 +4,7 @@ build-debug/
|
|||
release-build/
|
||||
build-release/
|
||||
cmake-build-debug/
|
||||
cmake-build-debug-wsl/
|
||||
.idea/
|
||||
|
||||
example/build
|
||||
|
|
|
@ -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/$<CONFIG>
|
||||
|
|
25
CMakeMacros.txt
Normal file
25
CMakeMacros.txt
Normal file
|
@ -0,0 +1,25 @@
|
|||
macro(add_sources)
|
||||
get_property(tmp GLOBAL PROPERTY SRCS)
|
||||
file(RELATIVE_PATH _relPath "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
foreach (_src ${ARGN})
|
||||
if (_relPath)
|
||||
list(APPEND tmp "${_relPath}/${_src}")
|
||||
else ()
|
||||
list(APPEND tmp "${_src}")
|
||||
endif ()
|
||||
endforeach ()
|
||||
set_property(GLOBAL PROPERTY SRCS ${tmp})
|
||||
endmacro()
|
||||
|
||||
macro(add_headers)
|
||||
get_property(tmp GLOBAL PROPERTY Headers)
|
||||
file(RELATIVE_PATH _relPath "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
foreach (_src ${ARGN})
|
||||
if (_relPath)
|
||||
list(APPEND tmp "${_relPath}/${_src}")
|
||||
else ()
|
||||
list(APPEND tmp "${_src}")
|
||||
endif ()
|
||||
endforeach ()
|
||||
set_property(GLOBAL PROPERTY Headers ${tmp})
|
||||
endmacro()
|
24
Includes/CMakeLists.txt
Normal file
24
Includes/CMakeLists.txt
Normal file
|
@ -0,0 +1,24 @@
|
|||
add_headers(
|
||||
Cookie.h
|
||||
EPollManager.h
|
||||
Http.h
|
||||
Map.h
|
||||
MiddleWare.h
|
||||
MiddleWareHandler.h
|
||||
ParameterValue.h
|
||||
PreMiddleWare.h
|
||||
Queue.h
|
||||
Request.h
|
||||
RequestHandler.h
|
||||
Response.h
|
||||
Route.h
|
||||
Router.h
|
||||
Server.h
|
||||
ServerConfig.h
|
||||
Session.h
|
||||
Socket.h
|
||||
SocketManager.h
|
||||
ThreadPool.h
|
||||
Types.h
|
||||
VWeb.h
|
||||
)
|
35
Includes/Cookie.h
Normal file
35
Includes/Cookie.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace VWeb {
|
||||
enum class CookieSameSite { Invalid = 0, Strict, Lax, None };
|
||||
struct Cookie {
|
||||
std::string Name{};
|
||||
std::string Value{};
|
||||
std::string Expires{};
|
||||
int MaxAge{};
|
||||
std::string Domain{};
|
||||
std::string Path{"/"};
|
||||
bool Secure{};
|
||||
bool HttpOnly{};
|
||||
CookieSameSite SameSite{CookieSameSite::Strict};
|
||||
bool IsOld = false;
|
||||
};
|
||||
struct Cookies {
|
||||
public:
|
||||
void Remove(const std::string &name);
|
||||
bool Has(const std::string &name) const;
|
||||
Cookie &Get(const std::string &name) { return Data[name]; };
|
||||
std::string TransformToHeader();
|
||||
void CreateCookieFromString(const std::string &data);
|
||||
void CreateOld(const std::string &name, const std::string &value);
|
||||
|
||||
std::unordered_map<std::string, Cookie> Data;
|
||||
Cookie &operator[](const std::string &name) { return Data[name]; }
|
||||
|
||||
protected:
|
||||
static void CookieToString(std::stringstream &stream, Cookie &cookie);
|
||||
};
|
||||
} // namespace VWeb
|
17
Includes/EPollManager.h
Normal file
17
Includes/EPollManager.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <sys/epoll.h>
|
||||
|
||||
namespace VWeb {
|
||||
class EPollManager {
|
||||
public:
|
||||
EPollManager();
|
||||
~EPollManager();
|
||||
[[nodiscard]] bool Dispatch(int sock, uint32_t eventType = EPOLLIN) const;
|
||||
[[nodiscard]] bool UpdateEvents(int sock, uint32_t eventType = EPOLLIN) const;
|
||||
int Wait(int idx, epoll_event *events) const;
|
||||
|
||||
protected:
|
||||
int m_EpollID{-1};
|
||||
};
|
||||
}
|
76
Includes/Http.h
Normal file
76
Includes/Http.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
namespace VWeb {
|
||||
enum class HttpStatusCode : int {
|
||||
Continue = 100,
|
||||
SwitchingProtocols = 101,
|
||||
Processing = 102,
|
||||
EarlyHints = 103,
|
||||
OK = 200,
|
||||
Created = 201,
|
||||
Accepted = 202,
|
||||
NonAuthoritativeInformation = 203,
|
||||
NoContent = 204,
|
||||
ResetContent = 205,
|
||||
PartialContent = 206,
|
||||
MultiStatus = 207,
|
||||
AlreadyReported = 208,
|
||||
IMUsed = 226,
|
||||
MultipleChoices = 300,
|
||||
MovedPermanently = 301,
|
||||
Found = 302,
|
||||
SeeOther = 303,
|
||||
NotModified = 304,
|
||||
UseProxy = 305,
|
||||
TemporaryRedirect = 307,
|
||||
PermanentRedirect = 308,
|
||||
BadRequest = 400,
|
||||
Unauthorized = 401,
|
||||
PaymentRequired = 402,
|
||||
Forbidden = 403,
|
||||
NotFound = 404,
|
||||
MethodNotAllowed = 405,
|
||||
NotAcceptable = 406,
|
||||
ProxyAuthenticationRequired = 407,
|
||||
RequestTimeout = 408,
|
||||
Conflict = 409,
|
||||
Gone = 410,
|
||||
LengthRequired = 411,
|
||||
PreconditionFailed = 412,
|
||||
PayloadTooLarge = 413,
|
||||
URITooLong = 414,
|
||||
UnsupportedMediaType = 415,
|
||||
RangeNotSatisfiable = 416,
|
||||
ExpectationFailed = 417,
|
||||
ImATeapot = 418,
|
||||
UnprocessableEntity = 422,
|
||||
Locked = 423,
|
||||
FailedDependency = 424,
|
||||
UpgradeRequired = 426,
|
||||
PreconditionRequired = 428,
|
||||
TooManyRequests = 429,
|
||||
RequestHeaderFieldsTooLarge = 431,
|
||||
UnavailableForLegalReasons = 451,
|
||||
InternalServerError = 500,
|
||||
NotImplemented = 501,
|
||||
BadGateway = 502,
|
||||
ServiceUnavailable = 503,
|
||||
GatewayTimeout = 504,
|
||||
HTTPVersionNotSupported = 505,
|
||||
VariantAlsoNegotiates = 506,
|
||||
InsufficientStorage = 507,
|
||||
LoopDetected = 508,
|
||||
NotExtended = 510,
|
||||
NetworkAuthenticationRequired = 511
|
||||
};
|
||||
enum class HttpMethod {
|
||||
GET = 0,
|
||||
HEAD,
|
||||
OPTIONS,
|
||||
POST,
|
||||
PUT,
|
||||
PATCH,
|
||||
DELETE,
|
||||
FALLBACK
|
||||
};
|
||||
} // namespace VWeb
|
59
Includes/Map.h
Normal file
59
Includes/Map.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
|
||||
namespace VWeb {
|
||||
template <typename T, typename S> struct SafeMap {
|
||||
explicit SafeMap(size_t maxSize = -1UL) : m_MaxSize(maxSize), m_End(false){};
|
||||
bool Add(const T &t, S &x) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
while (m_Data.size() == m_MaxSize && !m_End)
|
||||
return false;
|
||||
assert(!m_End);
|
||||
m_Data.emplace(t, std::move(x));
|
||||
return true;
|
||||
};
|
||||
bool Add(T &&t, S &&x) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
while (m_Data.size() == m_MaxSize && !m_End)
|
||||
return false;
|
||||
assert(!m_End);
|
||||
m_Data.push(std::move(t));
|
||||
return true;
|
||||
};
|
||||
void Remove(T t) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
if (m_Data.empty() || m_End)
|
||||
return;
|
||||
if (m_Data.contains(t))
|
||||
m_Data.erase(t);
|
||||
m_CVFull.notify_one();
|
||||
};
|
||||
void Clear() {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
std::unordered_map<T, S> empty;
|
||||
std::swap(m_Data, empty);
|
||||
m_CVFull.notify_all();
|
||||
};
|
||||
bool Has(T t) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
return m_Data.contains(t);
|
||||
};
|
||||
S &Get(T t) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
return m_Data[t];
|
||||
};
|
||||
int Size() { return m_Data.size(); };
|
||||
|
||||
private:
|
||||
std::unordered_map<T, S> m_Data{};
|
||||
std::mutex m_Mutex{};
|
||||
std::condition_variable m_CVFull{};
|
||||
const size_t m_MaxSize{};
|
||||
std::atomic<bool> m_End{};
|
||||
};
|
||||
}
|
16
Includes/MiddleWare.h
Normal file
16
Includes/MiddleWare.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include "Response.h"
|
||||
#include "Request.h"
|
||||
|
||||
namespace VWeb {
|
||||
typedef std::optional<Ref<Response>> PreMiddleWareReturn;
|
||||
struct MiddleWare {
|
||||
int Pos{0};
|
||||
virtual PreMiddleWareReturn PreHandle(Request &) { return {}; }
|
||||
virtual bool PostHandle(Request &, Response &) { return true; }
|
||||
virtual void Shutdown(Request &, const Response &){};
|
||||
bool operator<(const MiddleWare *rhs) const { return Pos < rhs->Pos; }
|
||||
};
|
||||
}
|
43
Includes/MiddleWareHandler.h
Normal file
43
Includes/MiddleWareHandler.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "MiddleWare.h"
|
||||
#include "Request.h"
|
||||
#include "Response.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace VWeb {
|
||||
|
||||
class MiddleWareHandler {
|
||||
public:
|
||||
PreMiddleWareReturn HandlePre(Ref<Request> &);
|
||||
void HandlePost(Ref<Request> &, Ref<Response> &);
|
||||
void Shutdown(Ref<Request> &, Ref<Response> &);
|
||||
|
||||
public:
|
||||
template <class T> Ref<MiddleWare> GetRef() {
|
||||
return GetById(typeid(T).name());
|
||||
}
|
||||
template <class T> T &Get() {
|
||||
return static_cast<T &>(*GetById(typeid(T).name()));
|
||||
}
|
||||
template <class T> void Set(Ref<MiddleWare> &instance) {
|
||||
auto &type = typeid(T);
|
||||
if (type.before(typeid(MiddleWare)))
|
||||
SetById(type.name(), instance);
|
||||
}
|
||||
template <class T> T &Create() {
|
||||
return static_cast<T &>(*CreateMiddleWare<T>());
|
||||
}
|
||||
template <class T> void Remove() { RemoveById(typeid(T).name()); }
|
||||
|
||||
protected:
|
||||
template <class T> Ref<MiddleWare> CreateMiddleWare() {
|
||||
return SetById(typeid(T).name(), CreateRef<T>());
|
||||
}
|
||||
Ref<MiddleWare> GetById(const char *id);
|
||||
Ref<MiddleWare> SetById(const char *id, const Ref<MiddleWare> &);
|
||||
void RemoveById(const char *id);
|
||||
std::map<std::string, Ref<MiddleWare>> m_MiddleWares;
|
||||
};
|
||||
} // namespace VWeb
|
21
Includes/ParameterValue.h
Normal file
21
Includes/ParameterValue.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace VWeb {
|
||||
struct ParameterValue {
|
||||
void Add(const std::string &item) { m_Data.push_back(item); }
|
||||
void Set(const std::string &item) {
|
||||
m_Data.clear();
|
||||
m_Data.push_back(item);
|
||||
}
|
||||
std::string &Get(int item) { return m_Data[item]; }
|
||||
std::string &GetFirst() { return Get(0); }
|
||||
size_t Size() { return m_Data.size(); }
|
||||
std::vector<std::string> &Values() { return m_Data; }
|
||||
|
||||
protected:
|
||||
std::vector<std::string> m_Data{};
|
||||
};
|
||||
}
|
48
Includes/PreMiddleWare.h
Normal file
48
Includes/PreMiddleWare.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "MiddleWare.h"
|
||||
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
|
||||
namespace VWeb {
|
||||
typedef std::function<PreMiddleWareReturn(Request &req)> AuthFunction;
|
||||
class AuthWare : public MiddleWare {
|
||||
public:
|
||||
AuthWare() = default;
|
||||
~AuthWare() = default;
|
||||
PreMiddleWareReturn PreHandle(Request &request) override;
|
||||
void SetAuthMethod(AuthFunction function) {
|
||||
m_AuthFunction = std::move(function);
|
||||
}
|
||||
|
||||
protected:
|
||||
AuthFunction m_AuthFunction{nullptr};
|
||||
};
|
||||
|
||||
class SessionManager : public MiddleWare {
|
||||
public:
|
||||
SessionManager();
|
||||
~SessionManager();
|
||||
PreMiddleWareReturn PreHandle(Request &request) override;
|
||||
bool PostHandle(Request &request, Response &response) override;
|
||||
|
||||
protected:
|
||||
void GC();
|
||||
|
||||
protected:
|
||||
Ref<std::thread> m_GCThread;
|
||||
std::mutex m_Mutex;
|
||||
std::unordered_map<std::string, Ref<Session>> m_Sessions;
|
||||
int m_Counter{-1};
|
||||
bool m_IsRunning{true};
|
||||
};
|
||||
|
||||
class CookieManager : public MiddleWare {
|
||||
public:
|
||||
CookieManager() = default;
|
||||
~CookieManager() = default;
|
||||
PreMiddleWareReturn PreHandle(Request &) override;
|
||||
};
|
||||
} // namespace VWeb
|
87
Includes/Queue.h
Normal file
87
Includes/Queue.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <optional>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
|
||||
namespace VWeb {
|
||||
template <typename T> struct SafeQueue {
|
||||
explicit SafeQueue(size_t maxSize = -1UL)
|
||||
: m_MaxSize(maxSize),
|
||||
m_End(false){};
|
||||
void Push(const T &t) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
while (m_Queue.size() == m_MaxSize && !m_End)
|
||||
m_CVFull.wait(lck);
|
||||
assert(!m_End);
|
||||
m_Queue.push(std::move(t));
|
||||
m_CVEmpty.notify_one();
|
||||
};
|
||||
void Push(T &&t) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
while (m_Queue.size() == m_MaxSize && !m_End)
|
||||
m_CVFull.wait(lck);
|
||||
assert(!m_End);
|
||||
m_Queue.push(std::move(t));
|
||||
m_CVEmpty.notify_one();
|
||||
};
|
||||
void Open() {
|
||||
m_End = false;
|
||||
std::lock_guard<std::mutex> lck(m_Mutex);
|
||||
m_CVEmpty.notify_all();
|
||||
m_CVFull.notify_all();
|
||||
};
|
||||
void Close() {
|
||||
m_End = true;
|
||||
std::lock_guard<std::mutex> lck(m_Mutex);
|
||||
m_CVEmpty.notify_all();
|
||||
m_CVFull.notify_all();
|
||||
};
|
||||
void Clear() {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
std::queue<T> empty;
|
||||
std::swap(m_Queue, empty);
|
||||
m_CVEmpty.notify_all();
|
||||
m_CVFull.notify_all();
|
||||
};
|
||||
std::optional<T> Pop() {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
if (m_Queue.empty() || m_End)
|
||||
return {};
|
||||
T t = std::move(m_Queue.front());
|
||||
m_Queue.pop();
|
||||
m_CVFull.notify_one();
|
||||
return t;
|
||||
};
|
||||
std::optional<T> WaitAndPop() {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
while (m_Queue.empty() && !m_End)
|
||||
m_CVEmpty.wait(lck);
|
||||
if (m_Queue.empty() || m_End)
|
||||
return {};
|
||||
T t = std::move(m_Queue.front());
|
||||
m_Queue.pop();
|
||||
m_CVFull.notify_one();
|
||||
return t;
|
||||
};
|
||||
bool IsClosed() { return m_End; }
|
||||
int Size() { return m_Queue.size(); }
|
||||
std::queue<T> &GetQueue() { return m_Queue; }
|
||||
void Flush() {
|
||||
while (!m_Queue.empty())
|
||||
m_CVEmpty.notify_one();
|
||||
m_End = true;
|
||||
m_CVEmpty.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<T> m_Queue;
|
||||
std::mutex m_Mutex;
|
||||
std::condition_variable m_CVEmpty, m_CVFull;
|
||||
const size_t m_MaxSize;
|
||||
std::atomic<bool> m_End;
|
||||
};
|
||||
}
|
34
Includes/Request.h
Normal file
34
Includes/Request.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "Cookie.h"
|
||||
#include "Http.h"
|
||||
#include "ParameterValue.h"
|
||||
#include "Session.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace VWeb {
|
||||
struct Request {
|
||||
public:
|
||||
int ID;
|
||||
std::string Body;
|
||||
HttpMethod Method{HttpMethod::GET};
|
||||
std::string URI;
|
||||
Ref<Session> SessionData;
|
||||
Ref<Cookies> CookieData;
|
||||
Cookie &GetCookie(const std::string &key) { return CookieData->Get(key); };
|
||||
ParameterValue &Parameter(const std::string &key) { return Parameters[key]; }
|
||||
bool HasParameter(const std::string &key) const {
|
||||
return Parameters.contains(key);
|
||||
}
|
||||
bool HasHeader(const std::string &key) const { return Headers.contains(key); }
|
||||
std::string &FirstOf(const std::string &key) {
|
||||
return Parameters[key].GetFirst();
|
||||
}
|
||||
ParameterValue &Header(const std::string &key) { return Headers[key]; }
|
||||
std::unordered_map<std::string, ParameterValue> Parameters;
|
||||
std::unordered_map<std::string, ParameterValue> Headers;
|
||||
std::vector<std::string> URLParameters;
|
||||
};
|
||||
} // namespace VWeb
|
42
Includes/RequestHandler.h
Normal file
42
Includes/RequestHandler.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include "MiddleWareHandler.h"
|
||||
#include "Queue.h"
|
||||
#include "Request.h"
|
||||
#include "Router.h"
|
||||
#include "SocketManager.h"
|
||||
#include "ThreadPool.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace VWeb {
|
||||
class Server;
|
||||
typedef std::function<const void(Request &, const std::string &body)>
|
||||
ParseFunction;
|
||||
class RequestHandler {
|
||||
public:
|
||||
explicit RequestHandler(const Ref<SocketManager> &manager);
|
||||
void InitThreads(int count);
|
||||
void AddRequest(Ref<Request> &request);
|
||||
void Stop();
|
||||
void SetRouter(Ref<Router> &router);
|
||||
void AddSendResponse(SendData);
|
||||
Ref<MiddleWareHandler> &Middleware() { return m_MiddleWare; }
|
||||
bool HasBodyParser(const std::string &key) {
|
||||
return m_ParseFunctions.contains(key);
|
||||
}
|
||||
ParseFunction &GetBodyParser(const std::string &key) {
|
||||
return m_ParseFunctions.at(key);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unordered_map<std::string, ParseFunction> m_ParseFunctions;
|
||||
Ref<Router> m_Router{nullptr};
|
||||
Ref<SocketManager> m_SocketManager{nullptr};
|
||||
Server *m_Server{nullptr};
|
||||
SafeQueue<Ref<Request>> m_Requests{};
|
||||
ThreadPool m_Pool{"RequestHandler"};
|
||||
Ref<MiddleWareHandler> m_MiddleWare{nullptr};
|
||||
friend Server;
|
||||
};
|
||||
} // namespace VWeb
|
43
Includes/Response.h
Normal file
43
Includes/Response.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "Cookie.h"
|
||||
#include "Http.h"
|
||||
#include "Session.h"
|
||||
#include "Types.h"
|
||||
#include "ParameterValue.h"
|
||||
|
||||
namespace VWeb {
|
||||
class Response {
|
||||
public:
|
||||
static Ref<Response> FromCode(HttpStatusCode code);
|
||||
|
||||
public:
|
||||
size_t Length{0};
|
||||
Ref<Cookies> CookieData{nullptr};
|
||||
Ref<Session> SessionData{nullptr};
|
||||
HttpStatusCode Status{HttpStatusCode::OK};
|
||||
HttpMethod Method{HttpMethod::GET};
|
||||
|
||||
public:
|
||||
std::string GetResponse();
|
||||
void SetHeader(const std::string &key, ParameterValue &value);
|
||||
void SetHeader(const std::string &key, const std::string &value);
|
||||
void AddHeaders(const std::string &key, const std::vector<std::string> &values);
|
||||
void AddHeader(const std::string &key, const std::string &value);
|
||||
void SetType(const std::string &type);
|
||||
void AddContent(const std::string &data);
|
||||
void SetStatus(HttpStatusCode);
|
||||
void SetMethod(HttpMethod);
|
||||
void Reset();
|
||||
Response &operator<<(const std::string &data) {
|
||||
m_Content << data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string TransformHeaders(const std::string &content);
|
||||
std::string m_Type{"text/html"};
|
||||
std::stringstream m_Content;
|
||||
std::unordered_map<std::string, ParameterValue> m_Headers;
|
||||
};
|
||||
} // namespace VWeb
|
34
Includes/Route.h
Normal file
34
Includes/Route.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "Http.h"
|
||||
#include <initializer_list>
|
||||
#include "Request.h"
|
||||
#include "Response.h"
|
||||
#include <vector>
|
||||
|
||||
namespace VWeb {
|
||||
class Router;
|
||||
class Route {
|
||||
public:
|
||||
Route() = default;
|
||||
virtual ~Route() = default;
|
||||
Route(std::initializer_list<HttpMethod>);
|
||||
virtual bool Execute(Request &request, Response &response);
|
||||
virtual bool Get(Request &request, Response &response);
|
||||
virtual bool Post(Request &request, Response &response);
|
||||
virtual bool Put(Request &request, Response &response);
|
||||
virtual bool Patch(Request &request, Response &response);
|
||||
virtual bool Delete(Request &request, Response &response);
|
||||
bool Options(Request &request, Response &response);
|
||||
virtual bool Fallback(Request &request, Response &response);
|
||||
bool SupportsMethod(Request &request);
|
||||
virtual bool IsAllowed(Request &request);
|
||||
|
||||
void AllowMethod(HttpMethod method);
|
||||
|
||||
protected:
|
||||
bool m_AllowAll{true};
|
||||
std::vector<HttpMethod> m_AllowedMethods;
|
||||
friend Router;
|
||||
};
|
||||
}
|
29
Includes/Router.h
Normal file
29
Includes/Router.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include "Route.h"
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace VWeb {
|
||||
typedef std::function<bool(Request &, Response &)> RouteFunction;
|
||||
class Router {
|
||||
public:
|
||||
Router();
|
||||
void AddRoute(const std::string &name, const Ref<Route> &route);
|
||||
Ref<Route> &GetRoute(const std::string &name);
|
||||
void DeleteRoute(const std::string &name);
|
||||
|
||||
Ref<Response> HandleRoute(Ref<Request> &request);
|
||||
Ref<Route> FindRoute(Ref<Request> &request);
|
||||
static void AddToArgs(Ref<Request> &request, std::vector<std::string> &items);
|
||||
|
||||
public:
|
||||
void Get(const std::string &path, RouteFunction);
|
||||
void Post(const std::string &path, RouteFunction);
|
||||
void Put(const std::string &path, RouteFunction);
|
||||
void Patch(const std::string &path, RouteFunction);
|
||||
void Delete(const std::string &path, RouteFunction);
|
||||
std::unordered_map<std::string, Ref<Route>> m_Routes;
|
||||
};
|
||||
} // namespace VWeb
|
49
Includes/Server.h
Normal file
49
Includes/Server.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include "EPollManager.h"
|
||||
#include "SocketManager.h"
|
||||
#include "Map.h"
|
||||
#include "Router.h"
|
||||
#include "RequestHandler.h"
|
||||
#include "MiddleWareHandler.h"
|
||||
#include "Route.h"
|
||||
|
||||
namespace VWeb {
|
||||
class Server {
|
||||
public:
|
||||
Server();
|
||||
// Will Load SharedLibs from subdirectory
|
||||
// This will allow that VWeb will run as Standalone and will load .so files
|
||||
// without recompiling itself
|
||||
void LoadSharedLibs();
|
||||
void Start();
|
||||
void Join() { m_WorkerThread->join(); }
|
||||
void Stop() { m_IsExit = true; }
|
||||
Ref<Router> &GetRouter() { return m_Router; }
|
||||
Ref<ServerConfig> &GetServerConfig() { return m_ServerConfig; }
|
||||
void AddRoute(const std::string &path, const Ref<Route> &route);
|
||||
void RemoveRoute(const std::string &path);
|
||||
|
||||
Ref<MiddleWareHandler> &Middleware();
|
||||
|
||||
protected:
|
||||
void Execute();
|
||||
void OutgoingExecute(epoll_event &event);
|
||||
void IncomingExecute(epoll_event &event);
|
||||
void HandleRequestReading(epoll_event &event);
|
||||
void CreateRequest(int sockID);
|
||||
|
||||
protected:
|
||||
Ref<Router> m_Router;
|
||||
Ref<ServerConfig> m_ServerConfig;
|
||||
Ref<RequestHandler> m_RequestHandler;
|
||||
SafeMap<int, Accept> m_RawRequest{60000};
|
||||
SafeMap<int, SendData> m_OutRequest{60000};
|
||||
Ref<std::thread> m_WorkerThread;
|
||||
std::mutex m_Mutex;
|
||||
bool m_IsExit{false};
|
||||
|
||||
protected:
|
||||
friend RequestHandler;
|
||||
};
|
||||
}
|
22
Includes/ServerConfig.h
Normal file
22
Includes/ServerConfig.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "EPollManager.h"
|
||||
#include "SocketManager.h"
|
||||
#include "Types.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace VWeb {
|
||||
struct ServerConfig {
|
||||
int Port{9020};
|
||||
int MaxBufferSize{-1};
|
||||
int BufferSize{16384};
|
||||
int WorkerThreads{-1};
|
||||
int MaxEvents{5000};
|
||||
bool AllowSharedLibs{true};
|
||||
Ref<EPollManager> EPoll{nullptr};
|
||||
Ref<SocketManager> Socket{nullptr};
|
||||
std::string ErrorDir;
|
||||
std::string MimeFiles;
|
||||
};
|
||||
}
|
31
Includes/Session.h
Normal file
31
Includes/Session.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include "Types.h"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace VWeb {
|
||||
using ms = std::chrono::duration<double, std::milli>;
|
||||
struct SessionData {
|
||||
virtual ~SessionData() = default;
|
||||
template <class T> T *As() { return reinterpret_cast<T *>(this); }
|
||||
};
|
||||
struct Session {
|
||||
std::string Id;
|
||||
long TTLSeconds = 1440; // 24 minutes 1440 seconds
|
||||
bool IsValid();
|
||||
void Update();
|
||||
void Remove(const std::string &key);
|
||||
bool Has(const std::string &key);
|
||||
Ref<SessionData> &operator[](const std::string &key) { return m_Data[key]; }
|
||||
void SetSessionData(const std::string &key, const Ref<SessionData> &data) {
|
||||
m_Data[key] = data;
|
||||
}
|
||||
bool ContainsData() { return !m_Data.empty(); }
|
||||
|
||||
protected:
|
||||
std::chrono::time_point<std::chrono::system_clock, ms> m_LastCall =
|
||||
std::chrono::system_clock::now();
|
||||
std::unordered_map<std::string, Ref<SessionData>> m_Data;
|
||||
};
|
||||
}
|
22
Includes/Socket.h
Normal file
22
Includes/Socket.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace VWeb {
|
||||
enum class EPollReturns { OK = 0, BREAK, FAILURE };
|
||||
enum class WriteState { OK = 0, EPOLL, ERRORED };
|
||||
|
||||
struct Accept {
|
||||
EPollReturns ReturnValue{EPollReturns::OK};
|
||||
std::stringstream Data{};
|
||||
ssize_t CurrentBytes;
|
||||
int SockId{-1};
|
||||
};
|
||||
|
||||
struct SendData {
|
||||
ssize_t Offset{0};
|
||||
ssize_t Size{0};
|
||||
int SocketID{0};
|
||||
std::string Content;
|
||||
};
|
||||
} // namespace VWeb
|
41
Includes/SocketManager.h
Normal file
41
Includes/SocketManager.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "Socket.h"
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
namespace VWeb {
|
||||
class SocketUtils {
|
||||
public:
|
||||
static bool MakeAsync(int socketId);
|
||||
static bool Add(int socketId);
|
||||
static bool Close(int socketId);
|
||||
static bool WriteDone(int socketId);
|
||||
static WriteState Write(SendData &);
|
||||
};
|
||||
|
||||
class ServerConfig;
|
||||
class SocketManager {
|
||||
public:
|
||||
explicit SocketManager(const Ref<ServerConfig> &);
|
||||
~SocketManager();
|
||||
Accept Handle();
|
||||
bool SetSendListen(int socketID);
|
||||
bool SetReadListen(int socketID);
|
||||
Ref<ServerConfig> &GetServerConfig() { return m_ServerConfig; }
|
||||
[[nodiscard]] int ID() const { return m_SocketID; }
|
||||
[[nodiscard]] bool IsErrored() const { return m_IsErrored; }
|
||||
void Init();
|
||||
|
||||
protected:
|
||||
void Errored(const std::string &data);
|
||||
|
||||
int m_SocketID{};
|
||||
Ref<ServerConfig> m_ServerConfig{};
|
||||
struct sockaddr_in m_Address {
|
||||
0
|
||||
};
|
||||
int m_AddressLength{sizeof(m_Address)};
|
||||
bool m_IsErrored{false};
|
||||
};
|
||||
} // namespace VWeb
|
27
Includes/ThreadPool.h
Normal file
27
Includes/ThreadPool.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#include "Queue.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace VWeb {
|
||||
struct WorkerJob {
|
||||
virtual ~WorkerJob() = default;
|
||||
virtual void Execute() = 0;
|
||||
};
|
||||
class ThreadPool {
|
||||
public:
|
||||
explicit ThreadPool(std::string name);
|
||||
void SetThreadCount(int);
|
||||
void Dispatch(const Ref<WorkerJob> &);
|
||||
void Create();
|
||||
void Stop();
|
||||
void Execute();
|
||||
|
||||
protected:
|
||||
std::string m_Name;
|
||||
int m_ThreadCount{1};
|
||||
bool m_IsCreated{false};
|
||||
std::vector<std::thread> m_Threads{};
|
||||
SafeQueue<Ref<WorkerJob>> m_Queue;
|
||||
bool m_IsDone{false};
|
||||
};
|
||||
} // namespace VWeb
|
11
Includes/Types.h
Normal file
11
Includes/Types.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace VWeb {
|
||||
template <typename T> using Ref = std::shared_ptr<T>;
|
||||
template <typename T, typename... Args>
|
||||
constexpr Ref<T> CreateRef(Args &&...args) {
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
} // namespace VWeb
|
7
Includes/VWeb.h
Normal file
7
Includes/VWeb.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "Request.h"
|
||||
#include "Response.h"
|
||||
#include "Route.h"
|
||||
#include "Server.h"
|
||||
#include "ServerConfig.h"
|
15
Source/CMakeLists.txt
Normal file
15
Source/CMakeLists.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
add_sources(
|
||||
EPollManager.cpp
|
||||
RequestHandler.cpp
|
||||
Server.cpp
|
||||
SocketManager.cpp
|
||||
ThreadPool.cpp
|
||||
Router.cpp
|
||||
MiddleWare.cpp
|
||||
Route.cpp
|
||||
StringUtils.cpp
|
||||
Cookie.cpp
|
||||
Session.cpp
|
||||
Response.cpp
|
||||
InbuildMiddleWare.cpp
|
||||
)
|
|
@ -1,7 +1,6 @@
|
|||
#include "Includes/VWeb.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include <VWeb.h>
|
||||
|
||||
namespace VWeb {
|
||||
void Cookies::Remove(const std::string &name) {
|
||||
if (Data.contains(name))
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#include <VWeb.h>
|
||||
#include "Includes/VWeb.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
namespace VWeb {
|
||||
EPollManager::EPollManager() {
|
||||
m_EpollID = epoll_create1(0);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "Includes/PreMiddleWare.h"
|
||||
#include "StringUtils.h"
|
||||
#include "VWeb.h"
|
||||
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include <VWeb.h>
|
||||
#include "Includes/VWeb.h"
|
||||
|
||||
namespace VWeb {
|
||||
std::optional<Ref<Response>>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "Includes/VWeb.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include <VWeb.h>
|
||||
|
||||
namespace VWeb {
|
||||
|
||||
HttpMethod StringToHTTPMethod(std::string &method) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include <VWeb.h>
|
||||
#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<std::string> &values) {
|
||||
const std::vector<std::string> &values) {
|
||||
auto &element = m_Headers[key];
|
||||
for (const auto &value : values)
|
||||
element.Add(value);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include <VWeb.h>
|
||||
#include <algorithm>
|
||||
#include "Includes/VWeb.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace VWeb {
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "Includes/VWeb.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include <VWeb.h>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
|
@ -119,7 +119,7 @@ Ref<Route> Router::FindRoute(Ref<Request> &request) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void Router::AddToArgs(Ref<Request> &request, Vector<std::string> &items) {
|
||||
void Router::AddToArgs(Ref<Request> &request, std::vector<std::string> &items) {
|
||||
request->URLParameters.push_back(items[items.size() - 1]);
|
||||
items.pop_back();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#include <VWeb.h>
|
||||
#include "Includes/PreMiddleWare.h"
|
||||
#include "Includes/VWeb.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace VWeb {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include <VWeb.h>
|
||||
#include "Includes/VWeb.h"
|
||||
|
||||
namespace VWeb {
|
||||
bool Session::IsValid() {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#include <VWeb.h>
|
||||
#include "Includes/VWeb.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
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) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <VWeb.h>
|
||||
#include "Includes/VWeb.h"
|
||||
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
|
|
651
VWeb.h
651
VWeb.h
|
@ -1,651 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <condition_variable>
|
||||
#include <fcntl.h>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <netinet/in.h>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace VWeb {
|
||||
|
||||
template <typename T> using Ref = std::shared_ptr<T>;
|
||||
template <typename T> using Vector = std::vector<T>;
|
||||
template <typename T, typename... Args>
|
||||
constexpr Ref<T> CreateRef(Args &&...args) {
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
typedef std::mutex Mutex;
|
||||
|
||||
#pragma region SAFESTRUCTS
|
||||
template <typename T, typename S> struct SafeMap {
|
||||
explicit SafeMap(size_t maxSize = -1UL) : m_MaxSize(maxSize), m_End(false){};
|
||||
bool Add(const T &t, S &x) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
while (m_Data.size() == m_MaxSize && !m_End)
|
||||
return false;
|
||||
assert(!m_End);
|
||||
m_Data.emplace(t, std::move(x));
|
||||
return true;
|
||||
};
|
||||
bool Add(T &&t, S &&x) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
while (m_Data.size() == m_MaxSize && !m_End)
|
||||
return false;
|
||||
assert(!m_End);
|
||||
m_Data.push(std::move(t));
|
||||
return true;
|
||||
};
|
||||
void Remove(T t) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
if (m_Data.empty() || m_End)
|
||||
return;
|
||||
if (m_Data.contains(t))
|
||||
m_Data.erase(t);
|
||||
m_CVFull.notify_one();
|
||||
};
|
||||
void Clear() {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
std::unordered_map<T, S> empty;
|
||||
std::swap(m_Data, empty);
|
||||
m_CVFull.notify_all();
|
||||
};
|
||||
bool Has(T t) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
return m_Data.contains(t);
|
||||
};
|
||||
S &Get(T t) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
return m_Data[t];
|
||||
};
|
||||
int Size() { return m_Data.size(); };
|
||||
|
||||
private:
|
||||
std::unordered_map<T, S> m_Data{};
|
||||
std::mutex m_Mutex{};
|
||||
std::condition_variable m_CVFull{};
|
||||
const size_t m_MaxSize{};
|
||||
std::atomic<bool> m_End{};
|
||||
};
|
||||
|
||||
template <typename T> struct SafeQueue {
|
||||
explicit SafeQueue(size_t maxSize = -1UL)
|
||||
: m_MaxSize(maxSize),
|
||||
m_End(false){};
|
||||
void Push(const T &t) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
while (m_Queue.size() == m_MaxSize && !m_End)
|
||||
m_CVFull.wait(lck);
|
||||
assert(!m_End);
|
||||
m_Queue.push(std::move(t));
|
||||
m_CVEmpty.notify_one();
|
||||
};
|
||||
void Push(T &&t) {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
while (m_Queue.size() == m_MaxSize && !m_End)
|
||||
m_CVFull.wait(lck);
|
||||
assert(!m_End);
|
||||
m_Queue.push(std::move(t));
|
||||
m_CVEmpty.notify_one();
|
||||
};
|
||||
void Open() {
|
||||
m_End = false;
|
||||
std::lock_guard<std::mutex> lck(m_Mutex);
|
||||
m_CVEmpty.notify_all();
|
||||
m_CVFull.notify_all();
|
||||
};
|
||||
void Close() {
|
||||
m_End = true;
|
||||
std::lock_guard<std::mutex> lck(m_Mutex);
|
||||
m_CVEmpty.notify_all();
|
||||
m_CVFull.notify_all();
|
||||
};
|
||||
void Clear() {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
std::queue<T> empty;
|
||||
std::swap(m_Queue, empty);
|
||||
m_CVEmpty.notify_all();
|
||||
m_CVFull.notify_all();
|
||||
};
|
||||
std::optional<T> Pop() {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
if (m_Queue.empty() || m_End)
|
||||
return {};
|
||||
T t = std::move(m_Queue.front());
|
||||
m_Queue.pop();
|
||||
m_CVFull.notify_one();
|
||||
return t;
|
||||
};
|
||||
std::optional<T> WaitAndPop() {
|
||||
std::unique_lock<std::mutex> lck(m_Mutex);
|
||||
while (m_Queue.empty() && !m_End)
|
||||
m_CVEmpty.wait(lck);
|
||||
if (m_Queue.empty() || m_End)
|
||||
return {};
|
||||
T t = std::move(m_Queue.front());
|
||||
m_Queue.pop();
|
||||
m_CVFull.notify_one();
|
||||
return t;
|
||||
};
|
||||
bool IsClosed() { return m_End; }
|
||||
int Size() { return m_Queue.size(); }
|
||||
std::queue<T> &GetQueue() { return m_Queue; }
|
||||
void Flush() {
|
||||
while (!m_Queue.empty())
|
||||
m_CVEmpty.notify_one();
|
||||
m_End = true;
|
||||
m_CVEmpty.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<T> m_Queue;
|
||||
std::mutex m_Mutex;
|
||||
std::condition_variable m_CVEmpty, m_CVFull;
|
||||
const size_t m_MaxSize;
|
||||
std::atomic<bool> m_End;
|
||||
};
|
||||
#pragma endregion SAFESTRUCTS
|
||||
#pragma region THREADING
|
||||
struct WorkerJob {
|
||||
virtual ~WorkerJob() = default;
|
||||
virtual void Execute() = 0;
|
||||
};
|
||||
class ThreadPool {
|
||||
public:
|
||||
explicit ThreadPool(std::string name);
|
||||
void SetThreadCount(int);
|
||||
void Dispatch(const Ref<WorkerJob> &);
|
||||
void Create();
|
||||
void Stop();
|
||||
void Execute();
|
||||
|
||||
protected:
|
||||
std::string m_Name;
|
||||
int m_ThreadCount{1};
|
||||
bool m_IsCreated{false};
|
||||
Vector<std::thread> m_Threads{};
|
||||
SafeQueue<Ref<WorkerJob>> m_Queue;
|
||||
bool m_IsDone{false};
|
||||
};
|
||||
#pragma endregion THREADING
|
||||
#pragma region VWEB
|
||||
enum class EPollReturns { OK = 0, BREAK, FAILURE };
|
||||
|
||||
class EPollManager;
|
||||
class SocketManager;
|
||||
struct ServerConfig {
|
||||
int Port{9020};
|
||||
int MaxBufferSize{-1};
|
||||
int BufferSize{16384};
|
||||
int WorkerThreads{-1};
|
||||
int MaxEvents{5000};
|
||||
bool AllowSharedLibs{true};
|
||||
Ref<EPollManager> EPoll{nullptr};
|
||||
Ref<SocketManager> Socket{nullptr};
|
||||
std::string ErrorDir;
|
||||
std::string MimeFiles;
|
||||
};
|
||||
struct Accept {
|
||||
EPollReturns ReturnValue{EPollReturns::OK};
|
||||
std::stringstream Data{};
|
||||
ssize_t CurrentBytes;
|
||||
int SockId{-1};
|
||||
};
|
||||
class EPollManager {
|
||||
public:
|
||||
EPollManager();
|
||||
~EPollManager();
|
||||
[[nodiscard]] bool Dispatch(int sock, uint32_t eventType = EPOLLIN) const;
|
||||
[[nodiscard]] bool UpdateEvents(int sock, uint32_t eventType = EPOLLIN) const;
|
||||
int Wait(int idx, epoll_event *events) const;
|
||||
|
||||
protected:
|
||||
int m_EpollID{-1};
|
||||
};
|
||||
|
||||
struct SendData {
|
||||
ssize_t Offset{0};
|
||||
ssize_t Size{0};
|
||||
int SocketID{0};
|
||||
std::string Content;
|
||||
};
|
||||
|
||||
enum class WriteState { OK = 0, EPOLL, ERRORED };
|
||||
|
||||
class SocketUtils {
|
||||
public:
|
||||
static bool MakeAsync(int socketId);
|
||||
static bool Add(int socketId);
|
||||
static bool Close(int socketId);
|
||||
static bool WriteDone(int socketId);
|
||||
static WriteState Write(SendData &);
|
||||
};
|
||||
class SocketManager {
|
||||
public:
|
||||
explicit SocketManager(const Ref<ServerConfig> &);
|
||||
~SocketManager();
|
||||
Accept Handle();
|
||||
bool SetSendListen(int socketID);
|
||||
bool SetReadListen(int socketID);
|
||||
Ref<ServerConfig> &GetServerConfig() { return m_ServerConfig; }
|
||||
[[nodiscard]] int ID() const { return m_SocketID; }
|
||||
[[nodiscard]] bool IsErrored() const { return m_IsErrored; }
|
||||
void Init();
|
||||
|
||||
protected:
|
||||
void Errored(const std::string &data);
|
||||
|
||||
int m_SocketID{};
|
||||
Ref<ServerConfig> m_ServerConfig{};
|
||||
struct sockaddr_in m_Address {
|
||||
0
|
||||
};
|
||||
int m_AddressLength{sizeof(m_Address)};
|
||||
bool m_IsErrored{false};
|
||||
};
|
||||
|
||||
class MiddleWareHandler;
|
||||
class RequestHandler;
|
||||
class Response;
|
||||
class Router;
|
||||
class Route;
|
||||
class Server {
|
||||
public:
|
||||
Server();
|
||||
// Will Load SharedLibs from subdirectory
|
||||
// This will allow that VWeb will run as Standalone and will load .so files
|
||||
// without recompiling itself
|
||||
void LoadSharedLibs();
|
||||
void Start();
|
||||
void Join() { m_WorkerThread->join(); }
|
||||
void Stop() { m_IsExit = true; }
|
||||
Ref<Router> &GetRouter() { return m_Router; }
|
||||
Ref<ServerConfig> &GetServerConfig() { return m_ServerConfig; }
|
||||
void AddRoute(const std::string &path, const Ref<Route> &route);
|
||||
void RemoveRoute(const std::string &path);
|
||||
|
||||
Ref<MiddleWareHandler> &Middleware();
|
||||
|
||||
protected:
|
||||
void Execute();
|
||||
void OutgoingExecute(epoll_event &event);
|
||||
void IncomingExecute(epoll_event &event);
|
||||
void HandleRequestReading(epoll_event &event);
|
||||
void CreateRequest(int sockID);
|
||||
|
||||
protected:
|
||||
Ref<Router> m_Router;
|
||||
Ref<ServerConfig> m_ServerConfig;
|
||||
Ref<RequestHandler> m_RequestHandler;
|
||||
SafeMap<int, Accept> m_RawRequest{60000};
|
||||
SafeMap<int, SendData> m_OutRequest{60000};
|
||||
Ref<std::thread> m_WorkerThread;
|
||||
Mutex m_Mutex;
|
||||
bool m_IsExit{false};
|
||||
|
||||
protected:
|
||||
friend RequestHandler;
|
||||
};
|
||||
#pragma region VWEB_ROUTING
|
||||
using ms = std::chrono::duration<double, std::milli>;
|
||||
struct SessionData {
|
||||
virtual ~SessionData() = default;
|
||||
template <class T> T *As() { return reinterpret_cast<T *>(this); }
|
||||
};
|
||||
struct Session {
|
||||
std::string Id;
|
||||
long TTLSeconds = 1440; // 24 minutes 1440 seconds
|
||||
bool IsValid();
|
||||
void Update();
|
||||
void Remove(const std::string &key);
|
||||
bool Has(const std::string &key);
|
||||
Ref<SessionData> &operator[](const std::string &key) { return m_Data[key]; }
|
||||
void SetSessionData(const std::string &key, const Ref<SessionData> &data) {
|
||||
m_Data[key] = data;
|
||||
}
|
||||
bool ContainsData() { return !m_Data.empty(); }
|
||||
|
||||
protected:
|
||||
std::chrono::time_point<std::chrono::system_clock, ms> m_LastCall =
|
||||
std::chrono::system_clock::now();
|
||||
std::unordered_map<std::string, Ref<SessionData>> m_Data;
|
||||
};
|
||||
enum class HttpStatusCode : int {
|
||||
Continue = 100,
|
||||
SwitchingProtocols = 101,
|
||||
Processing = 102,
|
||||
EarlyHints = 103,
|
||||
OK = 200,
|
||||
Created = 201,
|
||||
Accepted = 202,
|
||||
NonAuthoritativeInformation = 203,
|
||||
NoContent = 204,
|
||||
ResetContent = 205,
|
||||
PartialContent = 206,
|
||||
MultiStatus = 207,
|
||||
AlreadyReported = 208,
|
||||
IMUsed = 226,
|
||||
MultipleChoices = 300,
|
||||
MovedPermanently = 301,
|
||||
Found = 302,
|
||||
SeeOther = 303,
|
||||
NotModified = 304,
|
||||
UseProxy = 305,
|
||||
TemporaryRedirect = 307,
|
||||
PermanentRedirect = 308,
|
||||
BadRequest = 400,
|
||||
Unauthorized = 401,
|
||||
PaymentRequired = 402,
|
||||
Forbidden = 403,
|
||||
NotFound = 404,
|
||||
MethodNotAllowed = 405,
|
||||
NotAcceptable = 406,
|
||||
ProxyAuthenticationRequired = 407,
|
||||
RequestTimeout = 408,
|
||||
Conflict = 409,
|
||||
Gone = 410,
|
||||
LengthRequired = 411,
|
||||
PreconditionFailed = 412,
|
||||
PayloadTooLarge = 413,
|
||||
URITooLong = 414,
|
||||
UnsupportedMediaType = 415,
|
||||
RangeNotSatisfiable = 416,
|
||||
ExpectationFailed = 417,
|
||||
ImATeapot = 418,
|
||||
UnprocessableEntity = 422,
|
||||
Locked = 423,
|
||||
FailedDependency = 424,
|
||||
UpgradeRequired = 426,
|
||||
PreconditionRequired = 428,
|
||||
TooManyRequests = 429,
|
||||
RequestHeaderFieldsTooLarge = 431,
|
||||
UnavailableForLegalReasons = 451,
|
||||
InternalServerError = 500,
|
||||
NotImplemented = 501,
|
||||
BadGateway = 502,
|
||||
ServiceUnavailable = 503,
|
||||
GatewayTimeout = 504,
|
||||
HTTPVersionNotSupported = 505,
|
||||
VariantAlsoNegotiates = 506,
|
||||
InsufficientStorage = 507,
|
||||
LoopDetected = 508,
|
||||
NotExtended = 510,
|
||||
NetworkAuthenticationRequired = 511
|
||||
};
|
||||
enum class HttpMethod {
|
||||
GET = 0,
|
||||
HEAD,
|
||||
OPTIONS,
|
||||
POST,
|
||||
PUT,
|
||||
PATCH,
|
||||
DELETE,
|
||||
FALLBACK
|
||||
};
|
||||
enum class CookieSameSite { Invalid = 0, Strict, Lax, None };
|
||||
struct Cookie {
|
||||
std::string Name{};
|
||||
std::string Value{};
|
||||
std::string Expires{};
|
||||
int MaxAge{};
|
||||
std::string Domain{};
|
||||
std::string Path{"/"};
|
||||
bool Secure{};
|
||||
bool HttpOnly{};
|
||||
CookieSameSite SameSite{CookieSameSite::Strict};
|
||||
bool IsOld = false;
|
||||
};
|
||||
struct Cookies {
|
||||
public:
|
||||
void Remove(const std::string &name);
|
||||
bool Has(const std::string &name) const;
|
||||
Cookie &Get(const std::string &name) { return Data[name]; };
|
||||
std::string TransformToHeader();
|
||||
void CreateCookieFromString(const std::string &data);
|
||||
void CreateOld(const std::string &name, const std::string &value);
|
||||
|
||||
std::unordered_map<std::string, Cookie> Data;
|
||||
Cookie &operator[](const std::string &name) { return Data[name]; }
|
||||
|
||||
protected:
|
||||
static void CookieToString(std::stringstream &stream, Cookie &cookie);
|
||||
};
|
||||
struct ParameterValue {
|
||||
void Add(const std::string &item) { m_Data.push_back(item); }
|
||||
void Set(const std::string &item) {
|
||||
m_Data.clear();
|
||||
m_Data.push_back(item);
|
||||
}
|
||||
std::string &Get(int item) { return m_Data[item]; }
|
||||
std::string &GetFirst() { return Get(0); }
|
||||
size_t Size() { return m_Data.size(); }
|
||||
Vector<std::string> &Values() { return m_Data; }
|
||||
|
||||
protected:
|
||||
Vector<std::string> m_Data{};
|
||||
};
|
||||
|
||||
struct Request {
|
||||
public:
|
||||
int ID;
|
||||
std::string Body;
|
||||
HttpMethod Method{HttpMethod::GET};
|
||||
std::string URI;
|
||||
Ref<Session> SessionData;
|
||||
Ref<Cookies> CookieData;
|
||||
Cookie &GetCookie(const std::string &key) { return CookieData->Get(key); };
|
||||
ParameterValue &Parameter(const std::string &key) { return Parameters[key]; }
|
||||
bool HasParameter(const std::string &key) const {
|
||||
return Parameters.contains(key);
|
||||
}
|
||||
bool HasHeader(const std::string &key) const { return Headers.contains(key); }
|
||||
std::string &FirstOf(const std::string &key) {
|
||||
return Parameters[key].GetFirst();
|
||||
}
|
||||
ParameterValue &Header(const std::string &key) { return Headers[key]; }
|
||||
std::unordered_map<std::string, ParameterValue> Parameters;
|
||||
std::unordered_map<std::string, ParameterValue> Headers;
|
||||
Vector<std::string> URLParameters;
|
||||
};
|
||||
class Response {
|
||||
public:
|
||||
static Ref<Response> FromCode(HttpStatusCode code);
|
||||
public:
|
||||
size_t Length{0};
|
||||
Ref<Cookies> CookieData{nullptr};
|
||||
Ref<Session> SessionData{nullptr};
|
||||
HttpStatusCode Status{HttpStatusCode::OK};
|
||||
HttpMethod Method{HttpMethod::GET};
|
||||
|
||||
public:
|
||||
std::string GetResponse();
|
||||
void SetHeader(const std::string &key, ParameterValue &value);
|
||||
void SetHeader(const std::string &key, const std::string &value);
|
||||
void AddHeaders(const std::string &key, const Vector<std::string> &values);
|
||||
void AddHeader(const std::string &key, const std::string &value);
|
||||
void SetType(const std::string &type);
|
||||
void AddContent(const std::string &data);
|
||||
void SetStatus(HttpStatusCode);
|
||||
void SetMethod(HttpMethod);
|
||||
void Reset();
|
||||
Response &operator<<(const std::string &data) {
|
||||
m_Content << data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string TransformHeaders(const std::string &content);
|
||||
std::string m_Type{"text/html"};
|
||||
std::stringstream m_Content;
|
||||
std::unordered_map<std::string, ParameterValue> m_Headers;
|
||||
};
|
||||
class MiddleWareHandler;
|
||||
typedef std::function<const void(Request &, const std::string& body)> ParseFunction;
|
||||
class RequestHandler {
|
||||
public:
|
||||
explicit RequestHandler(const Ref<SocketManager> &manager);
|
||||
void InitThreads(int count);
|
||||
void AddRequest(Ref<Request> &request);
|
||||
void Stop();
|
||||
void SetRouter(Ref<Router> &router);
|
||||
void AddSendResponse(SendData);
|
||||
Ref<MiddleWareHandler> &Middleware() { return m_MiddleWare; }
|
||||
bool HasBodyParser(const std::string &key) {
|
||||
return m_ParseFunctions.contains(key);
|
||||
}
|
||||
ParseFunction &GetBodyParser(const std::string &key) {
|
||||
return m_ParseFunctions.at(key);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unordered_map<std::string, ParseFunction> m_ParseFunctions;
|
||||
Ref<Router> m_Router{nullptr};
|
||||
Ref<SocketManager> m_SocketManager{nullptr};
|
||||
Server *m_Server{nullptr};
|
||||
SafeQueue<Ref<Request>> m_Requests{};
|
||||
ThreadPool m_Pool{"RequestHandler"};
|
||||
Ref<MiddleWareHandler> m_MiddleWare{nullptr};
|
||||
friend Server;
|
||||
};
|
||||
|
||||
typedef std::function<bool(Request &, Response &)> RouteFunction;
|
||||
class Route {
|
||||
public:
|
||||
Route() = default;
|
||||
virtual ~Route() = default;
|
||||
Route(std::initializer_list<HttpMethod>);
|
||||
virtual bool Execute(Request &request, Response &response);
|
||||
virtual bool Get(Request &request, Response &response);
|
||||
virtual bool Post(Request &request, Response &response);
|
||||
virtual bool Put(Request &request, Response &response);
|
||||
virtual bool Patch(Request &request, Response &response);
|
||||
virtual bool Delete(Request &request, Response &response);
|
||||
bool Options(Request &request, Response &response);
|
||||
virtual bool Fallback(Request &request, Response &response);
|
||||
bool SupportsMethod(Request &request);
|
||||
virtual bool IsAllowed(Request &request);
|
||||
|
||||
void AllowMethod(HttpMethod method);
|
||||
|
||||
protected:
|
||||
bool m_AllowAll{true};
|
||||
Vector<HttpMethod> m_AllowedMethods;
|
||||
friend Router;
|
||||
};
|
||||
|
||||
class Router {
|
||||
public:
|
||||
Router();
|
||||
void AddRoute(const std::string &name, const Ref<Route> &route);
|
||||
Ref<Route> &GetRoute(const std::string &name);
|
||||
void DeleteRoute(const std::string &name);
|
||||
|
||||
Ref<Response> HandleRoute(Ref<Request> &request);
|
||||
Ref<Route> FindRoute(Ref<Request> &request);
|
||||
static void AddToArgs(Ref<Request> &request, Vector<std::string> &items);
|
||||
|
||||
public:
|
||||
void Get(const std::string &path, RouteFunction);
|
||||
void Post(const std::string &path, RouteFunction);
|
||||
void Put(const std::string &path, RouteFunction);
|
||||
void Patch(const std::string &path, RouteFunction);
|
||||
void Delete(const std::string &path, RouteFunction);
|
||||
std::unordered_map<std::string, Ref<Route>> m_Routes;
|
||||
};
|
||||
|
||||
typedef std::optional<Ref<Response>> PreMiddleWareReturn;
|
||||
struct MiddleWare {
|
||||
int Pos{0};
|
||||
virtual PreMiddleWareReturn PreHandle(Request &) { return {}; }
|
||||
virtual bool PostHandle(Request &, Response &) { return true; }
|
||||
virtual void Shutdown(Request &, const Response &){};
|
||||
bool operator<(const MiddleWare *rhs) const { return Pos < rhs->Pos; }
|
||||
};
|
||||
|
||||
class MiddleWareHandler {
|
||||
public:
|
||||
PreMiddleWareReturn HandlePre(Ref<Request> &);
|
||||
void HandlePost(Ref<Request> &, Ref<Response> &);
|
||||
void Shutdown(Ref<Request> &, Ref<Response> &);
|
||||
|
||||
public:
|
||||
template <class T> Ref<MiddleWare> GetRef() {
|
||||
return GetById(typeid(T).name());
|
||||
}
|
||||
template <class T> T &Get() {
|
||||
return static_cast<T &>(*GetById(typeid(T).name()));
|
||||
}
|
||||
template <class T> void Set(Ref<MiddleWare> &instance) {
|
||||
auto &type = typeid(T);
|
||||
if (type.before(typeid(MiddleWare)))
|
||||
SetById(type.name(), instance);
|
||||
}
|
||||
template <class T> T &Create() {
|
||||
return static_cast<T &>(*CreateMiddleWare<T>());
|
||||
}
|
||||
template <class T> void Remove() { RemoveById(typeid(T).name()); }
|
||||
|
||||
protected:
|
||||
template <class T> Ref<MiddleWare> CreateMiddleWare() {
|
||||
return SetById(typeid(T).name(), CreateRef<T>());
|
||||
}
|
||||
Ref<MiddleWare> GetById(const char *id);
|
||||
Ref<MiddleWare> SetById(const char *id, const Ref<MiddleWare> &);
|
||||
void RemoveById(const char *id);
|
||||
std::map<std::string, Ref<MiddleWare>> m_MiddleWares;
|
||||
};
|
||||
|
||||
typedef std::function<PreMiddleWareReturn(Request &req)> AuthFunction;
|
||||
class AuthWare : public MiddleWare {
|
||||
public:
|
||||
AuthWare() = default;
|
||||
~AuthWare() = default;
|
||||
PreMiddleWareReturn PreHandle(Request &request) override;
|
||||
void SetAuthMethod(AuthFunction function) {
|
||||
m_AuthFunction = std::move(function);
|
||||
}
|
||||
|
||||
protected:
|
||||
AuthFunction m_AuthFunction{nullptr};
|
||||
};
|
||||
|
||||
class SessionManager : public MiddleWare {
|
||||
public:
|
||||
SessionManager();
|
||||
~SessionManager();
|
||||
PreMiddleWareReturn PreHandle(Request &request) override;
|
||||
bool PostHandle(Request &request, Response &response) override;
|
||||
|
||||
protected:
|
||||
void GC();
|
||||
|
||||
protected:
|
||||
Ref<std::thread> m_GCThread;
|
||||
std::mutex m_Mutex;
|
||||
std::unordered_map<std::string, Ref<Session>> m_Sessions;
|
||||
int m_Counter{-1};
|
||||
bool m_IsRunning{true};
|
||||
};
|
||||
|
||||
class CookieManager : public MiddleWare {
|
||||
public:
|
||||
CookieManager() = default;
|
||||
~CookieManager() = default;
|
||||
PreMiddleWareReturn PreHandle(Request &) override;
|
||||
};
|
||||
|
||||
#pragma endregion VWEB_ROUTING
|
||||
#pragma endregion VWEB
|
||||
} // namespace VWeb
|
|
@ -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}/..)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include <VWeb.h>
|
||||
#include "VWeb-1.0/VWeb.h"
|
||||
|
||||
class MyCompleteController : public VWeb::Route {
|
||||
public:
|
||||
|
|
Loading…
Reference in a new issue