Move things around some more
This commit is contained in:
@@ -48,6 +48,8 @@ public:
|
||||
|
||||
// todo: inplace initialize the data struct!
|
||||
|
||||
// todo: actually register handlers on the socket context with the behavior of HTTP! (take from HttpApp.h)
|
||||
|
||||
return (HttpContext *) us_create_socket_context(loop, sizeof(HttpContextData<SSL>));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#ifndef HTTPCONTEXTDATA_H
|
||||
#define HTTPCONTEXTDATA_H
|
||||
|
||||
// this means we will depend on HttpRouter and HttpParser here!
|
||||
#include "../http/HttpParser.h"
|
||||
|
||||
#include "../http/HttpRouter.h"
|
||||
// we depend on these
|
||||
#include "HttpParser.h"
|
||||
#include "HttpRouter.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
#ifndef HTTPPARSER_H
|
||||
#define HTTPPARSER_H
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <cstring>
|
||||
|
||||
class HttpRequest {
|
||||
|
||||
friend class HttpParser;
|
||||
|
||||
private:
|
||||
const static int MAX_HEADERS = 50;
|
||||
struct Header {
|
||||
std::string_view key, value;
|
||||
} headers[MAX_HEADERS];
|
||||
int querySeparator;
|
||||
|
||||
public:
|
||||
std::string_view getHeader(std::string_view header) {
|
||||
for (Header *h = headers; (++h)->key.length(); ) {
|
||||
if (h->key.length() == header.length() && !strncmp(h->key.data(), header.data(), header.length())) {
|
||||
return h->value;
|
||||
}
|
||||
}
|
||||
return std::string_view(nullptr, 0);
|
||||
}
|
||||
|
||||
// todo: implement this
|
||||
/*int getHeader(std::string_view header) {
|
||||
return 0;
|
||||
}*/
|
||||
|
||||
std::string_view getUrl() {
|
||||
return std::string_view(headers->value.data(), querySeparator);
|
||||
}
|
||||
|
||||
std::string_view getQuery() {
|
||||
return std::string_view(headers->value.data() + querySeparator, headers->value.length() - querySeparator);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class HttpParser {
|
||||
|
||||
private:
|
||||
std::string fallback;
|
||||
int remainingStreamingBytes = 0;
|
||||
|
||||
const size_t MAX_FALLBACK_SIZE = 1024 * 4;
|
||||
|
||||
static unsigned int toUnsignedInteger(std::string_view str) {
|
||||
int unsignedIntegerValue = 0;
|
||||
for (unsigned char c : str) {
|
||||
unsignedIntegerValue = unsignedIntegerValue * 10 + (c - '0');
|
||||
}
|
||||
return unsignedIntegerValue;
|
||||
}
|
||||
|
||||
static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct HttpRequest::Header *headers) {
|
||||
char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer;
|
||||
|
||||
for (unsigned int i = 0; i < HttpRequest::MAX_HEADERS; i++) {
|
||||
for (preliminaryKey = postPaddedBuffer; (*postPaddedBuffer != ':') & (*postPaddedBuffer > 32); *(postPaddedBuffer++) |= 32);
|
||||
if (*postPaddedBuffer == '\r') {
|
||||
if ((postPaddedBuffer != end) & (postPaddedBuffer[1] == '\n') & (i > 0)) {
|
||||
headers->key = std::string_view(nullptr, 0);
|
||||
return (postPaddedBuffer + 2) - start;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
headers->key = std::string_view(preliminaryKey, (size_t) (postPaddedBuffer - preliminaryKey));
|
||||
for (postPaddedBuffer++; (*postPaddedBuffer == ':' || *postPaddedBuffer < 33) && *postPaddedBuffer != '\r'; postPaddedBuffer++);
|
||||
preliminaryValue = postPaddedBuffer;
|
||||
postPaddedBuffer = (char *) memchr(postPaddedBuffer, '\r', end - postPaddedBuffer);
|
||||
if (postPaddedBuffer && postPaddedBuffer[1] == '\n') {
|
||||
headers->value = std::string_view(preliminaryValue, (size_t) (postPaddedBuffer - preliminaryValue));
|
||||
postPaddedBuffer += 2;
|
||||
headers++;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// the only caller of getHeaders
|
||||
template <int CONSUME_MINIMALLY>
|
||||
int fenceAndConsumePostPadded(char *data, int length, void *user, HttpRequest *req, std::function<void(void *, HttpRequest *)> &requestHandler, std::function<void(void *, std::string_view)> &dataHandler) {
|
||||
int consumedTotal = 0;
|
||||
data[length] = '\r';
|
||||
|
||||
for (int consumed; length && (consumed = getHeaders(data, data + length, req->headers)); ) {
|
||||
data += consumed;
|
||||
length -= consumed;
|
||||
consumedTotal += consumed;
|
||||
|
||||
req->headers->value = std::string_view(req->headers->value.data(), std::max<int>(0, req->headers->value.length() - 9));
|
||||
|
||||
// querySeparator is untested, todo: go through this
|
||||
const char *querySeparatorPtr = (const char *) memchr(req->headers->value.data(), '?', req->headers->value.length());
|
||||
req->querySeparator = (querySeparatorPtr ? querySeparatorPtr : req->headers->value.data() + req->headers->value.length()) - req->headers->value.data();
|
||||
|
||||
requestHandler(user, req);
|
||||
|
||||
std::string_view contentLengthString = req->getHeader("content-length");
|
||||
if (contentLengthString.length()) {
|
||||
remainingStreamingBytes = toUnsignedInteger(contentLengthString);
|
||||
|
||||
if (!CONSUME_MINIMALLY) {
|
||||
int emittable = std::min(remainingStreamingBytes, length);
|
||||
dataHandler(user, std::string_view(data, emittable));
|
||||
remainingStreamingBytes -= emittable;
|
||||
|
||||
data += emittable;
|
||||
length -= emittable;
|
||||
consumedTotal += emittable;
|
||||
}
|
||||
}
|
||||
|
||||
if (CONSUME_MINIMALLY) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return consumedTotal;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// todo: what can we do with the socket inside the handlers? we need to check on return from any handler if we closed or terminated or upgraded the socket
|
||||
void consumePostPadded(char *data, int length, void *user, std::function<void(void *, HttpRequest *)> &&requestHandler, std::function<void(void *, std::string_view)> &&dataHandler, std::function<void(void *)> &&errorHandler) {
|
||||
|
||||
HttpRequest req;
|
||||
|
||||
if (remainingStreamingBytes) {
|
||||
if (remainingStreamingBytes >= length) {
|
||||
dataHandler(user, std::string_view(data, length));
|
||||
remainingStreamingBytes -= length;
|
||||
return;
|
||||
} else {
|
||||
dataHandler(user, std::string_view(data, remainingStreamingBytes));
|
||||
|
||||
data += remainingStreamingBytes;
|
||||
length -= remainingStreamingBytes;
|
||||
|
||||
remainingStreamingBytes = 0;
|
||||
}
|
||||
} else if (fallback.length()) {
|
||||
int had = fallback.length();
|
||||
|
||||
int maxCopyDistance = std::min(MAX_FALLBACK_SIZE - fallback.length(), (size_t) length);
|
||||
|
||||
fallback.reserve(maxCopyDistance + 32); // padding should be same as libus
|
||||
fallback.append(data, maxCopyDistance);
|
||||
|
||||
int consumed = fenceAndConsumePostPadded<true>(fallback.data(), fallback.length(), user, &req, requestHandler, dataHandler);
|
||||
if (consumed) {
|
||||
|
||||
fallback.clear();
|
||||
|
||||
data += consumed - had;
|
||||
length -= consumed - had;
|
||||
|
||||
// this is exactly the same as above!
|
||||
if (remainingStreamingBytes) {
|
||||
if (remainingStreamingBytes >= length) {
|
||||
dataHandler(user, std::string_view(data, length));
|
||||
remainingStreamingBytes -= length;
|
||||
return;
|
||||
} else {
|
||||
dataHandler(user, std::string_view(data, remainingStreamingBytes));
|
||||
|
||||
data += remainingStreamingBytes;
|
||||
length -= remainingStreamingBytes;
|
||||
|
||||
remainingStreamingBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (fallback.length() == MAX_FALLBACK_SIZE) {
|
||||
errorHandler(user);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int consumed = fenceAndConsumePostPadded<false>(data, length, user, &req, requestHandler, dataHandler);
|
||||
|
||||
data += consumed;
|
||||
length -= consumed;
|
||||
|
||||
if (length) {
|
||||
if (length < MAX_FALLBACK_SIZE) {
|
||||
fallback.append(data, length);
|
||||
} else {
|
||||
errorHandler(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // HTTPPARSER_H
|
||||
@@ -0,0 +1,171 @@
|
||||
#ifndef HTTPROUTER_HPP
|
||||
#define HTTPROUTER_HPP
|
||||
|
||||
// this header also needs testing and fixing as a separate module
|
||||
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
|
||||
template <class USERDATA>
|
||||
class HttpRouter {
|
||||
private:
|
||||
std::vector<std::function<void(USERDATA, std::vector<std::string_view> *)>> handlers;
|
||||
std::vector<std::string_view> params;
|
||||
|
||||
struct Node {
|
||||
std::string name;
|
||||
std::map<std::string, Node *> children;
|
||||
short handler;
|
||||
};
|
||||
|
||||
Node *tree = new Node({"GET", {}, -1});
|
||||
std::string compiled_tree;
|
||||
|
||||
void add(std::vector<std::string> 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<void(USERDATA, std::vector<std::string_view> *)> handler) {
|
||||
|
||||
// step over any initial slash
|
||||
if (pattern[0] == '/') {
|
||||
pattern++;
|
||||
}
|
||||
|
||||
std::vector<std::string> 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) {
|
||||
|
||||
int index = lookup(url, url_length);
|
||||
if (index != -1) {
|
||||
handlers[index](userData, ¶ms);
|
||||
}
|
||||
|
||||
params.clear();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // HTTPROUTER_HPP
|
||||
Reference in New Issue
Block a user