First Running Version

This commit is contained in:
Maurice Grönwoldt 2022-08-23 14:13:21 +02:00
commit 792abbee93
21 changed files with 1632 additions and 0 deletions

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
build/
debug-build/
release-build/
cmake-build-debug/
.idea/
example/build

29
CMakeLists.txt Normal file
View file

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.23)
project(VWeb)
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)
include_directories(${CMAKE_SOURCE_DIR}/)
add_library(VWeb ${SOURCE_FILES})
set(mode release)
if (CMAKE_BUILD_TYPE STREQUAL "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:VWeb> ${target_file})

107
Source/Cookie.cpp Normal file
View file

@ -0,0 +1,107 @@
#include "StringUtils.h"
#include <VWeb.h>
namespace VWeb {
void Cookies::Remove(const std::string &name) {
if (Data.contains(name))
Data.erase(name);
}
bool Cookies::Has(const std::string &name) const { return Data.contains(name); }
std::string Cookies::TransformToHeader() {
std::stringstream view{};
for (auto &cookie : Data) {
auto &realCookie = cookie.second;
if (!realCookie.IsOld && !realCookie.Name.empty() &&
!realCookie.Value.empty())
CookieToString(view, realCookie);
}
return view.str();
}
void Cookies::CreateCookieFromString(const std::string &data) {
auto params = String::Split(data, ";");
Cookie cookie{};
int i = 0;
for (auto &param : params) {
auto split = String::Split(param, "=");
if (split.size() == 1) {
// we have a bool parameter
if (split[0] == "HttpOnly")
cookie.HttpOnly = true;
else if (split[0] == "Secure")
cookie.Secure = true;
} else {
if (i == 0) {
cookie.Name = split[0];
cookie.Value = split[1];
} else {
if (split[0] == "SameSite") {
if (split[1] == "None")
cookie.SameSite = CookieSameSite::None;
else if (split[1] == "Lax")
cookie.SameSite = CookieSameSite::Lax;
else if (split[1] == "Strict")
cookie.SameSite = CookieSameSite::Strict;
} else if (split[0] == "Path")
cookie.Path = split[1];
else if (split[0] == "Domain")
cookie.Domain = split[1];
else if (split[0] == "Max-Age")
cookie.MaxAge = String::ToNumber(split[1], 0);
else if (split[0] == "Expires")
cookie.Expires = split[1];
}
}
i++;
}
if (!cookie.Name.empty() && !cookie.Value.empty())
Data[cookie.Name] = std::move(cookie);
}
void Cookies::CreateOld(const std::string &name, const std::string &value) {
auto& cookie = Data[name];
cookie.Name = name;
cookie.Value = value;
cookie.IsOld = true;
}
void Cookies::CookieToString(std::stringstream &stream, Cookie &cookie) {
if (cookie.Name.empty() || cookie.Value.empty())
return;
std::stringstream view{};
// Cookie names that are starting with __Secure or __Host are Secure only
// Cookies!
if (cookie.Name.starts_with("__Secure"))
cookie.Secure = true;
if (cookie.Name.starts_with("__Host")) {
cookie.Secure = true;
if (cookie.Path.empty())
cookie.Path = "/";
}
view << "Set-Cookie: " << cookie.Name << "=" << cookie.Value;
if (!cookie.Expires.empty())
view << "; Expires=" << cookie.Expires;
if (cookie.MaxAge)
view << "; Max-Age=" << cookie.MaxAge;
if (!cookie.Domain.empty())
view << "; Domain=" << cookie.Domain;
if (!cookie.Path.empty())
view << "; Path=" << cookie.Path;
if (cookie.Secure || cookie.SameSite == CookieSameSite::None)
view << "; Secure";
if (cookie.HttpOnly)
view << "; HttpOnly";
if (cookie.SameSite != CookieSameSite::Invalid) {
std::string sameSiteValue;
switch (cookie.SameSite) {
case CookieSameSite::None: sameSiteValue = "None"; break;
case CookieSameSite::Strict: sameSiteValue = "Strict"; break;
case CookieSameSite::Lax: sameSiteValue = "Lax"; break;
default: break;
}
view << "; SameSite=" << sameSiteValue;
}
view << "\n";
}
} // namespace VWeb

35
Source/EPollManager.cpp Normal file
View file

@ -0,0 +1,35 @@
#include <VWeb.h>
namespace VWeb {
EPollManager::EPollManager() {
m_EpollID = epoll_create1(0);
if (m_EpollID < 0)
exit(49);
unsigned int flags = fcntl(m_EpollID, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(m_EpollID, F_SETFL, flags);
}
EPollManager::~EPollManager() { close(m_EpollID); }
bool EPollManager::Dispatch(int sock, uint32_t eventType) const {
struct epoll_event event {
0
};
event.data.fd = sock;
event.events = eventType;
return epoll_ctl(m_EpollID, EPOLL_CTL_ADD, sock, &event) != -1;
}
bool EPollManager::UpdateEvents(int sock, uint32_t eventType) const {
struct epoll_event event {
0
};
event.data.fd = sock;
event.events = eventType;
return epoll_ctl(m_EpollID, EPOLL_CTL_MOD, sock, &event) != -1;
}
int EPollManager::Wait(int maxEvents, epoll_event *events) const {
return epoll_wait(m_EpollID, events, maxEvents, 10);
}
} // namespace VWeb

33
Source/MiddleWare.cpp Normal file
View file

@ -0,0 +1,33 @@
#include <VWeb.h>
namespace VWeb {
void MiddleWareHandler::HandlePre(Ref<Request> &request) {
for (auto &[key, middleWare] : m_MiddleWares) {
middleWare->PreHandle(*request);
}
}
void MiddleWareHandler::HandlePost(Ref<Request> &request,
Ref<Response> &response) {
for (auto &[key, middleWare] : m_MiddleWares) {
middleWare->PostHandle(*request, *response);
}
}
void MiddleWareHandler::Shutdown(Ref<Request> &request,
Ref<Response> &response) {
for (auto &[key, middleWare] : m_MiddleWares) {
middleWare->Shutdown(*request, *response);
}
}
Ref<MiddleWare> MiddleWareHandler::GetById(const char *id) {
return m_MiddleWares[id];
}
Ref<MiddleWare> MiddleWareHandler::SetById(const char *id,
const Ref<MiddleWare> &middleWare) {
m_MiddleWares[id] = middleWare;
return middleWare;
}
void MiddleWareHandler::RemoveById(const char *id) {
if (m_MiddleWares.contains(id))
m_MiddleWares.erase(id);
}
} // namespace VWeb

99
Source/RequestHandler.cpp Normal file
View file

@ -0,0 +1,99 @@
#include "StringUtils.h"
#include <VWeb.h>
namespace VWeb {
HttpMethod StringToHTTPMethod(std::string &method) {
static std::unordered_map<std::string, HttpMethod> s_StringToMethodMap{
{"get", HttpMethod::GET}, {"head", HttpMethod::HEAD},
{"options", HttpMethod::OPTIONS}, {"post", HttpMethod::POST},
{"put", HttpMethod::PUT}, {"patch", HttpMethod::PATCH},
{"delete", HttpMethod::DELETE}, {"fallback", HttpMethod::FALLBACK},
};
String::ToLowerCase(method);
if (s_StringToMethodMap.contains(method))
return s_StringToMethodMap[method];
return s_StringToMethodMap["fallback"];
}
bool ParseRequest(Ref<Request> &request) {
std::istringstream resp(request->Body);
std::string line;
std::string::size_type index;
while (std::getline(resp, line) && line != "\r") {
index = line.find(':', 0);
if (index != std::string::npos) {
auto key = line.substr(0, index);
String::Trim(key);
request->Header(key).Add(String::TrimCopy(line.substr(index + 1)));
} else if (line.find("HTTP")) {
auto headers = String::Split(line, " ");
if (headers.size() != 3)
return false;
request->Method = StringToHTTPMethod(headers[0]);
request->URI = String::UrlDecode(headers[1]);
} else {
return false;
}
}
return true;
}
struct RequestJob : public WorkerJob {
Ref<Request> MRequest{};
Ref<Router> MRouter{};
Ref<MiddleWareHandler> MMiddleWareHandler{};
RequestHandler *MRequestHandler{nullptr};
void Execute() override {
if (!ParseRequest(MRequest)) {
fprintf(stderr, "[Request] >> Request failed to parse\n");
SocketUtils::Close(MRequest->ID);
return;
}
MRequest->CookieData = CreateRef<Cookies>();
MRequest->SessionData = CreateRef<Session>();
MMiddleWareHandler->HandlePre(MRequest);
auto response = MRouter->HandleRoute(MRequest);
MMiddleWareHandler->HandlePost(MRequest, response);
auto content = response->GetResponse();
MRequestHandler->AddSendResponse(
{0, (ssize_t)content.size(), MRequest->ID, content});
MMiddleWareHandler->Shutdown(MRequest, response);
}
};
RequestHandler::RequestHandler(const Ref<SocketManager> &manager)
: m_SocketManager(manager), m_MiddleWare(CreateRef<MiddleWareHandler>()) {
m_MiddleWare = CreateRef<MiddleWareHandler>();
}
void RequestHandler::InitThreads(int count) {
m_Pool.SetThreadCount(count);
m_Pool.Create();
}
void RequestHandler::AddRequest(Ref<Request> &request) {
if (m_Router) {
auto job = CreateRef<RequestJob>();
job->MRequest = request;
job->MMiddleWareHandler = m_MiddleWare;
job->MRequestHandler = this;
job->MRouter = m_Router;
m_Pool.Dispatch(job);
}
}
void RequestHandler::Stop() {
m_Pool.Stop();
}
void RequestHandler::SetRouter(Ref<Router> &router) {
m_Router = router;
}
void RequestHandler::AddSendResponse(SendData sendData) {
auto id = sendData.SocketID;
m_Server->m_OutRequest.Add(id, sendData);
if (!m_SocketManager->SetSendListen(id)) {
SocketUtils::Close(id);
m_Server->m_OutRequest.Remove(id);
}
}
} // namespace VWeb

112
Source/Response.cpp Normal file
View file

@ -0,0 +1,112 @@
#include <VWeb.h>
namespace VWeb {
// clang-format off
static std::unordered_map<HttpStatusCode, std::string> s_HTTPCodeToString = {
{HttpStatusCode::Continue, "100 Continue"},
{HttpStatusCode::SwitchingProtocols, "101 Switching Protocols"},
{HttpStatusCode::Processing, "102 Processing"},
{HttpStatusCode::EarlyHints, "103 Early Hints"},
{HttpStatusCode::OK, "200 OK"},
{HttpStatusCode::Created, "201 Created"},
{HttpStatusCode::Accepted, "202 Accepted"},
{HttpStatusCode::NonAuthoritativeInformation,"203 Non-Authoritative Information"},
{HttpStatusCode::NoContent, "204 No Content"},
{HttpStatusCode::ResetContent, "205 Reset Content"},
{HttpStatusCode::PartialContent, "206 Partial Content"},
{HttpStatusCode::MultiStatus, "207 Multi-Status"},
{HttpStatusCode::AlreadyReported, "208 Already Reported"},
{HttpStatusCode::IMUsed, "226 IM Used"},
{HttpStatusCode::MultipleChoices, "300 Multiple Choices"},
{HttpStatusCode::MovedPermanently, "301 Moved Permanently"},
{HttpStatusCode::Found, "302 Found"},
{HttpStatusCode::SeeOther, "303 See Other"},
{HttpStatusCode::NotModified, "304 Not Modified"},
{HttpStatusCode::UseProxy, "305 Use Proxy"},
{HttpStatusCode::TemporaryRedirect, "307 Temporary Redirect"},
{HttpStatusCode::PermanentRedirect, "308 Permanent Redirect"},
{HttpStatusCode::BadRequest, "400 Bad Request"},
{HttpStatusCode::Unauthorized, "401 Unauthorized"},
{HttpStatusCode::PaymentRequired, "402 Payment Required"},
{HttpStatusCode::Forbidden, "403 Forbidden"},
{HttpStatusCode::NotFound, "404 Not Found"},
{HttpStatusCode::MethodNotAllowed, "405 Method Not Allowed"},
{HttpStatusCode::NotAcceptable, "406 Not Acceptable"},
{HttpStatusCode::ProxyAuthenticationRequired,"407 Proxy Authentication Required"},
{HttpStatusCode::RequestTimeout, "408 Request Timeout"},
{HttpStatusCode::Conflict, "409 Conflict"},
{HttpStatusCode::Gone, "410 Gone"},
{HttpStatusCode::LengthRequired, "411 Length Required"},
{HttpStatusCode::PreconditionFailed, "412 Precondition Failed"},
{HttpStatusCode::PayloadTooLarge, "413 Payload Too Large"},
{HttpStatusCode::URITooLong, "414 URI Too Long"},
{HttpStatusCode::UnsupportedMediaType, "415 Unsupported Media Type"},
{HttpStatusCode::RangeNotSatisfiable, "416 Range Not Satisfiable"},
{HttpStatusCode::ExpectationFailed, "417 Expectation Failed"},
{HttpStatusCode::ImATeapot, "418 I'm a teapot"},
{HttpStatusCode::UnprocessableEntity, "422 Unprocessable Entity"},
{HttpStatusCode::Locked, "423 Locked"},
{HttpStatusCode::FailedDependency, "424 Failed Dependency"},
{HttpStatusCode::UpgradeRequired, "426 Upgrade Required"},
{HttpStatusCode::PreconditionRequired, "428 Precondition Required"},
{HttpStatusCode::TooManyRequests, "429 Too Many Requests"},
{HttpStatusCode::RequestHeaderFieldsTooLarge,"431 Request Header Fields Too Large"},
{HttpStatusCode::UnavailableForLegalReasons,"451 Unavailable For Legal Reasons"},
{HttpStatusCode::InternalServerError, "500 Internal Server Error"},
{HttpStatusCode::NotImplemented, "501 Not Implemented"},
{HttpStatusCode::BadGateway, "502 Bad Gateway"},
{HttpStatusCode::ServiceUnavailable, "503 Service Unavailable"},
{HttpStatusCode::GatewayTimeout, "504 Gateway Time-out"},
{HttpStatusCode::HTTPVersionNotSupported, "505 HTTP Version Not Supported"},
{HttpStatusCode::VariantAlsoNegotiates, "506 Variant Also Negotiates"},
{HttpStatusCode::InsufficientStorage, "507 Insufficient Storage"},
{HttpStatusCode::LoopDetected, "508 Loop Detected"},
{HttpStatusCode::NotExtended, "510 Not Extended"},
{HttpStatusCode::NetworkAuthenticationRequired,"511 Network Authentication Required"}
};
// clang-format on
std::string Response::GetResponse() {
std::string content = m_Content.str();
auto headData = TransformHeaders(content);
auto outContent = std::string(headData + content);
Length = content.length();
return outContent;
}
void Response::SetHeader(const std::string &key, const std::string &value) {
m_Headers[key].Set(value);
}
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) {
auto &element = m_Headers[key];
for (const auto &value : values)
element.Add(value);
}
void Response::AddHeader(const std::string &key, const std::string &value) {
m_Headers[key].Add(value);
}
void Response::SetType(const std::string &type) { m_Type = type; }
void Response::AddContent(const std::string &data) { m_Content << data; }
void Response::SetStatus(HttpStatusCode statusCode) { Status = statusCode; }
void Response::SetMethod(HttpMethod method) { Method = method; }
void Response::Reset() { m_Content.clear(); }
std::string Response::TransformHeaders(const std::string &content) {
std::ostringstream stream;
stream << "HTTP/1.1 " << s_HTTPCodeToString[Status] << "\n";
stream << "content-type: " << m_Type << "\n";
stream << "content-length: " << content.length() << "\n";
for (auto &[key, value] : m_Headers) {
for (auto &val : value.Values()) {
stream << key << ": " << val << "\n";
}
}
stream << CookieData->TransformToHeader();
stream << "\n";
return stream.str();
}
} // namespace VWeb

83
Source/Route.cpp Normal file
View file

@ -0,0 +1,83 @@
#include <VWeb.h>
#include <algorithm>
namespace VWeb {
#define stringify(name) {name, std::string(#name).replace(0,12,"")}
static std::unordered_map<HttpMethod, std::string> s_HttpMethodToString = {
stringify(HttpMethod::HEAD),
stringify(HttpMethod::GET),
stringify(HttpMethod::OPTIONS),
stringify(HttpMethod::POST),
stringify(HttpMethod::PUT),
stringify(HttpMethod::PATCH),
stringify(HttpMethod::DELETE),
stringify(HttpMethod::FALLBACK)
};
#undef stringify
Route::Route(std::initializer_list<HttpMethod> methods) {
m_AllowedMethods = methods;
m_AllowAll = false;
m_AllowedMethods.push_back(HttpMethod::HEAD);
m_AllowedMethods.push_back(HttpMethod::OPTIONS);
}
bool Route::Execute(const Request &request, Response &response) {
switch (request.Method) {
case HttpMethod::GET:
case HttpMethod::HEAD: return Get(request, response);
case HttpMethod::POST: return Post(request, response);
case HttpMethod::PUT: return Put(request, response);
case HttpMethod::OPTIONS: return Options(request, response);
case HttpMethod::PATCH: return Patch(request, response);
case HttpMethod::DELETE: return Delete(request, response);
default: return Fallback(request, response);
}
}
bool Route::Get(const Request &request, Response &response) {
return true;
}
bool Route::Post(const Request &request, Response &response) {
return true;
}
bool Route::Put(const Request &request, Response &response) {
return true;
}
bool Route::Patch(const Request &request, Response &response) {
return true;
}
bool Route::Delete(const Request &request, Response &response) {
return true;
}
bool Route::Options(const Request &request, Response &response) {
std::stringstream str{};
bool isFirst = true;
if (m_AllowAll) {
for (auto &[key, value] : s_HttpMethodToString) {
if (!isFirst)
str << ", ";
str << value;
isFirst = false;
}
} else {
for (auto &method : m_AllowedMethods) {
if (!isFirst)
str << ", ";
str << s_HttpMethodToString[method];
isFirst = false;
}
}
response.SetHeader("Allow", str.str());
return true;
}
bool Route::Fallback(const Request &request, Response &response) {
return true;
}
bool Route::IsAllowed(const Request &request) { return true; }
bool Route::SupportsMethod(const Request &request) {
return m_AllowAll ||
std::find(m_AllowedMethods.begin(), m_AllowedMethods.end(),
request.Method) != m_AllowedMethods.end();
}
} // namespace VWeb

97
Source/Router.cpp Normal file
View file

@ -0,0 +1,97 @@
#include "StringUtils.h"
#include <VWeb.h>
#include <type_traits>
namespace VWeb {
template <typename E> constexpr auto to_underlying(E e) noexcept {
return static_cast<std::underlying_type_t<E>>(e);
}
class ErrorRoute : public Route {
public:
bool Execute(const Request &request, Response &response) override {
response.Reset();
response << "Unhandled Error: Status "
<< std::to_string(to_underlying(response.Status));
response.SetType("text/plain");
return true;
}
};
Router::Router() { m_Routes["@"] = CreateRef<ErrorRoute>(); }
void Router::AddRoute(const std::string &name, const Ref<Route> &route) {
m_Routes[name] = route;
}
Ref<Route> &Router::GetRoute(const std::string &name) { return m_Routes[name]; }
void Router::DeleteRoute(const std::string &name) {
if (m_Routes.contains(name)) {
m_Routes.erase(name);
}
}
Ref<Response> Router::HandleRoute(Ref<Request> &request) {
auto response = CreateRef<Response>();
auto route = FindRoute(request);
response->CookieData = request->CookieData;
response->Method = request->Method;
if (!route) {
response->SetStatus(HttpStatusCode::NotFound);
m_Routes["@"]->Execute(*request, *response);
return response;
}
if (!route->IsAllowed(*request)) {
response->SetStatus(HttpStatusCode::Forbidden);
m_Routes["@"]->Execute(*request, *response);
return response;
}
if (!route->Execute(*request, *response)) {
std::string rKey = "@" + std::to_string(to_underlying(response->Status));
m_Routes.contains(rKey) ? m_Routes[rKey]->Execute(*request, *response)
: m_Routes["@"]->Execute(*request, *response);
}
return response;
}
Ref<Route> Router::FindRoute(Ref<Request> &request) {
auto &url = request->URI;
if (m_Routes.contains(url.data())) {
auto &route = m_Routes.at(url.data());
if (route->SupportsMethod(*request))
return route;
}
if (url.starts_with("@"))
return nullptr;
auto split = String::Split(url.data(), "/");
if (split.size() > 1) {
AddToArgs(request, split);
while (split.size() > 1) {
std::string nUrl = String::Join(split, "/");
if (m_Routes.contains(nUrl)) {
auto &route = m_Routes[nUrl];
if (route->SupportsMethod(*request))
return route;
}
AddToArgs(request, split);
}
}
if (m_Routes.contains("/")) {
auto &route = m_Routes["/"];
if (route->SupportsMethod(*request))
return route;
}
return nullptr;
}
void Router::AddToArgs(Ref<Request> &request, Vector<std::string> &items) {
request->URLParameters.push_back(items[items.size() - 1]);
items.pop_back();
}
} // namespace VWeb

135
Source/Server.cpp Normal file
View file

@ -0,0 +1,135 @@
#include <VWeb.h>
#include <cstring>
namespace VWeb {
Server::Server() {
m_Router = CreateRef<Router>();
m_ServerConfig = CreateRef<ServerConfig>();
m_ServerConfig->EPoll = CreateRef<EPollManager>();
m_ServerConfig->Socket = CreateRef<SocketManager>(m_ServerConfig);
m_RequestHandler = CreateRef<RequestHandler>(m_ServerConfig->Socket);
m_RequestHandler->m_Server = this;
};
void Server::LoadSharedLibs() {
// @TODO: LOAD .so files inside sub-folder
}
void Server::Start() {
m_ServerConfig->Socket->Init();
if (m_ServerConfig->Socket->IsErrored())
return;
m_RequestHandler->SetRouter(m_Router);
m_RequestHandler->InitThreads(m_ServerConfig->WorkerThreads);
m_WorkerThread = CreateRef<std::thread>(&Server::Execute, this);
fprintf(stdout, "[VWeb] Running Server On: 0.0.0.0:%d\n",
m_ServerConfig->Port);
}
void Server::AddRoute(const std::string &path, const Ref<Route> &route) {
m_Router->AddRoute(path, route);
}
void Server::RemoveRoute(const std::string &path) {
m_Router->DeleteRoute(path);
}
void Server::Execute() {
constexpr size_t MAX_EVENTS = 5000;
struct epoll_event events[MAX_EVENTS];
int sockID = m_ServerConfig->Socket->ID();
for (;;) {
if (m_IsExit)
break;
int eventCount = m_ServerConfig->EPoll->Wait(MAX_EVENTS, events);
for (int i = 0; i < eventCount; ++i) {
auto &event = events[i];
if ((event.events & EPOLLERR) || (event.events & EPOLLHUP)) {
m_RawRequest.Remove(event.data.fd);
SocketUtils::Close(event.data.fd);
continue;
}
if (event.events & EPOLLOUT) {
OutgoingExecute(events[i]);
}
if (event.data.fd == sockID) {
IncomingExecute(event);
} else if (event.events & EPOLLIN) {
HandleRequestReading(event);
}
}
}
}
void Server::OutgoingExecute(epoll_event &event) {
if (!m_OutRequest.Has(event.data.fd)) {
m_ServerConfig->Socket->SetReadListen(event.data.fd);
return;
}
auto &sendData = m_OutRequest.Get(event.data.fd);
auto returnVal = SocketUtils::Write(sendData);
if (returnVal == WriteState::ERRORED || returnVal == WriteState::OK) {
SocketUtils::WriteDone(sendData.SocketID);
m_OutRequest.Remove(event.data.fd);
}
}
void Server::IncomingExecute(epoll_event &event) {
for (;;) {
auto req = m_ServerConfig->Socket->Handle();
if (req.ReturnValue == EPollReturns::BREAK ||
req.ReturnValue == EPollReturns::FAILURE)
break;
else if (req.ReturnValue == EPollReturns::OK) {
if (!m_RawRequest.Add(req.SockId, req))
SocketUtils::Close(req.SockId);
} else
continue;
}
}
void Server::HandleRequestReading(epoll_event &event) {
int fd = event.data.fd;
if (!m_RawRequest.Has(fd)) {
SocketUtils::Close(fd);
return;
}
auto &data = m_RawRequest.Get(fd);
char buf[m_ServerConfig->BufferSize];
bool isFinished = false;
for (;;) {
memset(buf, 0, sizeof(buf));
ssize_t nBytes = recv(fd, buf, m_ServerConfig->BufferSize, 0);
if (nBytes < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (isFinished)
CreateRequest(fd);
break;
} else {
m_RawRequest.Remove(fd);
SocketUtils::Close(fd);
break;
}
} else if (nBytes == 0) {
m_RawRequest.Remove(fd);
SocketUtils::Close(fd);
} else {
if (m_ServerConfig->MaxBufferSize != -1 &&
data.CurrentBytes + nBytes > m_ServerConfig->MaxBufferSize) {
data.Data.clear();
data.Data << "GET @431 HTTP/1.1";
CreateRequest(fd);
break;
}
data.Data << buf;
data.CurrentBytes += nBytes;
if (nBytes < m_ServerConfig->BufferSize - 1) {
isFinished = true;
continue;
}
}
}
}
void Server::CreateRequest(int sockID) {
Accept &meAccept = m_RawRequest.Get(sockID);
auto request = CreateRef<Request>();
request->ID = meAccept.SockId;
request->Body = meAccept.Data.str();
m_RawRequest.Remove(sockID);
m_RequestHandler->AddRequest(request);
}
} // namespace VWeb

