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

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