Make Router more sexy
This commit is contained in:
parent
5bb68a7d02
commit
d547bb9f95
9 changed files with 144 additions and 173 deletions
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#include <limits>
|
||||
|
||||
namespace VWeb {
|
||||
enum class HttpStatusCode : int {
|
||||
|
@ -63,14 +64,25 @@ enum class HttpStatusCode : int {
|
|||
NotExtended = 510,
|
||||
NetworkAuthenticationRequired = 511
|
||||
};
|
||||
enum class HttpMethod {
|
||||
GET = 0,
|
||||
HEAD,
|
||||
OPTIONS,
|
||||
POST,
|
||||
PUT,
|
||||
PATCH,
|
||||
DELETE,
|
||||
FALLBACK
|
||||
|
||||
enum class HttpMethod : uint32_t {
|
||||
GET = 1 << 0,
|
||||
HEAD = 1 << 1,
|
||||
OPTIONS = 1 << 2,
|
||||
POST = 1 << 3,
|
||||
PUT = 1 << 4,
|
||||
PATCH = 1 << 5,
|
||||
DELETE = 1 << 6,
|
||||
FALLBACK = 1 << 7,
|
||||
ALL = std::numeric_limits<uint32_t>::max()
|
||||
};
|
||||
|
||||
inline uint32_t operator|(HttpMethod lhs, HttpMethod rhs) {
|
||||
return static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs);
|
||||
}
|
||||
|
||||
inline bool operator&(const uint32_t lhs, HttpMethod rhs) {
|
||||
return lhs & static_cast<uint32_t>(rhs);
|
||||
}
|
||||
|
||||
} // namespace VWeb
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
#include "Cookie.h"
|
||||
#include "Http.h"
|
||||
#include "ParameterValue.h"
|
||||
#include "Session.h"
|
||||
#include "Types.h"
|
||||
#include "ParameterValue.h"
|
||||
|
||||
namespace VWeb {
|
||||
class Response {
|
||||
|
@ -22,7 +22,8 @@ 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 std::vector<std::string> &values);
|
||||
void AddHeaders(const std::string &key,
|
||||
const std::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);
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "Http.h"
|
||||
#include <initializer_list>
|
||||
#include "Request.h"
|
||||
#include "Response.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
namespace VWeb {
|
||||
|
@ -12,23 +13,20 @@ class Route {
|
|||
public:
|
||||
Route() = default;
|
||||
virtual ~Route() = default;
|
||||
Route(std::initializer_list<HttpMethod>);
|
||||
virtual bool Execute(Request &request, Response &response);
|
||||
virtual bool Get(Request &request, Response &response);
|
||||
virtual bool Post(Request &request, Response &response);
|
||||
virtual bool Put(Request &request, Response &response);
|
||||
virtual bool Patch(Request &request, Response &response);
|
||||
virtual bool Delete(Request &request, Response &response);
|
||||
bool Options(Request &request, Response &response);
|
||||
virtual bool Fallback(Request &request, Response &response);
|
||||
bool SupportsMethod(Request &request);
|
||||
virtual bool IsAllowed(Request &request);
|
||||
|
||||
void AllowMethod(HttpMethod method);
|
||||
void SetAllowedMethods(uint32_t methods);
|
||||
[[nodiscard]] uint32_t GetAllowedMethods() const;
|
||||
|
||||
protected:
|
||||
bool m_AllowAll{true};
|
||||
std::vector<HttpMethod> m_AllowedMethods;
|
||||
uint32_t m_AllowedMethods = 0;
|
||||
friend Router;
|
||||
};
|
||||
}
|
||||
} // namespace VWeb
|
|
@ -7,23 +7,44 @@
|
|||
|
||||
namespace VWeb {
|
||||
typedef std::function<bool(Request &, Response &)> RouteFunction;
|
||||
typedef std::function<std::shared_ptr<Route>()> RouteInstaniateFunction;
|
||||
|
||||
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, std::vector<std::string> &items);
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
void Register(const std::string &endpoint, HttpMethod allowedMethod) {
|
||||
Register<T>(endpoint, static_cast<uint32_t>(allowedMethod));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
Register(const std::string &endpoint,
|
||||
uint32_t allowedMethods = static_cast<uint32_t>(HttpMethod::ALL)) {
|
||||
static_assert(std::is_base_of_v<Route, T>, "must be a Route");
|
||||
allowedMethods |= HttpMethod::HEAD | HttpMethod::OPTIONS;
|
||||
m_Routes[endpoint] = {.AllowedMethods = allowedMethods,
|
||||
.Instaniate = [] { return std::make_shared<T>(); }};
|
||||
}
|
||||
|
||||
void Get(const std::string &path, RouteFunction);
|
||||
void Post(const std::string &path, RouteFunction);
|
||||
void Put(const std::string &path, RouteFunction);
|
||||
void Patch(const std::string &path, RouteFunction);
|
||||
void Delete(const std::string &path, RouteFunction);
|
||||
std::unordered_map<std::string, Ref<Route>> m_Routes;
|
||||
|
||||
private:
|
||||
struct RouteInstance {
|
||||
uint32_t AllowedMethods = HttpMethod::OPTIONS | HttpMethod::HEAD;
|
||||
RouteInstaniateFunction Instaniate;
|
||||
};
|
||||
std::unordered_map<std::string, RouteInstance> m_Routes;
|
||||
std::unordered_map<std::string, RouteFunction> m_FunctionRoutes;
|
||||
};
|
||||
} // namespace VWeb
|
|
@ -21,8 +21,7 @@ public:
|
|||
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);
|
||||
void RemoveRoute(const std::string &path) const;
|
||||
|
||||
Ref<MiddleWareHandler> &Middleware();
|
||||
|
||||
|
|
|
@ -4,84 +4,26 @@
|
|||
|
||||
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(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(Request &request, Response &response) {
|
||||
return true;
|
||||
}
|
||||
bool Route::Post(Request &request, Response &response) {
|
||||
return true;
|
||||
}
|
||||
bool Route::Put(Request &request, Response &response) {
|
||||
return true;
|
||||
}
|
||||
bool Route::Patch(Request &request, Response &response) {
|
||||
return true;
|
||||
}
|
||||
bool Route::Delete(Request &request, Response &response) {
|
||||
return true;
|
||||
}
|
||||
bool Route::Options(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(Request &request, Response &response) {
|
||||
return true;
|
||||
}
|
||||
bool Route::Get(Request &request, Response &response) { return true; }
|
||||
bool Route::Post(Request &request, Response &response) { return true; }
|
||||
bool Route::Put(Request &request, Response &response) { return true; }
|
||||
bool Route::Patch(Request &request, Response &response) { return true; }
|
||||
bool Route::Delete(Request &request, Response &response) { return true; }
|
||||
bool Route::Fallback(Request &request, Response &response) { return true; }
|
||||
bool Route::IsAllowed(Request &request) { return true; }
|
||||
bool Route::SupportsMethod(Request &request) {
|
||||
return m_AllowAll ||
|
||||
std::find(m_AllowedMethods.begin(), m_AllowedMethods.end(),
|
||||
request.Method) != m_AllowedMethods.end();
|
||||
}
|
||||
void Route::AllowMethod(HttpMethod method) {
|
||||
if (std::find(m_AllowedMethods.begin(), m_AllowedMethods.end(), method) == m_AllowedMethods.end())
|
||||
m_AllowedMethods.push_back(method);
|
||||
void Route::SetAllowedMethods(const uint32_t methods) {
|
||||
m_AllowedMethods = methods;
|
||||
}
|
||||
uint32_t Route::GetAllowedMethods() const { return m_AllowedMethods; }
|
||||
} // namespace VWeb
|
||||
|
|
|
@ -6,6 +6,15 @@
|
|||
|
||||
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
|
||||
|
||||
template <typename E> constexpr auto to_underlying(E e) noexcept {
|
||||
return static_cast<std::underlying_type_t<E>>(e);
|
||||
}
|
||||
|
@ -21,40 +30,7 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class InstanceHandleRoute : public Route {
|
||||
public:
|
||||
bool Get(Request &request, Response &response) override {
|
||||
return GetFunc && GetFunc(request, response);
|
||||
}
|
||||
bool Post(Request &request, Response &response) override {
|
||||
return PostFunc && PostFunc(request, response);
|
||||
}
|
||||
bool Put(Request &request, Response &response) override {
|
||||
return PutFunc && PutFunc(request, response);
|
||||
}
|
||||
bool Patch(Request &request, Response &response) override {
|
||||
return PatchFunc && PatchFunc(request, response);
|
||||
}
|
||||
bool Delete(Request &request, Response &response) override {
|
||||
return DeleteFunc && DeleteFunc(request, response);
|
||||
}
|
||||
|
||||
protected:
|
||||
RouteFunction GetFunc{nullptr};
|
||||
RouteFunction PostFunc{nullptr};
|
||||
RouteFunction PutFunc{nullptr};
|
||||
RouteFunction PatchFunc{nullptr};
|
||||
RouteFunction DeleteFunc{nullptr};
|
||||
friend Router;
|
||||
};
|
||||
|
||||
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]; }
|
||||
Router::Router() { Register<ErrorRoute>("@"); }
|
||||
|
||||
void Router::DeleteRoute(const std::string &name) {
|
||||
if (m_Routes.contains(name)) {
|
||||
|
@ -62,59 +38,97 @@ void Router::DeleteRoute(const std::string &name) {
|
|||
}
|
||||
}
|
||||
|
||||
static void HandleOptions(Ref<Response> &response, uint32_t allowedMethods) {
|
||||
std::stringstream str{};
|
||||
bool isFirst = true;
|
||||
for (auto &[key, value] : s_HttpMethodToString) {
|
||||
if (allowedMethods & static_cast<uint32_t>(key)) {
|
||||
if (!isFirst)
|
||||
str << ", ";
|
||||
str << value;
|
||||
isFirst = false;
|
||||
}
|
||||
}
|
||||
response->SetHeader("Allow", str.str());
|
||||
}
|
||||
|
||||
Ref<Response> Router::HandleRoute(Ref<Request> &request) {
|
||||
auto response = CreateRef<Response>();
|
||||
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["@"]->Execute(*request, *response);
|
||||
m_Routes["@"].Instaniate()->Execute(*request, *response);
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!route->IsAllowed(*request)) {
|
||||
response->SetStatus(HttpStatusCode::Forbidden);
|
||||
m_Routes["@"]->Execute(*request, *response);
|
||||
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]->Execute(*request, *response)
|
||||
: m_Routes["@"]->Execute(*request, *response);
|
||||
m_Routes.contains(rKey)
|
||||
? m_Routes[rKey].Instaniate()->Execute(*request, *response)
|
||||
: m_Routes["@"].Instaniate()->Execute(*request, *response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
static Ref<Route> Instaniate(const RouteInstaniateFunction &func,
|
||||
uint32_t allowedMethods) {
|
||||
auto ref = func();
|
||||
ref->SetAllowedMethods(allowedMethods);
|
||||
return ref;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const auto &url = request->URI;
|
||||
|
||||
if (url.starts_with("@"))
|
||||
return nullptr;
|
||||
|
||||
auto split = String::Split(url.data(), "/");
|
||||
{
|
||||
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 (m_Routes.contains(nUrl)) {
|
||||
auto &route = m_Routes[nUrl];
|
||||
if (route->SupportsMethod(*request))
|
||||
return route;
|
||||
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 (m_Routes.contains("/")) {
|
||||
auto &route = m_Routes["/"];
|
||||
if (route->SupportsMethod(*request))
|
||||
return route;
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
@ -124,37 +138,24 @@ void Router::AddToArgs(Ref<Request> &request, std::vector<std::string> &items) {
|
|||
items.pop_back();
|
||||
}
|
||||
|
||||
InstanceHandleRoute* GetOrNull(const std::string& path, Router* router) {
|
||||
InstanceHandleRoute* route;
|
||||
if (!router->m_Routes.contains(path)) {
|
||||
router->AddRoute(path, CreateRef<InstanceHandleRoute>());
|
||||
}
|
||||
route = dynamic_cast<InstanceHandleRoute*>(router->m_Routes[path].get());
|
||||
return route;
|
||||
}
|
||||
void Router::Get(const std::string &path, RouteFunction func) {
|
||||
auto* route = GetOrNull(path, this);
|
||||
if (route)
|
||||
route->GetFunc = std::move(func);
|
||||
m_FunctionRoutes[s_HttpMethodToString[HttpMethod::GET] + path] =
|
||||
std::move(func);
|
||||
}
|
||||
void Router::Post(const std::string &path, RouteFunction func) {
|
||||
auto* route = GetOrNull(path, this);
|
||||
if (route)
|
||||
route->PostFunc = std::move(func);
|
||||
m_FunctionRoutes[s_HttpMethodToString[HttpMethod::POST] + path] =
|
||||
std::move(func);
|
||||
}
|
||||
void Router::Put(const std::string &path, RouteFunction func) {
|
||||
auto* route = GetOrNull(path, this);
|
||||
if (route)
|
||||
route->PutFunc = std::move(func);
|
||||
m_FunctionRoutes[s_HttpMethodToString[HttpMethod::PUT] + path] =
|
||||
std::move(func);
|
||||
}
|
||||
void Router::Patch(const std::string &path, RouteFunction func) {
|
||||
auto* route = GetOrNull(path, this);
|
||||
if (route)
|
||||
route->PatchFunc = std::move(func);
|
||||
m_FunctionRoutes[s_HttpMethodToString[HttpMethod::PATCH] + path] =
|
||||
std::move(func);
|
||||
}
|
||||
void Router::Delete(const std::string &path, RouteFunction func) {
|
||||
auto* route = GetOrNull(path, this);
|
||||
if (route)
|
||||
route->DeleteFunc = std::move(func);
|
||||
m_FunctionRoutes[s_HttpMethodToString[HttpMethod::DELETE] + path] =
|
||||
std::move(func);
|
||||
}
|
||||
} // namespace VWeb
|
||||
|
|
|
@ -29,10 +29,7 @@ void Server::Start() {
|
|||
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) {
|
||||
void Server::RemoveRoute(const std::string &path) const {
|
||||
m_Router->DeleteRoute(path);
|
||||
}
|
||||
void Server::Execute() {
|
||||
|
|
|
@ -25,7 +25,7 @@ int main() {
|
|||
auto& router = server.GetRouter();
|
||||
// For debugging and profiling more than 1 thread can be hard.
|
||||
server.GetServerConfig()->WorkerThreads = 1;
|
||||
router->Get("/test", [](const Request&, Response& response) {
|
||||
router->Get("/test", [](Request&, Response& response) {
|
||||
response << "NICE";
|
||||
return true;
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue