A little bit of clean-ups HTTP wise

This commit is contained in:
Alex Hultman
2018-07-02 21:07:15 +02:00
parent b265e43ec9
commit 06381565d7
2 changed files with 78 additions and 132 deletions
+59 -119
View File
@@ -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
View File
@@ -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