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
#define HTTPPARSER_H
// this whole header needs fixing and testing as a separate module, fuzzing testing
#include <string>
#include <functional>
#include <cstring>
struct HttpRequest {
const static int MAX_HEADERS = 50;
struct Header {
char *key, *value;
unsigned int keyLength, valueLength;
std::string_view key, value;
} headers[MAX_HEADERS];
operator bool() {
return key;
}
};
static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct Header *headers) {
char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer;
#define MAX_HEADERS 50
Header headers[MAX_HEADERS];
// UNSAFETY NOTE: assumes *end == '\r' (might unref end pointer)
static char *getHeaders(char *buffer, char *end, struct Header *headers, size_t maxHeaders) {
for (unsigned int i = 0; i < maxHeaders; i++) {
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;
for (unsigned int i = 0; i < 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->keyLength = (unsigned int) (buffer - headers->key);
for (buffer++; (*buffer == ':' || *buffer < 33) && *buffer != '\r'; buffer++);
headers->value = buffer;
buffer = (char *) memchr(buffer, '\r', end - buffer); //for (; *buffer != '\r'; buffer++);
if (buffer /*!= end*/ && buffer[1] == '\n') {
headers->valueLength = (unsigned int) (buffer - headers->value);
buffer += 2;
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;
@@ -49,121 +41,85 @@ struct HttpRequest {
return 0;
}
int consumePostPadded(char *data, int length) {
char *cursor = data;
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;
}
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 {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);
}
/*struct HttpRequest {
// std::string_view getUrl()
// std::string_view getHeader(std::string_view)
};*/
std::string_view getUrl() {
return std::string_view(headers[0].value, headers[0].valueLength);
return headers->value;
}
};
class HttpParser {
public://private:
private:
std::string fallback;
int remainingStreamingBytes = 0;
const size_t MAX_FALLBACK_SIZE = 1024 * 4;
public:
int str2int(const char *str, int len) {
int i;
int ret = 0;
for (i = 0; i < len; i++) {
ret = ret * 10 + (str[i] - '0');
unsigned int toUnsignedInteger(std::string_view str) {
int unsignedIntegerValue = 0;
for (unsigned char c : str) {
unsignedIntegerValue = unsignedIntegerValue * 10 + (c - '0');
}
return ret;
return unsignedIntegerValue;
}
// think about better interface for this one. HttpParser::consumePostPadded<limit or not>(data, length, httpData, onHttpRequest)
template <int LIMIT_TO_ONE_REQUEST>
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 ret = 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';
int consumed = 0;
req->fenceRegion(data, length);
while (length && (consumed = req->consumePostPadded(data, length))) {
for (int consumed; length && (consumed = HttpRequest::getHeaders(data, data + length, req->headers)); ) {
data += 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);
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?
length -= emittable;
std::string_view contentLengthString = req->getHeader("content-length");
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;
}
}
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) {
HttpRequest req;
if (remainingStreamingBytes) {
// at this point we reset the timeout timer, we are streaming and we got a chunk
if (remainingStreamingBytes >= length) {
dataHandler(user, std::string_view(data, length));
remainingStreamingBytes -= length;
// no change to the socket here! we read all data in the buffer, return
return;
} else {
dataHandler(user, std::string_view(data, remainingStreamingBytes));
@@ -172,8 +128,6 @@ public:
length -= remainingStreamingBytes;
remainingStreamingBytes = 0;
// okay we are done with that, let's parse some more
}
} else if (fallback.length()) {
int had = fallback.length();
@@ -183,26 +137,19 @@ public:
fallback.reserve(maxCopyDistance + 32); // padding should be same as libus
fallback.append(data, maxCopyDistance);
// helst ska denna inte emitta någon data alls, vi gör det efteråt!
if (int consumed = fenceAndConsumePostPadded<true>(fallback.data(), fallback.length(), user, &req, requestHandler, dataHandler); consumed) {
int consumed = fenceAndConsumePostPadded<true>(fallback.data(), fallback.length(), user, &req, requestHandler, dataHandler);
if (consumed) {
// I guess?
fallback.clear();
data += consumed - had;
length -= consumed - had;
// ska vi inte tömma fallback här?
// exakt samma if-sats som ovan!
// this is exactly the same as above!
if (remainingStreamingBytes) {
// at this point we reset the timeout timer, we are streaming and we got a chunk
if (remainingStreamingBytes >= length) {
dataHandler(user, std::string_view(data, length));
remainingStreamingBytes -= length;
// no change to the socket here! we read all data in the buffer, return
return;
} else {
dataHandler(user, std::string_view(data, remainingStreamingBytes));
@@ -211,17 +158,13 @@ public:
length -= remainingStreamingBytes;
remainingStreamingBytes = 0;
// okay we are done with that, let's parse some more
}
}
} else {
if (fallback.length() == MAX_FALLBACK_SIZE) {
// here we failed to parse any header in the 4kb we were given!
std::cout << "INVALID HTTP! no more chances!" << std::endl;
errorHandler(user);
}
// no change in socket, or a closed socket!
return;
}
}
@@ -235,12 +178,9 @@ public:
if (length < MAX_FALLBACK_SIZE) {
fallback.append(data, length);
} else {
std::cout << "tail is invalid http!" << std::endl;
exit(-1);
errorHandler(user);
}
}
// 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);
int length = requestLength * 10;
//int currentOffset = 0;
int maxChunkSize = 10000;
char *paddedBuffer = (char *) malloc(maxChunkSize + 32);
// dela upp dessa 10 i 5 segment
HttpParser httpParser;
int validRequests = 0;
int validRequests = 0, numDataEmits = 0, numChunks = 0;
size_t dataBytes = 0;
for (int i = 0; i < 10; i++) {
@@ -46,17 +44,18 @@ void testHttpParserPerformance() {
chunkSize = length - currentOffset;
}
// kpiera skiten
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++;
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) {
//std::cout << "data bytes: " << data.length() << std::endl;
}, [&dataBytes, &numDataEmits](void *, std::string_view data) {
numDataEmits++;
dataBytes += data.length();
}, [](void *) {
@@ -64,15 +63,22 @@ void testHttpParserPerformance() {
return;
});
numChunks++;
currentOffset += chunkSize;
}
}
std::cout << "validRequests: " << validRequests << 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++) {
httpParser.consumePostPadded(data, sizeof(data) - 2, nullptr, [&validRequests](void *user, HttpRequest *req) {
httpParser.consumePostPadded(request, requestLength, nullptr, [&validRequests](void *user, HttpRequest *req) {
validRequests++;
}, [](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