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})

20
example/main.cpp Normal file
View File

@ -0,0 +1,20 @@
#include <iostream>
#include <VWeb.h>
class BigDataRoute : public VWeb::Route {
public:
bool Execute(const VWeb::Request &request, VWeb::Response &response) override {
response << "<pre>\n";
for (int i = 0; i < 100; ++i)
response << "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n";
response << "</pre>";
return true;
}
};
int main() {
VWeb::Server server;
server.AddRoute("/test", VWeb::CreateRef<BigDataRoute>());
server.Start();
server.Join();
return 0;
}