#include "Includes/VWeb.h" #include "StringUtils.h" #include #include namespace VWeb { #define stringify(name) \ { name, std::string(#name).replace(0, 12, "") } static std::unordered_map 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 template constexpr auto to_underlying(E e) noexcept { return static_cast>(e); } class ErrorRoute : public Route { public: bool Execute(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() { Register("@"); } void Router::DeleteRoute(const std::string &name) { if (m_Routes.contains(name)) { m_Routes.erase(name); } } static void HandleOptions(Ref &response, uint32_t allowedMethods) { std::stringstream str{}; bool isFirst = true; for (auto &[key, value] : s_HttpMethodToString) { if (allowedMethods & static_cast(key)) { if (!isFirst) str << ", "; str << value; isFirst = false; } } response->SetHeader("Allow", str.str()); } Ref Router::HandleRoute(Ref &request) { auto response = CreateRef(); auto route = FindRoute(request); response->CookieData = request->CookieData; response->SessionData = request->SessionData; response->Method = request->Method; if (!route) { // Lets check if we can run it through functions routes.. const auto it = m_FunctionRoutes.find( s_HttpMethodToString[request->Method] + request->URI); if (it != m_FunctionRoutes.end()) { it->second(*request, *response); return response; } response->SetStatus(HttpStatusCode::NotFound); m_Routes["@"].Instaniate()->Execute(*request, *response); return response; } if (!route->IsAllowed(*request)) { response->SetStatus(HttpStatusCode::Forbidden); m_Routes["@"].Instaniate()->Execute(*request, *response); return response; } if (request->Method == HttpMethod::OPTIONS) { HandleOptions(response, route->GetAllowedMethods()); return response; } if (!route->Execute(*request, *response)) { std::string rKey = "@" + std::to_string(to_underlying(response->Status)); m_Routes.contains(rKey) ? m_Routes[rKey].Instaniate()->Execute(*request, *response) : m_Routes["@"].Instaniate()->Execute(*request, *response); } return response; } static Ref Instaniate(const RouteInstaniateFunction &func, uint32_t allowedMethods) { auto ref = func(); ref->SetAllowedMethods(allowedMethods); return ref; } Ref Router::FindRoute(Ref &request) { const auto &url = request->URI; if (url.starts_with("@")) return nullptr; { if (const auto it = m_Routes.find(url); it != m_Routes.end() && it->second.AllowedMethods & request->Method) { return Instaniate(it->second.Instaniate, it->second.AllowedMethods); } } auto split = String::Split(url, "/"); if (split.size() > 1) { AddToArgs(request, split); while (split.size() > 1) { std::string nUrl = String::Join(split, "/"); if (auto it = m_Routes.find(url); it != m_Routes.end() && it->second.AllowedMethods & request->Method) { return Instaniate(it->second.Instaniate, it->second.AllowedMethods); } AddToArgs(request, split); } } { if (const auto it = m_Routes.find("/"); it != m_Routes.end() && it->second.AllowedMethods & request->Method) { return Instaniate(it->second.Instaniate, it->second.AllowedMethods); } } return nullptr; } void Router::AddToArgs(Ref &request, std::vector &items) { request->URLParameters.push_back(items[items.size() - 1]); items.pop_back(); } void Router::Get(const std::string &path, RouteFunction func) { m_FunctionRoutes[s_HttpMethodToString[HttpMethod::GET] + path] = std::move(func); } void Router::Post(const std::string &path, RouteFunction func) { m_FunctionRoutes[s_HttpMethodToString[HttpMethod::POST] + path] = std::move(func); } void Router::Put(const std::string &path, RouteFunction func) { m_FunctionRoutes[s_HttpMethodToString[HttpMethod::PUT] + path] = std::move(func); } void Router::Patch(const std::string &path, RouteFunction func) { m_FunctionRoutes[s_HttpMethodToString[HttpMethod::PATCH] + path] = std::move(func); } void Router::Delete(const std::string &path, RouteFunction func) { m_FunctionRoutes[s_HttpMethodToString[HttpMethod::DELETE] + path] = std::move(func); } } // namespace VWeb