20
Source/Session.cpp Normal file
View file

@ -0,0 +1,20 @@
#include <VWeb.h>
namespace VWeb {
bool Session::IsValid() {
auto call = std::chrono::system_clock::now();
auto time =
std::chrono::duration_cast<std::chrono::seconds>(call - m_LastCall)
.count();
return time < TTLSeconds;
}
void Session::Update() { m_LastCall = std::chrono::system_clock::now(); }
void Session::Remove(const std::string &key) {
if (m_Data.contains(key))
m_Data.erase(key);
}
bool Session::Has(const std::string &key) { return m_Data.contains(key); }
} // namespace VWeb

102
Source/SocketManager.cpp Normal file
View file

@ -0,0 +1,102 @@
#include <VWeb.h>
namespace VWeb {
#pragma region VWebSocketUtils
bool SocketUtils::MakeAsync(int socketId) {
int flags = fcntl(socketId, F_GETFL, 0);
if (flags == -1)
return false;
if (fcntl(socketId, F_SETFL, flags | O_NONBLOCK) == -1)
return false;
return true;
}
bool SocketUtils::Add(int socketId) { return MakeAsync(socketId); }
bool SocketUtils::Close(int socketId) { return close(socketId) >= 0; }
bool SocketUtils::WriteDone(int socketId) {
return shutdown(socketId, SHUT_WR) != -1;
}
WriteState SocketUtils::Write(VWeb::SendData &sendData) {
const char *buf = sendData.Content.c_str();
ssize_t n;
while (sendData.Size > 0) {
n = send(sendData.SocketID, buf + sendData.Offset, sendData.Size, 0);
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return WriteState::EPOLL;
fprintf(stderr, "[SocketUtils::Write] >> Im Errored\n");
return WriteState::ERRORED;
}
sendData.Offset += n;
sendData.Size -= n;
}
return WriteState::OK;
}
#pragma endregion VWebSocketUtils
#pragma region VWebSocketManager
SocketManager::SocketManager(const Ref<VWeb::ServerConfig> &serverConfig)
: m_ServerConfig(serverConfig) {}
SocketManager::~SocketManager() { close(m_SocketID); }
Accept SocketManager::Handle() {
Accept requestReturn{};
int client =
accept(m_SocketID, (sockaddr *)&m_Address, (socklen_t *)&m_AddressLength);
if (client < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
requestReturn.ReturnValue = EPollReturns::BREAK;
else
requestReturn.ReturnValue = EPollReturns::FAILURE;
} else {
requestReturn.SockId = client;
requestReturn.ReturnValue = EPollReturns::FAILURE;
if (SocketUtils::Add(client) &&
m_ServerConfig->EPoll->Dispatch(client, EPOLLIN | EPOLLET)) {
requestReturn.ReturnValue = EPollReturns::OK;
} else {
SocketUtils::Close(client);
}
}
return requestReturn;
}
bool SocketManager::SetSendListen(int socketID) {
return m_ServerConfig->EPoll->UpdateEvents(socketID, EPOLLOUT | EPOLLET);
}
bool SocketManager::SetReadListen(int socketID) {
return m_ServerConfig->EPoll->UpdateEvents(socketID, EPOLLIN | EPOLLET);
}
void SocketManager::Init() {
if ((m_SocketID = socket(AF_INET, SOCK_STREAM, 0)) == 0)
return Errored("Cannot Create Socket");
auto trues = 1;
struct timeval tv {
1, 0
};
setsockopt(m_SocketID, SOL_SOCKET, SO_REUSEADDR, &trues, sizeof(int));
setsockopt(m_SocketID, SOL_SOCKET, SO_REUSEPORT, &trues, sizeof(int));
setsockopt(m_SocketID, SOL_SOCKET, SOCK_CLOEXEC, &trues, sizeof(int));
setsockopt(m_SocketID, SOL_SOCKET, SOCK_NONBLOCK, &trues, sizeof(int));
setsockopt(m_SocketID, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv);
m_Address.sin_family = AF_INET;
m_Address.sin_addr.s_addr = htonl(INADDR_ANY);
m_Address.sin_port = htons(m_ServerConfig->Port);
if (bind(m_SocketID, (struct sockaddr *)&m_Address, m_AddressLength) < 0)
return Errored("Cannot Bind Socket");
SocketUtils::MakeAsync(m_SocketID);
if (listen(m_SocketID, SOMAXCONN) < 0)
return Errored("Socket Failed to Listen");
if (!m_ServerConfig->EPoll->Dispatch(m_SocketID,EPOLLIN | EPOLLET | EPOLLOUT))
return Errored("Cannot Add Event");
}
void SocketManager::Errored(const std::string &data) {
if (m_SocketID > 0)
close(m_SocketID);
fprintf(stderr, "%s\n", data.c_str());
m_IsErrored = true;
}
#pragma endregion VWebSocketManager
} // namespace VWeb

