First Running Version
This commit is contained in:
commit
792abbee93
21 changed files with 1632 additions and 0 deletions
107
Source/Cookie.cpp
Normal file
107
Source/Cookie.cpp
Normal 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 ¶m : 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
35
Source/EPollManager.cpp
Normal 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
33
Source/MiddleWare.cpp
Normal 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
99
Source/RequestHandler.cpp
Normal 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
112
Source/Response.cpp
Normal 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
83
Source/Route.cpp
Normal 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
97
Source/Router.cpp
Normal 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
135
Source/Server.cpp
Normal 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
20
Source/Session.cpp
Normal 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
102
Source/SocketManager.cpp
Normal 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
86
Source/StringUtils.cpp
Normal 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
18
Source/StringUtils.h
Normal 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
43
Source/ThreadPool.cpp
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue