A little bit of clean-ups HTTP wise
This commit is contained in:
+59
-119
@@ -1,45 +1,37 @@
|
|||||||
#ifndef HTTPPARSER_H
|
#ifndef HTTPPARSER_H
|
||||||
#define HTTPPARSER_H
|
#define HTTPPARSER_H
|
||||||
|
|
||||||
// this whole header needs fixing and testing as a separate module, fuzzing testing
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
struct HttpRequest {
|
struct HttpRequest {
|
||||||
|
|
||||||
|
const static int MAX_HEADERS = 50;
|
||||||
struct Header {
|
struct Header {
|
||||||
char *key, *value;
|
std::string_view key, value;
|
||||||
unsigned int keyLength, valueLength;
|
} headers[MAX_HEADERS];
|
||||||
|
|
||||||
operator bool() {
|
static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct Header *headers) {
|
||||||
return key;
|
char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#define MAX_HEADERS 50
|
for (unsigned int i = 0; i < MAX_HEADERS; i++) {
|
||||||
Header headers[MAX_HEADERS];
|
for (preliminaryKey = postPaddedBuffer; (*postPaddedBuffer != ':') & (*postPaddedBuffer > 32); *(postPaddedBuffer++) |= 32);
|
||||||
|
if (*postPaddedBuffer == '\r') {
|
||||||
// UNSAFETY NOTE: assumes *end == '\r' (might unref end pointer)
|
if ((postPaddedBuffer != end) & (postPaddedBuffer[1] == '\n') & (i > 0)) {
|
||||||
static char *getHeaders(char *buffer, char *end, struct Header *headers, size_t maxHeaders) {
|
headers->key = std::string_view(nullptr, 0);
|
||||||
for (unsigned int i = 0; i < maxHeaders; i++) {
|
return (postPaddedBuffer + 2) - start;
|
||||||
for (headers->key = buffer; (*buffer != ':') & (*buffer > 32); *(buffer++) |= 32);
|
|
||||||
if (*buffer == '\r') {
|
|
||||||
if ((buffer != end) & (buffer[1] == '\n') & (i > 0)) {
|
|
||||||
headers->key = 0;
|
|
||||||
return buffer + 2;
|
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
headers->keyLength = (unsigned int) (buffer - headers->key);
|
headers->key = std::string_view(preliminaryKey, (size_t) (postPaddedBuffer - preliminaryKey));
|
||||||
for (buffer++; (*buffer == ':' || *buffer < 33) && *buffer != '\r'; buffer++);
|
for (postPaddedBuffer++; (*postPaddedBuffer == ':' || *postPaddedBuffer < 33) && *postPaddedBuffer != '\r'; postPaddedBuffer++);
|
||||||
headers->value = buffer;
|
preliminaryValue = postPaddedBuffer;
|
||||||
buffer = (char *) memchr(buffer, '\r', end - buffer); //for (; *buffer != '\r'; buffer++);
|
postPaddedBuffer = (char *) memchr(postPaddedBuffer, '\r', end - postPaddedBuffer);
|
||||||
if (buffer /*!= end*/ && buffer[1] == '\n') {
|
if (postPaddedBuffer && postPaddedBuffer[1] == '\n') {
|
||||||
headers->valueLength = (unsigned int) (buffer - headers->value);
|
headers->value = std::string_view(preliminaryValue, (size_t) (postPaddedBuffer - preliminaryValue));
|
||||||
buffer += 2;
|
postPaddedBuffer += 2;
|
||||||
headers++;
|
headers++;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -49,121 +41,85 @@ struct HttpRequest {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string_view getHeader(std::string_view header) {
|
||||||
int consumePostPadded(char *data, int length) {
|
for (Header *h = headers; (++h)->key.length(); ) {
|
||||||
char *cursor = data;
|
if (h->key.length() == header.length() && !strncmp(h->key.data(), header.data(), header.length())) {
|
||||||
|
return h->value;
|
||||||
if (cursor = getHeaders(data, data + length, headers, MAX_HEADERS)) {
|
|
||||||
// strip out the initial stuff
|
|
||||||
headers->valueLength = std::max<int>(0, headers->valueLength - 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cursor == nullptr) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cursor - data;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fenceRegion(char *data, int length) {
|
|
||||||
data[length] = '\r';
|
|
||||||
}
|
|
||||||
|
|
||||||
Header getHeader(const char *key, size_t length) {
|
|
||||||
if (headers) {
|
|
||||||
for (Header *h = headers; *++h; ) {
|
|
||||||
if (h->keyLength == length && !strncmp(h->key, key, length)) {
|
|
||||||
return *h;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {nullptr, nullptr, 0, 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string_view getHeader(std::string_view header) {
|
|
||||||
Header h = getHeader(header.data(), header.length());
|
|
||||||
|
|
||||||
if (h.key) {
|
|
||||||
return std::string_view(h.value, h.valueLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::string_view(nullptr, 0);
|
return std::string_view(nullptr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*struct HttpRequest {
|
|
||||||
// std::string_view getUrl()
|
|
||||||
// std::string_view getHeader(std::string_view)
|
|
||||||
};*/
|
|
||||||
|
|
||||||
std::string_view getUrl() {
|
std::string_view getUrl() {
|
||||||
return std::string_view(headers[0].value, headers[0].valueLength);
|
return headers->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class HttpParser {
|
class HttpParser {
|
||||||
public://private:
|
|
||||||
|
private:
|
||||||
std::string fallback;
|
std::string fallback;
|
||||||
int remainingStreamingBytes = 0;
|
int remainingStreamingBytes = 0;
|
||||||
|
|
||||||
const size_t MAX_FALLBACK_SIZE = 1024 * 4;
|
const size_t MAX_FALLBACK_SIZE = 1024 * 4;
|
||||||
|
|
||||||
public:
|
unsigned int toUnsignedInteger(std::string_view str) {
|
||||||
|
int unsignedIntegerValue = 0;
|
||||||
int str2int(const char *str, int len) {
|
for (unsigned char c : str) {
|
||||||
int i;
|
unsignedIntegerValue = unsignedIntegerValue * 10 + (c - '0');
|
||||||
int ret = 0;
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
ret = ret * 10 + (str[i] - '0');
|
|
||||||
}
|
}
|
||||||
return ret;
|
return unsignedIntegerValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// think about better interface for this one. HttpParser::consumePostPadded<limit or not>(data, length, httpData, onHttpRequest)
|
// the only caller of getHeaders
|
||||||
template <int LIMIT_TO_ONE_REQUEST>
|
template <int CONSUME_MINIMALLY>
|
||||||
inline 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 fenceAndConsumePostPadded(char *data, int length, void *user, HttpRequest *req, std::function<void(void *, HttpRequest *)> &requestHandler, std::function<void(void *, std::string_view)> &dataHandler) {
|
||||||
int ret = 0;
|
int consumedTotal = 0;
|
||||||
|
data[length] = '\r';
|
||||||
|
|
||||||
int consumed = 0;
|
for (int consumed; length && (consumed = HttpRequest::getHeaders(data, data + length, req->headers)); ) {
|
||||||
req->fenceRegion(data, length);
|
|
||||||
while (length && (consumed = req->consumePostPadded(data, length))) {
|
|
||||||
data += consumed;
|
data += consumed;
|
||||||
length -= consumed;
|
length -= consumed;
|
||||||
|
consumedTotal += consumed;
|
||||||
|
|
||||||
ret += consumed;
|
req->headers->value = std::string_view(req->headers->value.data(), std::max<int>(0, req->headers->value.length() - 9));
|
||||||
|
|
||||||
requestHandler(user, req);
|
requestHandler(user, req);
|
||||||
if (std::string_view contentLengthString = req->getHeader("content-length"); contentLengthString.length()) {
|
|
||||||
remainingStreamingBytes = str2int(contentLengthString.data(), contentLengthString.length());
|
|
||||||
int emittable = std::min(remainingStreamingBytes, length);
|
|
||||||
dataHandler(user, std::string_view(data, emittable));
|
|
||||||
remainingStreamingBytes -= emittable;
|
|
||||||
|
|
||||||
data += emittable; // denna var buggen?
|
std::string_view contentLengthString = req->getHeader("content-length");
|
||||||
length -= emittable;
|
if (contentLengthString.length()) {
|
||||||
|
remainingStreamingBytes = toUnsignedInteger(contentLengthString);
|
||||||
|
|
||||||
ret += emittable;
|
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 (LIMIT_TO_ONE_REQUEST) {
|
if (CONSUME_MINIMALLY) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
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
|
||||||
inline 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) {
|
inline 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;
|
HttpRequest req;
|
||||||
|
|
||||||
if (remainingStreamingBytes) {
|
if (remainingStreamingBytes) {
|
||||||
// at this point we reset the timeout timer, we are streaming and we got a chunk
|
|
||||||
|
|
||||||
if (remainingStreamingBytes >= length) {
|
if (remainingStreamingBytes >= length) {
|
||||||
dataHandler(user, std::string_view(data, length));
|
dataHandler(user, std::string_view(data, length));
|
||||||
remainingStreamingBytes -= length;
|
remainingStreamingBytes -= length;
|
||||||
// no change to the socket here! we read all data in the buffer, return
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
dataHandler(user, std::string_view(data, remainingStreamingBytes));
|
dataHandler(user, std::string_view(data, remainingStreamingBytes));
|
||||||
@@ -172,8 +128,6 @@ public:
|
|||||||
length -= remainingStreamingBytes;
|
length -= remainingStreamingBytes;
|
||||||
|
|
||||||
remainingStreamingBytes = 0;
|
remainingStreamingBytes = 0;
|
||||||
|
|
||||||
// okay we are done with that, let's parse some more
|
|
||||||
}
|
}
|
||||||
} else if (fallback.length()) {
|
} else if (fallback.length()) {
|
||||||
int had = fallback.length();
|
int had = fallback.length();
|
||||||
@@ -183,26 +137,19 @@ public:
|
|||||||
fallback.reserve(maxCopyDistance + 32); // padding should be same as libus
|
fallback.reserve(maxCopyDistance + 32); // padding should be same as libus
|
||||||
fallback.append(data, maxCopyDistance);
|
fallback.append(data, maxCopyDistance);
|
||||||
|
|
||||||
// helst ska denna inte emitta någon data alls, vi gör det efteråt!
|
int consumed = fenceAndConsumePostPadded<true>(fallback.data(), fallback.length(), user, &req, requestHandler, dataHandler);
|
||||||
if (int consumed = fenceAndConsumePostPadded<true>(fallback.data(), fallback.length(), user, &req, requestHandler, dataHandler); consumed) {
|
if (consumed) {
|
||||||
|
|
||||||
// I guess?
|
|
||||||
fallback.clear();
|
fallback.clear();
|
||||||
|
|
||||||
data += consumed - had;
|
data += consumed - had;
|
||||||
length -= consumed - had;
|
length -= consumed - had;
|
||||||
|
|
||||||
// ska vi inte tömma fallback här?
|
// this is exactly the same as above!
|
||||||
|
|
||||||
|
|
||||||
// exakt samma if-sats som ovan!
|
|
||||||
if (remainingStreamingBytes) {
|
if (remainingStreamingBytes) {
|
||||||
// at this point we reset the timeout timer, we are streaming and we got a chunk
|
|
||||||
|
|
||||||
if (remainingStreamingBytes >= length) {
|
if (remainingStreamingBytes >= length) {
|
||||||
dataHandler(user, std::string_view(data, length));
|
dataHandler(user, std::string_view(data, length));
|
||||||
remainingStreamingBytes -= length;
|
remainingStreamingBytes -= length;
|
||||||
// no change to the socket here! we read all data in the buffer, return
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
dataHandler(user, std::string_view(data, remainingStreamingBytes));
|
dataHandler(user, std::string_view(data, remainingStreamingBytes));
|
||||||
@@ -211,17 +158,13 @@ public:
|
|||||||
length -= remainingStreamingBytes;
|
length -= remainingStreamingBytes;
|
||||||
|
|
||||||
remainingStreamingBytes = 0;
|
remainingStreamingBytes = 0;
|
||||||
|
|
||||||
// okay we are done with that, let's parse some more
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (fallback.length() == MAX_FALLBACK_SIZE) {
|
if (fallback.length() == MAX_FALLBACK_SIZE) {
|
||||||
// here we failed to parse any header in the 4kb we were given!
|
errorHandler(user);
|
||||||
std::cout << "INVALID HTTP! no more chances!" << std::endl;
|
|
||||||
}
|
}
|
||||||
// no change in socket, or a closed socket!
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,12 +178,9 @@ public:
|
|||||||
if (length < MAX_FALLBACK_SIZE) {
|
if (length < MAX_FALLBACK_SIZE) {
|
||||||
fallback.append(data, length);
|
fallback.append(data, length);
|
||||||
} else {
|
} else {
|
||||||
std::cout << "tail is invalid http!" << std::endl;
|
errorHandler(user);
|
||||||
exit(-1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// vore najs om invalid http kunde hamna i samma ställe av koden!
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+19
-13
@@ -23,15 +23,13 @@ void testHttpParserPerformance() {
|
|||||||
|
|
||||||
char *data = (char *) malloc(requestLength * 10);
|
char *data = (char *) malloc(requestLength * 10);
|
||||||
int length = requestLength * 10;
|
int length = requestLength * 10;
|
||||||
//int currentOffset = 0;
|
|
||||||
|
|
||||||
|
|
||||||
int maxChunkSize = 10000;
|
int maxChunkSize = 10000;
|
||||||
char *paddedBuffer = (char *) malloc(maxChunkSize + 32);
|
char *paddedBuffer = (char *) malloc(maxChunkSize + 32);
|
||||||
|
|
||||||
// dela upp dessa 10 i 5 segment
|
// dela upp dessa 10 i 5 segment
|
||||||
HttpParser httpParser;
|
HttpParser httpParser;
|
||||||
int validRequests = 0;
|
int validRequests = 0, numDataEmits = 0, numChunks = 0;
|
||||||
size_t dataBytes = 0;
|
size_t dataBytes = 0;
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
@@ -46,17 +44,18 @@ void testHttpParserPerformance() {
|
|||||||
chunkSize = length - currentOffset;
|
chunkSize = length - currentOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
// kpiera skiten
|
|
||||||
memcpy(paddedBuffer, data + currentOffset, chunkSize);
|
memcpy(paddedBuffer, data + currentOffset, chunkSize);
|
||||||
|
|
||||||
httpParser.consumePostPadded(/*data + currentOffset*/ paddedBuffer, chunkSize, nullptr, [&validRequests](void *user, HttpRequest *req) {
|
httpParser.consumePostPadded(paddedBuffer, chunkSize, nullptr, [&validRequests](void *user, HttpRequest *req) {
|
||||||
|
|
||||||
validRequests++;
|
validRequests++;
|
||||||
std::cout << "validRequests: " << validRequests << std::endl;
|
|
||||||
|
|
||||||
|
if (req->getUrl() != "/hello.htm") {
|
||||||
|
std::cout << "WRONG URL!" << std::endl;
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
}, [&dataBytes](void *, std::string_view data) {
|
}, [&dataBytes, &numDataEmits](void *, std::string_view data) {
|
||||||
//std::cout << "data bytes: " << data.length() << std::endl;
|
numDataEmits++;
|
||||||
dataBytes += data.length();
|
dataBytes += data.length();
|
||||||
}, [](void *) {
|
}, [](void *) {
|
||||||
|
|
||||||
@@ -64,15 +63,22 @@ void testHttpParserPerformance() {
|
|||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
numChunks++;
|
||||||
|
|
||||||
currentOffset += chunkSize;
|
currentOffset += chunkSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::cout << "validRequests: " << validRequests << std::endl;
|
||||||
std::cout << "Data bytes: " << dataBytes << std::endl;
|
std::cout << "Data bytes: " << dataBytes << std::endl;
|
||||||
|
std::cout << "Data emits: " << numDataEmits << std::endl;
|
||||||
|
std::cout << "Chunks parsed: " << numChunks << std::endl;
|
||||||
|
|
||||||
/*auto start = std::chrono::high_resolution_clock::now();
|
validRequests = 0;
|
||||||
|
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
for (int i = 0; i < 10000000; i++) {
|
for (int i = 0; i < 10000000; i++) {
|
||||||
httpParser.consumePostPadded(data, sizeof(data) - 2, nullptr, [&validRequests](void *user, HttpRequest *req) {
|
httpParser.consumePostPadded(request, requestLength, nullptr, [&validRequests](void *user, HttpRequest *req) {
|
||||||
validRequests++;
|
validRequests++;
|
||||||
}, [](void *, std::string_view data) {
|
}, [](void *, std::string_view data) {
|
||||||
|
|
||||||
@@ -80,9 +86,9 @@ void testHttpParserPerformance() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
auto stop = std::chrono::high_resolution_clock::now();*/
|
auto stop = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
//std::cout << "Parsed " << validRequests << " in " << std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count() << "ms" << std::endl;
|
std::cout << "Parsed " << validRequests << " in " << std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count() << "ms" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // TESTS_H
|
#endif // TESTS_H
|
||||||
|
|||||||
Reference in New Issue
Block a user