86
Source/StringUtils.cpp Normal file
View file

@ -0,0 +1,86 @@
#include "StringUtils.h"
#include <algorithm>
#include <sstream>
namespace VWeb {
static void StringLeftTrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}
static void StringRightTrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
[](unsigned char ch) { return !std::isspace(ch); })
.base(),
s.end());
}
std::vector<std::string> String::Split(const std::string &s,
const std::string &delimiter) {
size_t pos_start = 0, pos_end, delim_len = delimiter.length();
std::string token;
std::vector<std::string> res;
while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) {
token = s.substr(pos_start, pos_end - pos_start);
pos_start = pos_end + delim_len;
res.push_back(token);
}
res.push_back(s.substr(pos_start));
return res;
}
int String::ToNumber(std::string &string, int def) {
char *pEnd;
long val = std::strtol(string.c_str(), &pEnd, 10);
if (pEnd == string)
return def;
return (int)val;
}
std::string String::Join(const std::vector<std::string> &items,
const std::string &delimiter) {
std::stringstream string;
for (size_t i = 0; i < items.size(); ++i) {
if (i != 0)
string << delimiter;
string << items[i];
}
return string.str();
}
void String::Trim(std::string &value) {
StringLeftTrim(value);
StringRightTrim(value);
}
std::string String::TrimCopy(const std::string &value) {
auto val = value;
Trim(val);
return val;
}
void String::ToLowerCase(std::string &value) {
std::transform(value.begin(), value.end(), value.begin(),
[](unsigned char c) { return std::tolower(c); });
}
std::string String::UrlDecode(const std::string &val) {
std::string ret;
char ch;
size_t i;
unsigned int ii;
for (i = 0; i < val.length(); i++) {
if (int(val[i]) == 37) {
char *pEnd;
ii = std::strtoul(val.substr(i + 1, 2).c_str(), &pEnd, 16);
ch = static_cast<char>(ii);
ret += ch;
i = i + 2;
} else
ret += val[i];
}
return (ret);
}
} // namespace VWeb

