VWeb/Source/RequestHandler.cpp
VersusTuneZ 6aac1c31c7
🐛 fix that request.Body contains not full http request after parsing
This makes possible to parse json or other stuff out of it instead of
seeing the full message that is not needed anyway :)
2024-10-14 19:43:30 +02:00

174 lines
5.6 KiB
C++

#include "Includes/VWeb.h"
#include "StringUtils.h"
#include <cstdio>
#include <exception>
#include <sys/types.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);
String::ToLowerCase(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;
}
void ParseParameterString(Request &req, const std::string &toParse) {
auto data = String::Split(toParse, "&");
for (auto &item : data) {
auto d = String::Split(item, "=", 1);
req.Parameter(d[0]).Add(d.size() > 1 ? d[1] : "true");
}
}
std::string GetPostBody(const std::string &originalBody) {
auto body = String::Split(originalBody, "\r\n\r\n", 1);
if (body.size() > 1 && !body[body.size() - 1].empty())
return String::TrimCopy(String::UrlDecode(body[body.size() - 1]));
return {};
}
void ParseParameters(Request &request, RequestHandler &requestHandler) {
auto &uri = request.URI;
size_t hasURLParameters = uri.find('?');
if (hasURLParameters != std::string::npos) {
ParseParameterString(request, uri.substr(hasURLParameters + 1));
request.URI = uri.substr(0, hasURLParameters);
}
request.Body = GetPostBody(request.Body);
if (request.Method == HttpMethod::HEAD || request.Method == HttpMethod::GET ||
request.Method == HttpMethod::OPTIONS)
return;
// POST :D
auto &contentType = request.Header("content-type");
if (contentType.Size() == 0)
return;
auto &key = contentType.GetFirst();
if (key == "application/x-www-form-urlencoded") {
ParseParameterString(request, request.Body);
} else {
if (requestHandler.HasBodyParser(key)) {
auto &func = requestHandler.GetBodyParser(key);
func(request, request.Body);
}
}
}
std::string faultyRequest = R"_(
HTTP/1.1 500 Bad Gateway\r
Content-Type: text/html\r
Content-Length: 0\r
ETag: "66369d26-1f1"\r\n
)_";
struct RequestJob : public WorkerJob {
Ref<Request> MRequest{};
Ref<Router> MRouter{};
Ref<MiddleWareHandler> MMiddleWareHandler{};
RequestHandler *MRequestHandler{nullptr};
void Execute() override {
try {
if (!ParseRequest(MRequest)) {
fprintf(stderr, "[Request] >> Request failed to parse\n");
SocketUtils::Close(MRequest->ID);
return;
}
ParseParameters(*MRequest, *MRequestHandler);
MRequest->CookieData = CreateRef<Cookies>();
MRequest->SessionData = CreateRef<Session>();
Ref<Response> response;
auto preValue = MMiddleWareHandler->HandlePre(MRequest);
if (preValue.has_value()) {
response = preValue.value();
response->SessionData = MRequest->SessionData;
response->CookieData = MRequest->CookieData;
} else {
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);
} catch (const std::bad_alloc &e) {
// We are out of memory... try to clean up some request...
SocketUtils::Close(MRequest->ID);
MRequestHandler->CloseRequest(MRequest->ID);
} catch (const std::exception &e) {
fprintf(stderr,
"[VWEB] >> Failed to handle Requests... Error thrown\n%s\n",
e.what());
MRequestHandler->AddSendResponse(
{0, (ssize_t)faultyRequest.size(), MRequest->ID, faultyRequest});
}
}
};
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)) {
CloseRequest(id);
}
}
void RequestHandler::CloseRequest(const int id) const {
SocketUtils::Close(id);
m_Server->m_OutRequest.Remove(id);
}
} // namespace VWeb