First stab at new router
This commit is contained in:
@@ -248,11 +248,11 @@ public:
|
||||
/* Tell the router that we did not handle this request */
|
||||
req->setYield(true);
|
||||
}
|
||||
}));
|
||||
}, true));
|
||||
}
|
||||
|
||||
TemplatedApp &&get(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
|
||||
httpContext->onHttp("get", pattern, std::move(handler));
|
||||
TemplatedApp &&get(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler, bool upgrade = false) {
|
||||
httpContext->onHttp("get", pattern, std::move(handler), upgrade);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
|
||||
+19
-13
@@ -140,15 +140,12 @@ private:
|
||||
/* Mark pending request and emit it */
|
||||
httpResponseData->state = HttpResponseData<SSL>::HTTP_RESPONSE_PENDING;
|
||||
|
||||
/* Route the method and URL in two passes */
|
||||
typename HttpContextData<SSL>::RouterData routerData = {(HttpResponse<SSL> *) s, httpRequest};
|
||||
if (!httpContextData->router.route(httpRequest->getMethod(), httpRequest->getUrl(), routerData)) {
|
||||
/* If first pass failed, we try and match by "any" method */
|
||||
if (!httpContextData->router.route("*", httpRequest->getUrl(), routerData)) {
|
||||
/* If second pass fail, we have to force close this socket as we have no handler for it */
|
||||
us_socket_close(SSL, (us_socket_t *) s);
|
||||
return nullptr;
|
||||
}
|
||||
/* Route the method and URL */
|
||||
httpContextData->router.getUserData() = {(HttpResponse<SSL> *) s, httpRequest};
|
||||
if (!httpContextData->router.route(httpRequest->getMethod(), httpRequest->getUrl())) {
|
||||
/* We have to force close this socket as we have no handler for it */
|
||||
us_socket_close(SSL, (us_socket_t *) s);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* First of all we need to check if this socket was deleted due to upgrade */
|
||||
@@ -346,12 +343,21 @@ public:
|
||||
}
|
||||
|
||||
/* Register an HTTP route handler acording to URL pattern */
|
||||
void onHttp(std::string method, std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
|
||||
void onHttp(std::string method, std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler, bool upgrade = false) {
|
||||
HttpContextData<SSL> *httpContextData = getSocketContextData();
|
||||
|
||||
httpContextData->router.add(method, pattern, [handler = std::move(handler)](typename HttpContextData<SSL>::RouterData &user, std::pair<int, std::string_view *> params) mutable {
|
||||
/* Todo: This is ugly, fix */
|
||||
std::vector<std::string> methods;
|
||||
if (method == "*") {
|
||||
methods = httpContextData->router.methods;
|
||||
} else {
|
||||
methods = {method};
|
||||
}
|
||||
|
||||
httpContextData->router.add(methods, pattern, [handler = std::move(handler)](auto *r) mutable {
|
||||
auto user = r->getUserData();
|
||||
user.httpRequest->setYield(false);
|
||||
user.httpRequest->setParameters(params);
|
||||
user.httpRequest->setParameters(r->getParameters());
|
||||
handler(user.httpResponse, user.httpRequest);
|
||||
|
||||
/* If any handler yielded, the router will keep looking for a suitable handler. */
|
||||
@@ -359,7 +365,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, method == "*" ? httpContextData->router.LOW_PRIORITY : (upgrade ? httpContextData->router.HIGH_PRIORITY : httpContextData->router.MEDIUM_PRIORITY));
|
||||
}
|
||||
|
||||
/* Listen to port using this HttpContext */
|
||||
|
||||
+90
-132
@@ -18,8 +18,6 @@
|
||||
#ifndef UWS_HTTPROUTER_HPP
|
||||
#define UWS_HTTPROUTER_HPP
|
||||
|
||||
// todo: this module also needs a few clean-ups and simplifications
|
||||
|
||||
/* HTTP router is an independent module subject to unit testing and fuzz testing */
|
||||
/* This module is not fully optimized yet, waiting for more features before doing so */
|
||||
|
||||
@@ -30,15 +28,55 @@
|
||||
#include <string_view>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "f2/function2.hpp"
|
||||
|
||||
namespace uWS {
|
||||
|
||||
template <typename USERDATA>
|
||||
template <class USERDATA>
|
||||
struct HttpRouter {
|
||||
private:
|
||||
|
||||
USERDATA userData;
|
||||
|
||||
std::vector<std::string> methods = {"GET", "POST", "HEAD", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"};
|
||||
|
||||
/* 32-bit */
|
||||
const static uint32_t HANDLER_MASK = 0x0fffffff;
|
||||
const static uint32_t HIGH_PRIORITY = 0xd0000000;
|
||||
const static uint32_t MEDIUM_PRIORITY = 0xe0000000;
|
||||
const static uint32_t LOW_PRIORITY = 0xf0000000;
|
||||
|
||||
static const unsigned int MAX_URL_SEGMENTS = 100;
|
||||
|
||||
private:
|
||||
/* Methods and their respective priority */
|
||||
std::map<std::string, int> priority;
|
||||
|
||||
struct Node {
|
||||
std::string name;
|
||||
std::vector<std::unique_ptr<Node>> children;
|
||||
std::vector<uint32_t> handlers;
|
||||
} root = {"rootNode"};
|
||||
|
||||
/* List of handlers */
|
||||
std::vector<fu2::unique_function<bool(HttpRouter *)>> handlers;
|
||||
|
||||
/* Advance from parent to child, adding child if necessary */
|
||||
Node *getNode(Node *parent, std::string child) {
|
||||
for (std::unique_ptr<Node> &node : parent->children) {
|
||||
if (node->name == child) {
|
||||
return node.get();
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert sorted, but keep order if parent is root (we sort methods by priority elsewhere) */
|
||||
std::unique_ptr<Node> newNode(new Node({child}));
|
||||
return parent->children.emplace(std::upper_bound(parent->children.begin(), parent->children.end(), newNode, [parent, this](auto &a, auto &b) {
|
||||
return b->name.length() && (parent != &root) && (b->name < a->name);
|
||||
}), std::move(newNode))->get();
|
||||
}
|
||||
|
||||
/* Basically a pre-allocated stack */
|
||||
struct RouteParameters {
|
||||
@@ -62,16 +100,6 @@ private:
|
||||
}
|
||||
} routeParameters;
|
||||
|
||||
std::vector<fu2::unique_function<bool(USERDATA &, std::pair<int, std::string_view *>)>> handlers;
|
||||
|
||||
HttpRouter(const HttpRouter &other) = delete;
|
||||
|
||||
struct Node {
|
||||
std::string name;
|
||||
std::vector<Node *> children;
|
||||
short handler = 0; // unhandled
|
||||
} tree;
|
||||
|
||||
std::string_view currentUrl;
|
||||
std::string_view urlSegmentVector[MAX_URL_SEGMENTS];
|
||||
int urlSegmentTop;
|
||||
@@ -119,165 +147,95 @@ private:
|
||||
/* If we have no more URL and not on first round, return where we may stand */
|
||||
if (urlSegment && !getUrlSegment(urlSegment).length()) {
|
||||
/* We have reached accross the entire URL with no stoppage, execute */
|
||||
int handlerIndex = parent->handler;
|
||||
if (handlerIndex) {
|
||||
return handlers[handlerIndex](userData, {routeParameters.paramsTop, routeParameters.params});
|
||||
} else {
|
||||
/* Unhandled */
|
||||
return false;
|
||||
for (int handler : parent->handlers) {
|
||||
if (handlers[handler & HANDLER_MASK](this)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/* We reached the end, so go back */
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto *p : parent->children) {
|
||||
for (auto &p : parent->children) {
|
||||
if (p->name.length() && p->name[0] == '*') {
|
||||
/* Wildcard match (can be seen as a shortcut) */
|
||||
int handlerIndex = p->handler;
|
||||
if (handlerIndex) {
|
||||
int handler = handlers[handlerIndex](userData, {routeParameters.paramsTop, routeParameters.params});
|
||||
if (handler) {
|
||||
return handler;
|
||||
for (int handler : p->handlers) {
|
||||
if (handlers[handler & HANDLER_MASK](this)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
/* Unhandled */
|
||||
return false;
|
||||
}
|
||||
} else if (p->name.length() && p->name[0] == ':' && getUrlSegment(urlSegment).length()) {
|
||||
/* Parameter match */
|
||||
routeParameters.push(getUrlSegment(urlSegment));
|
||||
int handler = executeHandlers(p, urlSegment + 1, userData);
|
||||
if (handler) {
|
||||
return handler;
|
||||
} else {
|
||||
// unwind parameter stack
|
||||
routeParameters.pop();
|
||||
if (executeHandlers(p.get(), urlSegment + 1, userData)) {
|
||||
return true;
|
||||
}
|
||||
routeParameters.pop();
|
||||
} else if (p->name == getUrlSegment(urlSegment)) {
|
||||
/* Static match */
|
||||
int handler = executeHandlers(p, urlSegment + 1, userData);
|
||||
if (handler) {
|
||||
return handler;
|
||||
if (executeHandlers(p.get(), urlSegment + 1, userData)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void printNode(Node *node, int indentation) {
|
||||
for (int i = 0; i < indentation; i++) {
|
||||
std::cout << " ";
|
||||
}
|
||||
std::cout << node->name << "(" << node->handler << ")" << std::endl;
|
||||
for (auto *p : node->children) {
|
||||
printNode(p, indentation + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void freeNode(Node *node) {
|
||||
for (auto *p : node->children) {
|
||||
freeNode(p);
|
||||
}
|
||||
if (node != &tree) {
|
||||
delete node;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
HttpRouter() {
|
||||
/* We want to use 0 as "no handler" */
|
||||
handlers.resize(1);
|
||||
int p = 0;
|
||||
for (std::string &method : methods) {
|
||||
priority[method] = p++;
|
||||
}
|
||||
}
|
||||
|
||||
~HttpRouter() {
|
||||
// todo: delete all Nodes or use unique_ptr
|
||||
freeNode(&tree);
|
||||
void print() {
|
||||
printNode(&root, 0);
|
||||
}
|
||||
|
||||
/* For debugging you may want to print this */
|
||||
void printTree() {
|
||||
printNode(&tree, -1);
|
||||
std::pair<int, std::string_view *> getParameters() {
|
||||
return {routeParameters.paramsTop, routeParameters.params};
|
||||
}
|
||||
|
||||
/* Register a route to be routed */
|
||||
HttpRouter *add(std::string method, std::string_view pattern, fu2::unique_function<bool(USERDATA &, std::pair<int, std::string_view *>)> &&handler) {
|
||||
/* Step over any initial slash */
|
||||
if (pattern[0] == '/') {
|
||||
pattern = pattern.substr(1);
|
||||
}
|
||||
|
||||
/* Parse the route as a vector of strings */
|
||||
std::vector<std::string> route;
|
||||
route.push_back(method);
|
||||
|
||||
std::stringstream test;
|
||||
test << pattern;
|
||||
|
||||
/* Empty pattern or / is the default */
|
||||
if (!pattern.length()) {
|
||||
route.push_back("");
|
||||
}
|
||||
|
||||
std::string segment;
|
||||
while(std::getline(test, segment, '/')) {
|
||||
route.push_back(segment);
|
||||
}
|
||||
|
||||
/* Add this handler to the list of handlers */
|
||||
short handlerIndex = handlers.size();
|
||||
handlers.emplace_back(std::move(handler));
|
||||
|
||||
/* Build the routing tree */
|
||||
Node *parent = &tree;
|
||||
for (unsigned int i = 0; i < route.size(); i++) {
|
||||
std::string node = route[i];
|
||||
// do we already have this?
|
||||
Node *found = nullptr;
|
||||
for (auto *child : parent->children) {
|
||||
if (child->name == node) {
|
||||
found = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
if (i == route.size() - 1) {
|
||||
// only ever touch the handler id on the leaf node
|
||||
parent->children.push_back(found = new Node({node, {}, handlerIndex}));
|
||||
} else {
|
||||
parent->children.push_back(found = new Node({node, {}, 0}));
|
||||
}
|
||||
} else if (i == route.size() - 1) {
|
||||
// touch leaf node of existing path
|
||||
found->handler = handlerIndex;
|
||||
}
|
||||
parent = found;
|
||||
}
|
||||
|
||||
return this;
|
||||
USERDATA &getUserData() {
|
||||
return userData;
|
||||
}
|
||||
|
||||
/* Routes by method and url until handler found and said handler consumes the request by returning true.
|
||||
* If a handler returns false, we keep searching for another match. If we cannot find a handler that
|
||||
* a) matches the url and method and b) consume the request, then we fail and return false.
|
||||
* In that case, a second pass where method changed to "*" to denote "any" could be used to
|
||||
* give such routes a chance. If second pass fails, we have an unhandled request and you may
|
||||
* do whatever you want with your connection, such as close it, or respond with a fix message */
|
||||
bool route(std::string_view method, std::string_view url, USERDATA &userData) {
|
||||
/* Fast path */
|
||||
bool route(std::string_view method, std::string_view url) {
|
||||
/* Reset url parsing cache */
|
||||
setUrl(url);
|
||||
routeParameters.reset();
|
||||
|
||||
/* Begin by finding the method node */
|
||||
for (auto &p : tree.children) {
|
||||
for (auto &p : root.children) {
|
||||
if (p->name == method) {
|
||||
/* Then route the url */
|
||||
return executeHandlers(p, 0, userData);
|
||||
return executeHandlers(p.get(), 0, userData);
|
||||
}
|
||||
}
|
||||
|
||||
/* We did not find any handler for this method.
|
||||
* You may want to re-route with "*" as method. */
|
||||
/* We did not find any handler for this method and url */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Adds the corresponding entires in matching tree and handler list */
|
||||
void add(std::vector<std::string> methods, std::string pattern, fu2::unique_function<bool(HttpRouter *)> &&handler, int priority = MEDIUM_PRIORITY) {
|
||||
for (std::string method : methods) {
|
||||
/* Lookup method */
|
||||
Node *node = getNode(&root, method);
|
||||
/* Iterate over all segments */
|
||||
setUrl(pattern);
|
||||
for (int i = 0; getUrlSegment(i).length() || i == 0; i++) {
|
||||
node = getNode(node, std::string(getUrlSegment(i)));
|
||||
}
|
||||
/* Insert handler in order sorted by priority (most significant 1 byte) */
|
||||
node->handlers.insert(std::upper_bound(node->handlers.begin(), node->handlers.end(), priority | handlers.size()), priority | handlers.size());
|
||||
}
|
||||
|
||||
/* Alloate this handler */
|
||||
handlers.emplace_back(std::move(handler));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user