18
Source/StringUtils.h Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include <vector>
#include <string>
namespace VWeb {
class String {
public:
static std::vector<std::string> Split(const std::string& s,
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 void ToLowerCase(std::string& value);
static void Trim(std::string& value);
static std::string TrimCopy(const std::string& value);
static std::string UrlDecode(const std::string& value);
};
}

43
Source/ThreadPool.cpp Normal file
View file

@ -0,0 +1,43 @@
#include <VWeb.h>
#include <thread>
namespace VWeb {
ThreadPool::ThreadPool(const std::string &name) : m_Name(name) {}
void ThreadPool::Create() {
if (m_IsCreated)
return;
m_IsDone = false;
m_IsCreated = true;
m_Queue.Open();
for (int i = 0; i < m_ThreadCount; ++i) {
m_Threads.push_back(std::thread(&ThreadPool::Execute, this));
};
printf("[ThreadPool] >> Created %d Threads for Pool \"%s\"\n", m_ThreadCount, m_Name.c_str());
}
void ThreadPool::Stop() {
m_IsDone = true;
m_Queue.Flush();
for (int i = 0; i < m_ThreadCount; ++i)
m_Threads[i].join();
}
void ThreadPool::Dispatch(const Ref<WorkerJob> &job) { m_Queue.Push(job); }
void ThreadPool::SetThreadCount(int count) {
if (m_IsCreated)
return;
m_ThreadCount =
count == -1 ? (int)std::thread::hardware_concurrency() : count;
}
void ThreadPool::Execute() {
if (!m_IsCreated)
return;
while (auto queueItem = m_Queue.WaitAndPop()) {
if (!queueItem.has_value() || m_Queue.IsClosed())
continue;
auto &item = queueItem.value();
if (item == nullptr)
continue;
item->Execute();
}
}
} // namespace VWeb

573
VWeb.h Normal file
View file

@ -0,0 +1,573 @@
#pragma once
#include <cassert>
#include <condition_variable>
#include <fcntl.h>
#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 <vector>
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(const 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 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);
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;
};
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]; }
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::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<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);
}
std::string &FirstOf(const std::string &key) {
return Parameters[key].GetFirst();
}
ParameterValue &Header(const std::string &key) { return Parameters[key]; }
std::unordered_map<std::string, ParameterValue> Parameters;
std::unordered_map<std::string, ParameterValue> Headers;
Vector<std::string> URLParameters;
};
class Response {
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;
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; }
protected:
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;
};
class Route {
public:
Route() = default;
virtual ~Route() = default;
Route(std::initializer_list<HttpMethod>);
virtual bool Execute(const Request &request, Response &response);
virtual bool Get(const Request &request, Response &response);
virtual bool Post(const Request &request, Response &response);
virtual bool Put(const Request &request, Response &response);
virtual bool Patch(const Request &request, Response &response);
virtual bool Delete(const Request &request, Response &response);
bool Options(const Request &request, Response &response);
virtual bool Fallback(const Request &request, Response &response);
bool SupportsMethod(const Request &request);
virtual bool IsAllowed(const Request &request);
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);
protected:
std::unordered_map<std::string, Ref<Route>> m_Routes;
};
struct MiddleWare {
int Pos{0};
virtual void PreHandle(Request &){};
virtual void PostHandle(Request &, Response &){};
virtual void Shutdown(Request &, Response &){};
bool operator<(const MiddleWare *rhs) const { return Pos < rhs->Pos; }
};
class MiddleWareHandler {
public:
void HandlePre(Ref<Request> &);
void HandlePost(Ref<Request> &, Ref<Response> &);
void Shutdown(Ref<Request> &, Ref<Response> &);
public:
template <class T> Ref<MiddleWare> Get() { return 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> Ref<MiddleWare> Create() {
return SetById(typeid(T).name(), CreateRef<T>());
}
template <class T> void Remove() { RemoveById(typeid(T).name()); }
protected:
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;
};
#pragma endregion VWEB_ROUTING
#pragma endregion VWEB
} // namespace VWeb

18
_clang-format Normal file
View file

@ -0,0 +1,18 @@
---
BasedOnStyle: LLVM
AlignAfterOpenBracket: Align
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: Never
BinPackArguments: true
BinPackParameters: true
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
FixNamespaceComments: true
IncludeBlocks: Regroup

BIN
dist/libVWeb.debug.a vendored Normal file

Binary file not shown.

BIN
dist/libVWeb.release.a vendored Normal file

Binary file not shown.

15
example/CMakeLists.txt Normal file
View file

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.17)
project(VWeb_Example)
set(CMAKE_CXX_STANDARD 20)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
add_executable(VWeb_Example main.cpp)
include_directories(${CMAKE_SOURCE_DIR}/..)
set(mode release)
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(mode debug)
endif ()
set(vweb_lib ${CMAKE_SOURCE_DIR}/../dist/libVWeb.${mode}.a)
target_link_libraries(VWeb_Example Threads::Threads ${vweb_lib})

Some files were not shown because too many files have changed in this diff Show more