From de4314539588055f4118312b7185161637da2eaf Mon Sep 17 00:00:00 2001 From: Alex Hultman Date: Thu, 21 Jun 2018 01:05:09 +0200 Subject: [PATCH] Make it header-only, add routing support --- 15.pro | 8 +-- main.cpp | 16 +++-- src/Context.cpp | 0 src/Context.h | 25 +++++++- src/Http.cpp | 0 src/HttpRouter.h | 164 +++++++++++++++++++++++++++++++++++++++++++++++ src/Hub.cpp | 1 - 7 files changed, 201 insertions(+), 13 deletions(-) delete mode 100644 src/Context.cpp delete mode 100644 src/Http.cpp create mode 100644 src/HttpRouter.h delete mode 100644 src/Hub.cpp diff --git a/15.pro b/15.pro index 3200899..f4e5cdc 100644 --- a/15.pro +++ b/15.pro @@ -10,16 +10,14 @@ SOURCES += \ uSockets/src/socket.c \ uSockets/src/eventing/libuv.c \ uSockets/src/ssl.c \ - uSockets/src/loop.c \ - src/Hub.cpp \ - src/Http.cpp \ - src/Context.cpp + uSockets/src/loop.c HEADERS += \ src/Hub.h \ src/Http.h \ src/Context.h \ - src/uWS.h + src/uWS.h \ + src/HttpRouter.h #uSockets/libusockets.h \ #uSockets/internal/eventing/epoll.h \ #uSockets/internal/networking/bsd.h \ diff --git a/main.cpp b/main.cpp index 12ec3fd..15f6b9d 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,7 @@ // much speaks for a header-only or header-mostly implementation now that uSockets is properly isolating its internal headers #include "Context.h" +#include "HttpRouter.h" int main() { @@ -14,13 +15,16 @@ int main() { options.cert_file_name = "/home/alexhultman/uWebSockets/misc/ssl/cert.pem"; options.passphrase = "1234"; - uWS::SSLContext(options).onHttpRequest([buffer](auto *s, HttpRequest *req) { + //uWS::SSLContext c(options); + uWS::Context c; - if (req->getUrl() == "/") { - s->writeStatus(200)->writeHeader("Hello", "World")->end(buffer, 512); - } else { - std::cout << "Got HTTP request at URL: " << req->getUrl() << std::endl; - } + c.route("GET", "/", [buffer](auto *s, HttpRequest *req, auto *args) { + + s->writeStatus(200)->writeHeader("Hello", "World")->end(buffer, 512); + + }).route("GET", "/wrong", [buffer](auto *s, HttpRequest *req, auto *args) { + + std::cout << "Wrong way!" << std::endl; }).listen("localhost", 3000, 0); diff --git a/src/Context.cpp b/src/Context.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/Context.h b/src/Context.h index 18f7535..eba3d83 100644 --- a/src/Context.h +++ b/src/Context.h @@ -12,7 +12,7 @@ #include "Http.h" -//#define SWAP_F [](auto a, auto b){ if constexpr(SSL) return a; else return b; } +#include "HttpRouter.h" namespace uWS { @@ -52,6 +52,15 @@ protected: // client protocols + // we have a router too + struct UserData { + // pass whatever you need as user data + HttpSocket *httpSocket; + HttpRequest *httpRequest; + }; + + HttpRouter r; + public: // the shared constructor @@ -140,6 +149,20 @@ public: return *this; } + ContextBase &route(std::string method, std::string pattern, std::function *, HttpRequest *, std::vector *)> handler) { + // calling this function overrides any other onHttpRequest! + onHttpRequest([this](auto *s, HttpRequest *req) { + UserData user = {s, req}; + r.route("GET", 3, req->getUrl().data(), req->getUrl().length(), &user); + }); + + r.add(method.c_str(), pattern.c_str(), [handler](UserData *user, auto *args) { + handler(user->httpSocket, user->httpRequest, args); + }); + + return *this; + } + // for client and server ContextBase &onWebSocketConnection() { return *this; diff --git a/src/Http.cpp b/src/Http.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/HttpRouter.h b/src/HttpRouter.h new file mode 100644 index 0000000..8541cac --- /dev/null +++ b/src/HttpRouter.h @@ -0,0 +1,164 @@ +#ifndef HTTPROUTER_HPP +#define HTTPROUTER_HPP + +#include +#include +#include +#include +#include +#include + +template +class HttpRouter { +private: + std::vector *)>> handlers; + std::vector params; + + struct Node { + std::string name; + std::map children; + short handler; + }; + + Node *tree = new Node({"GET", {}, -1}); + std::string compiled_tree; + + void add(std::vector route, short handler) { + Node *parent = tree; + for (std::string node : route) { + if (parent->children.find(node) == parent->children.end()) { + parent->children[node] = new Node({node, {}, handler}); + } + parent = parent->children[node]; + } + } + + unsigned short compile_tree(Node *n) { + unsigned short nodeLength = 6 + n->name.length(); + for (auto c : n->children) { + nodeLength += compile_tree(c.second); + } + + unsigned short nodeNameLength = n->name.length(); + + std::string compiledNode; + compiledNode.append((char *) &nodeLength, sizeof(nodeLength)); + compiledNode.append((char *) &nodeNameLength, sizeof(nodeNameLength)); + compiledNode.append((char *) &n->handler, sizeof(n->handler)); + compiledNode.append(n->name.data(), n->name.length()); + + compiled_tree = compiledNode + compiled_tree; + return nodeLength; + } + + inline const char *find_node(const char *parent_node, const char *name, int name_length) { + unsigned short nodeLength = *(unsigned short *) &parent_node[0]; + unsigned short nodeNameLength = *(unsigned short *) &parent_node[2]; + + //std::cout << "Finding node: <" << std::string(name, name_length) << ">" << std::endl; + + const char *stoppp = parent_node + nodeLength; + for (const char *candidate = parent_node + 6 + nodeNameLength; candidate < stoppp; ) { + + unsigned short nodeLength = *(unsigned short *) &candidate[0]; + unsigned short nodeNameLength = *(unsigned short *) &candidate[2]; + + // whildcard, parameter, equal + if (nodeNameLength == 0) { + return candidate; + } else if (candidate[6] == ':') { + // parameter + + // todo: push this pointer on the stack of args! + params.push_back(std::string_view(name, name_length)); + + return candidate; + } else if (nodeNameLength == name_length && !memcmp(candidate + 6, name, name_length)) { + return candidate; + } + + candidate = candidate + nodeLength; + } + + return nullptr; + } + + // returns next slash from start or end + inline const char *getNextSegment(const char *start, const char *end) { + const char *stop = (const char *) memchr(start, '/', end - start); + return stop ? stop : end; + } + + // should take method also! + inline int lookup(const char *url, int length) { + // all urls start with / + url++; + length--; + + const char *treeStart = (char *) compiled_tree.data(); + + const char *stop, *start = url, *end_ptr = url + length; + do { + stop = getNextSegment(start, end_ptr); + + //std::cout << "Matching(" << std::string(start, stop - start) << ")" << std::endl; + + if(nullptr == (treeStart = find_node(treeStart, start, stop - start))) { + return -1; + } + + start = stop + 1; + } while (stop != end_ptr); + + return *(short *) &treeStart[4]; + } + +public: + HttpRouter() { + // maximum 100 parameters + params.reserve(100); + } + + HttpRouter *add(const char *method, const char *pattern, std::function *)> handler) { + + // step over any initial slash + if (pattern[0] == '/') { + pattern++; + } + + std::vector nodes; + //nodes.push_back(method); + + const char *stop, *start = pattern, *end_ptr = pattern + strlen(pattern); + do { + stop = getNextSegment(start, end_ptr); + + //std::cout << "Segment(" << std::string(start, stop - start) << ")" << std::endl; + + nodes.push_back(std::string(start, stop - start)); + + start = stop + 1; + } while (stop != end_ptr); + + + // if pattern starts with / then move 1+ and run inline slash parser + + add(nodes, handlers.size()); + handlers.push_back(handler); + + compile(); + return this; + } + + void compile() { + compiled_tree.clear(); + compile_tree(tree); + } + + void route(const char *method, unsigned int method_length, const char *url, unsigned int url_length, USERDATA userData) { + handlers[lookup(url, url_length)](userData, ¶ms); + params.clear(); + } +}; + +#endif // HTTPROUTER_HPP diff --git a/src/Hub.cpp b/src/Hub.cpp deleted file mode 100644 index 8b13789..0000000 --- a/src/Hub.cpp +++ /dev/null @@ -1 +0,0 @@ -