First stab at new router

This commit is contained in:
Alex Hultman
2019-12-26 00:32:02 +01:00
parent 5ab190f430
commit d3b43e3fa1
3 changed files with 112 additions and 148 deletions
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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));
}
};
}