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 */
|
/* Tell the router that we did not handle this request */
|
||||||
req->setYield(true);
|
req->setYield(true);
|
||||||
}
|
}
|
||||||
}));
|
}, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
TemplatedApp &&get(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
|
TemplatedApp &&get(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler, bool upgrade = false) {
|
||||||
httpContext->onHttp("get", pattern, std::move(handler));
|
httpContext->onHttp("get", pattern, std::move(handler), upgrade);
|
||||||
return std::move(*this);
|
return std::move(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-13
@@ -140,15 +140,12 @@ private:
|
|||||||
/* Mark pending request and emit it */
|
/* Mark pending request and emit it */
|
||||||
httpResponseData->state = HttpResponseData<SSL>::HTTP_RESPONSE_PENDING;
|
httpResponseData->state = HttpResponseData<SSL>::HTTP_RESPONSE_PENDING;
|
||||||
|
|
||||||
/* Route the method and URL in two passes */
|
/* Route the method and URL */
|
||||||
typename HttpContextData<SSL>::RouterData routerData = {(HttpResponse<SSL> *) s, httpRequest};
|
httpContextData->router.getUserData() = {(HttpResponse<SSL> *) s, httpRequest};
|
||||||
if (!httpContextData->router.route(httpRequest->getMethod(), httpRequest->getUrl(), routerData)) {
|
if (!httpContextData->router.route(httpRequest->getMethod(), httpRequest->getUrl())) {
|
||||||
/* If first pass failed, we try and match by "any" method */
|
/* We have to force close this socket as we have no handler for it */
|
||||||
if (!httpContextData->router.route("*", httpRequest->getUrl(), routerData)) {
|
us_socket_close(SSL, (us_socket_t *) s);
|
||||||
/* If second pass fail, we have to force close this socket as we have no handler for it */
|
return nullptr;
|
||||||
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 */
|
/* 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 */
|
/* 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<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->setYield(false);
|
||||||
user.httpRequest->setParameters(params);
|
user.httpRequest->setParameters(r->getParameters());
|
||||||
handler(user.httpResponse, user.httpRequest);
|
handler(user.httpResponse, user.httpRequest);
|
||||||
|
|
||||||
/* If any handler yielded, the router will keep looking for a suitable handler. */
|
/* If any handler yielded, the router will keep looking for a suitable handler. */
|
||||||
@@ -359,7 +365,7 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
}, method == "*" ? httpContextData->router.LOW_PRIORITY : (upgrade ? httpContextData->router.HIGH_PRIORITY : httpContextData->router.MEDIUM_PRIORITY));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Listen to port using this HttpContext */
|
/* Listen to port using this HttpContext */
|
||||||
|
|||||||
+90
-132
@@ -18,8 +18,6 @@
|
|||||||
#ifndef UWS_HTTPROUTER_HPP
|
#ifndef UWS_HTTPROUTER_HPP
|
||||||
#define 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 */
|
/* 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 */
|
/* This module is not fully optimized yet, waiting for more features before doing so */
|
||||||
|
|
||||||
@@ -30,15 +28,55 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "f2/function2.hpp"
|
#include "f2/function2.hpp"
|
||||||
|
|
||||||
namespace uWS {
|
namespace uWS {
|
||||||
|
|
||||||
template <typename USERDATA>
|
template <class USERDATA>
|
||||||
struct HttpRouter {
|
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;
|
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 */
|
/* Basically a pre-allocated stack */
|
||||||
struct RouteParameters {
|
struct RouteParameters {
|
||||||
@@ -62,16 +100,6 @@ private:
|
|||||||
}
|
}
|
||||||
} routeParameters;
|
} 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 currentUrl;
|
||||||
std::string_view urlSegmentVector[MAX_URL_SEGMENTS];
|
std::string_view urlSegmentVector[MAX_URL_SEGMENTS];
|
||||||
int urlSegmentTop;
|
int urlSegmentTop;
|
||||||
@@ -119,165 +147,95 @@ private:
|
|||||||
/* If we have no more URL and not on first round, return where we may stand */
|
/* If we have no more URL and not on first round, return where we may stand */
|
||||||
if (urlSegment && !getUrlSegment(urlSegment).length()) {
|
if (urlSegment && !getUrlSegment(urlSegment).length()) {
|
||||||
/* We have reached accross the entire URL with no stoppage, execute */
|
/* We have reached accross the entire URL with no stoppage, execute */
|
||||||
int handlerIndex = parent->handler;
|
for (int handler : parent->handlers) {
|
||||||
if (handlerIndex) {
|
if (handlers[handler & HANDLER_MASK](this)) {
|
||||||
return handlers[handlerIndex](userData, {routeParameters.paramsTop, routeParameters.params});
|
return true;
|
||||||
} else {
|
}
|
||||||
/* Unhandled */
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
/* 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] == '*') {
|
if (p->name.length() && p->name[0] == '*') {
|
||||||
/* Wildcard match (can be seen as a shortcut) */
|
/* Wildcard match (can be seen as a shortcut) */
|
||||||
int handlerIndex = p->handler;
|
for (int handler : p->handlers) {
|
||||||
if (handlerIndex) {
|
if (handlers[handler & HANDLER_MASK](this)) {
|
||||||
int handler = handlers[handlerIndex](userData, {routeParameters.paramsTop, routeParameters.params});
|
return true;
|
||||||
if (handler) {
|
|
||||||
return handler;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
/* Unhandled */
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} else if (p->name.length() && p->name[0] == ':' && getUrlSegment(urlSegment).length()) {
|
} else if (p->name.length() && p->name[0] == ':' && getUrlSegment(urlSegment).length()) {
|
||||||
/* Parameter match */
|
/* Parameter match */
|
||||||
routeParameters.push(getUrlSegment(urlSegment));
|
routeParameters.push(getUrlSegment(urlSegment));
|
||||||
int handler = executeHandlers(p, urlSegment + 1, userData);
|
if (executeHandlers(p.get(), urlSegment + 1, userData)) {
|
||||||
if (handler) {
|
return true;
|
||||||
return handler;
|
|
||||||
} else {
|
|
||||||
// unwind parameter stack
|
|
||||||
routeParameters.pop();
|
|
||||||
}
|
}
|
||||||
|
routeParameters.pop();
|
||||||
} else if (p->name == getUrlSegment(urlSegment)) {
|
} else if (p->name == getUrlSegment(urlSegment)) {
|
||||||
/* Static match */
|
/* Static match */
|
||||||
int handler = executeHandlers(p, urlSegment + 1, userData);
|
if (executeHandlers(p.get(), urlSegment + 1, userData)) {
|
||||||
if (handler) {
|
return true;
|
||||||
return handler;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
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:
|
public:
|
||||||
HttpRouter() {
|
HttpRouter() {
|
||||||
/* We want to use 0 as "no handler" */
|
int p = 0;
|
||||||
handlers.resize(1);
|
for (std::string &method : methods) {
|
||||||
|
priority[method] = p++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~HttpRouter() {
|
void print() {
|
||||||
// todo: delete all Nodes or use unique_ptr
|
printNode(&root, 0);
|
||||||
freeNode(&tree);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For debugging you may want to print this */
|
std::pair<int, std::string_view *> getParameters() {
|
||||||
void printTree() {
|
return {routeParameters.paramsTop, routeParameters.params};
|
||||||
printNode(&tree, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Register a route to be routed */
|
USERDATA &getUserData() {
|
||||||
HttpRouter *add(std::string method, std::string_view pattern, fu2::unique_function<bool(USERDATA &, std::pair<int, std::string_view *>)> &&handler) {
|
return userData;
|
||||||
/* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Routes by method and url until handler found and said handler consumes the request by returning true.
|
/* Fast path */
|
||||||
* If a handler returns false, we keep searching for another match. If we cannot find a handler that
|
bool route(std::string_view method, std::string_view url) {
|
||||||
* 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) {
|
|
||||||
/* Reset url parsing cache */
|
/* Reset url parsing cache */
|
||||||
setUrl(url);
|
setUrl(url);
|
||||||
routeParameters.reset();
|
routeParameters.reset();
|
||||||
|
|
||||||
/* Begin by finding the method node */
|
/* Begin by finding the method node */
|
||||||
for (auto &p : tree.children) {
|
for (auto &p : root.children) {
|
||||||
if (p->name == method) {
|
if (p->name == method) {
|
||||||
/* Then route the url */
|
/* Then route the url */
|
||||||
return executeHandlers(p, 0, userData);
|
return executeHandlers(p.get(), 0, userData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We did not find any handler for this method.
|
/* We did not find any handler for this method and url */
|
||||||
* You may want to re-route with "*" as method. */
|
|
||||||
return false;
|
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