Rough skeleton for proper HTTP parsing
This commit is contained in:
@@ -30,10 +30,19 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
app.onGet("/", [](auto *s, auto *req, auto *args) {
|
app.onGet("/", [](auto *s, auto *req, auto *args) {
|
||||||
|
|
||||||
|
// streams need to expose more information about lifetime!
|
||||||
s->writeStatus("200 OK")->write([](int offset) {
|
s->writeStatus("200 OK")->write([](int offset) {
|
||||||
return std::string_view(buffer.data() + offset, buffer.length() - offset);
|
return std::string_view(buffer.data() + offset, buffer.length() - offset);
|
||||||
}, buffer.length());
|
}, buffer.length());
|
||||||
|
|
||||||
|
}).onPost("/upload", [](auto *s, auto *req, auto *args) {
|
||||||
|
|
||||||
|
s->read([s](std::string_view chunk) {
|
||||||
|
std::cout << "Received chunk on URL /upload: <" << chunk << ">" << std::endl;
|
||||||
|
|
||||||
|
s->writeStatus("200 OK")->end("Thanks for posting!");
|
||||||
|
});
|
||||||
|
|
||||||
}).onWebSocket("/wsApi", []() {
|
}).onWebSocket("/wsApi", []() {
|
||||||
|
|
||||||
}).listen("localhost", 3000, 0);
|
}).listen("localhost", 3000, 0);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
typedef typename std::conditional<SSL, us_ssl_socket_context, us_socket_context>::type SOCKET_CONTEXT_TYPE;
|
typedef typename std::conditional<SSL, us_ssl_socket_context, us_socket_context>::type SOCKET_CONTEXT_TYPE;
|
||||||
|
typedef typename HttpSocket<SSL>::Data HTTP_SOCKET_DATA_TYPE;
|
||||||
|
|
||||||
struct Data {
|
struct Data {
|
||||||
Data() {
|
Data() {
|
||||||
@@ -64,40 +65,28 @@ protected:
|
|||||||
new (data = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(httpServerContext)) Data();
|
new (data = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(httpServerContext)) Data();
|
||||||
|
|
||||||
static_dispatch(us_ssl_socket_context_on_open, us_socket_context_on_open)(httpServerContext, [](auto *s) {
|
static_dispatch(us_ssl_socket_context_on_open, us_socket_context_on_open)(httpServerContext, [](auto *s) {
|
||||||
Data *data = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s));
|
Data *appData = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s));
|
||||||
|
|
||||||
new (static_dispatch(us_ssl_socket_ext, us_socket_ext)(s)) typename HttpSocket<SSL>::Data;
|
new (static_dispatch(us_ssl_socket_ext, us_socket_ext)(s)) HTTP_SOCKET_DATA_TYPE;
|
||||||
|
|
||||||
if (!data->onHttpConnection) {
|
if (appData->onHttpConnection) {
|
||||||
return;
|
appData->onHttpConnection((HttpSocket<SSL> *) s);
|
||||||
}
|
}
|
||||||
|
|
||||||
data->onHttpConnection((HttpSocket<SSL> *) s);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
static_dispatch(us_ssl_socket_context_on_close, us_socket_context_on_close)(httpServerContext, [](auto *s) {
|
static_dispatch(us_ssl_socket_context_on_close, us_socket_context_on_close)(httpServerContext, [](auto *s) {
|
||||||
Data *data = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s));
|
Data *appData = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s));
|
||||||
|
|
||||||
// todo: run the destructor!
|
((HTTP_SOCKET_DATA_TYPE *) static_dispatch(us_ssl_socket_ext, us_socket_ext)(s))->~HTTP_SOCKET_DATA_TYPE();
|
||||||
//((typename HttpSocket<SSL>::Data *) static_dispatch(us_ssl_socket_ext, us_socket_ext)(s))->~(typename HttpSocket<SSL>::Data)();
|
|
||||||
|
|
||||||
if (!data->onHttpDisconnection) {
|
if (appData->onHttpDisconnection) {
|
||||||
return;
|
appData->onHttpDisconnection((HttpSocket<SSL> *) s);
|
||||||
}
|
}
|
||||||
|
|
||||||
data->onHttpDisconnection((HttpSocket<SSL> *) s);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
static_dispatch(us_ssl_socket_context_on_data, us_socket_context_on_data)(httpServerContext, [](auto *s, char *data, int length) {
|
static_dispatch(us_ssl_socket_context_on_data, us_socket_context_on_data)(httpServerContext, [](auto *s, char *data, int length) {
|
||||||
|
Data *appData = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s));
|
||||||
Data *contextData = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s));
|
((HttpSocket<SSL> *) s)->onData(data, length, appData->onHttpRequest);
|
||||||
|
|
||||||
HttpRequest req(data, length);
|
|
||||||
if (req.isComplete()) {
|
|
||||||
contextData->onHttpRequest((HttpSocket<SSL> *) s, &req);
|
|
||||||
} else {
|
|
||||||
std::cout << "Got chunked HTTP headers!" << std::endl;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
static_dispatch(us_ssl_socket_context_on_writable, us_socket_context_on_writable)(httpServerContext, [](auto *s) {
|
static_dispatch(us_ssl_socket_context_on_writable, us_socket_context_on_writable)(httpServerContext, [](auto *s) {
|
||||||
|
|||||||
+41
-15
@@ -1,19 +1,27 @@
|
|||||||
#ifndef HTTPREQUEST_H
|
#ifndef HTTPREQUEST_H
|
||||||
#define HTTPREQUEST_H
|
#define HTTPREQUEST_H
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
// holds the header pointers and wrappers
|
// holds the header pointers and wrappers
|
||||||
struct HttpRequest {
|
struct HttpRequest {
|
||||||
|
|
||||||
struct Header {
|
struct Header {
|
||||||
char *key, *value;
|
char *key, *value;
|
||||||
unsigned int keyLength, valueLength;
|
unsigned int keyLength, valueLength;
|
||||||
|
|
||||||
|
operator bool() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MAX_HEADERS 100
|
#define MAX_HEADERS 100
|
||||||
Header headers[MAX_HEADERS];
|
Header headers[MAX_HEADERS];
|
||||||
|
|
||||||
// UNSAFETY NOTE: assumes *end == '\r' (might unref end pointer)
|
// UNSAFETY NOTE: assumes *end == '\r' (might unref end pointer)
|
||||||
char *getHeaders(char *buffer, char *end, struct Header *headers, size_t maxHeaders) {
|
static char *getHeaders(char *buffer, char *end, struct Header *headers, size_t maxHeaders) {
|
||||||
for (unsigned int i = 0; i < maxHeaders; i++) {
|
for (unsigned int i = 0; i < maxHeaders; i++) {
|
||||||
for (headers->key = buffer; (*buffer != ':') & (*buffer > 32); *(buffer++) |= 32);
|
for (headers->key = buffer; (*buffer != ':') & (*buffer > 32); *(buffer++) |= 32);
|
||||||
if (*buffer == '\r') {
|
if (*buffer == '\r') {
|
||||||
@@ -40,27 +48,45 @@ struct HttpRequest {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view url;
|
|
||||||
|
|
||||||
HttpRequest(char *data, int length) {
|
int consumePostPadded(char *data, int length) {
|
||||||
// parse the shit
|
char *cursor = data;
|
||||||
data[length] = '\r';
|
|
||||||
|
|
||||||
if (getHeaders(data, data + length, headers, MAX_HEADERS)) {
|
|
||||||
|
|
||||||
|
if (cursor = getHeaders(data, data + length, headers, MAX_HEADERS)) {
|
||||||
|
// strip out the initial stuff
|
||||||
headers->valueLength = std::max<int>(0, headers->valueLength - 9);
|
headers->valueLength = std::max<int>(0, headers->valueLength - 9);
|
||||||
|
|
||||||
// headers should really just be string_view from the start!
|
|
||||||
url = std::string_view(headers[0].value, headers[0].valueLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view getUrl() {
|
std::string_view getUrl() {
|
||||||
return url;
|
return std::string_view(headers[0].value, headers[0].valueLength);
|
||||||
}
|
|
||||||
|
|
||||||
bool isComplete() {
|
|
||||||
return url.length();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
+148
-34
@@ -3,13 +3,17 @@
|
|||||||
|
|
||||||
#include "libusockets.h"
|
#include "libusockets.h"
|
||||||
#include "Loop.h"
|
#include "Loop.h"
|
||||||
|
#include "HttpRequest.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
// HttpSocket is an alias for us_socket
|
|
||||||
template <bool SSL>
|
template <bool SSL>
|
||||||
struct HttpSocket {
|
struct HttpSocket {
|
||||||
|
|
||||||
|
const size_t MAX_FALLBACK_SIZE = 1024 * 4;
|
||||||
|
|
||||||
template <class A, class B>
|
template <class A, class B>
|
||||||
static constexpr typename std::conditional<SSL, A, B>::type *static_dispatch(A *a, B *b) {
|
static constexpr typename std::conditional<SSL, A, B>::type *static_dispatch(A *a, B *b) {
|
||||||
if constexpr(SSL) {
|
if constexpr(SSL) {
|
||||||
@@ -21,34 +25,11 @@ struct HttpSocket {
|
|||||||
|
|
||||||
typedef typename std::conditional<SSL, us_ssl_socket, us_socket>::type SOCKET_TYPE;
|
typedef typename std::conditional<SSL, us_ssl_socket, us_socket>::type SOCKET_TYPE;
|
||||||
|
|
||||||
// chunked response will be tricky with this buffering scheme
|
int u32toa(uint32_t value, char *dst) {
|
||||||
// if we do not fit, we can always use the header buffer for this (both in and out!)
|
|
||||||
// put first 8kb chunk in the http buffer, then from there it's the stream's job!
|
|
||||||
// httpheaders should only have 1 stream in and 1 stream out, but we can have helper wrappers
|
|
||||||
|
|
||||||
// data is stored in ext
|
|
||||||
struct Data {
|
|
||||||
// incomplete headers buffer
|
|
||||||
std::string headerBuffer;
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
std::function<std::string_view(int)> outStream;
|
|
||||||
};
|
|
||||||
|
|
||||||
// only this one should be used!
|
|
||||||
void writeToCorkBuffer(const char *src, int length) {
|
|
||||||
uWS::Loop::Data *loopData = (uWS::Loop::Data *) us_loop_ext(us_socket_context_loop(us_socket_get_context((us_socket *) this)));
|
|
||||||
|
|
||||||
memcpy(loopData->corkBuffer + loopData->corkOffset, src, length);
|
|
||||||
loopData->corkOffset += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sprintf is super slow, this one is a lot faster
|
|
||||||
int u32toa_naive(uint32_t value, char *dst) {
|
|
||||||
char temp[10];
|
char temp[10];
|
||||||
char *p = temp;
|
char *p = temp;
|
||||||
do {
|
do {
|
||||||
*p++ = char(value % 10) + '0';
|
*p++ = (char) (value % 10) + '0';
|
||||||
value /= 10;
|
value /= 10;
|
||||||
} while (value > 0);
|
} while (value > 0);
|
||||||
|
|
||||||
@@ -61,6 +42,42 @@ struct HttpSocket {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int str2int(const char *str, int len) {
|
||||||
|
int i;
|
||||||
|
int ret = 0;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
ret = ret * 10 + (str[i] - '0');
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// chunked response will be tricky with this buffering scheme
|
||||||
|
// if we do not fit, we can always use the header buffer for this (both in and out!)
|
||||||
|
// put first 8kb chunk in the http buffer, then from there it's the stream's job!
|
||||||
|
// httpheaders should only have 1 stream in and 1 stream out, but we can have helper wrappers
|
||||||
|
|
||||||
|
// data is stored in ext
|
||||||
|
struct Data {
|
||||||
|
// fallback buffering
|
||||||
|
std::string fallback;
|
||||||
|
|
||||||
|
// these two control input streaming
|
||||||
|
int contentLength = 0;
|
||||||
|
std::function<void(std::string_view)> inStream;
|
||||||
|
|
||||||
|
// out streaming (.end should be a wrapper of this!)
|
||||||
|
int offset = 0;
|
||||||
|
std::function<std::string_view(int)> outStream;
|
||||||
|
};
|
||||||
|
|
||||||
|
// only this one should be used!
|
||||||
|
void writeToCorkBuffer(const char *src, int length) {
|
||||||
|
uWS::Loop::Data *loopData = (uWS::Loop::Data *) us_loop_ext(us_socket_context_loop(us_socket_get_context((us_socket *) this)));
|
||||||
|
|
||||||
|
memcpy(loopData->corkBuffer + loopData->corkOffset, src, length);
|
||||||
|
loopData->corkOffset += length;
|
||||||
|
}
|
||||||
|
|
||||||
// never rely on this one!
|
// never rely on this one!
|
||||||
int writeToCorkBufferAndReset(const char *src, int length, int contentLength) {
|
int writeToCorkBufferAndReset(const char *src, int length, int contentLength) {
|
||||||
uWS::Loop::Data *loopData = (uWS::Loop::Data *) us_loop_ext(us_socket_context_loop(us_socket_get_context((us_socket *) this)));
|
uWS::Loop::Data *loopData = (uWS::Loop::Data *) us_loop_ext(us_socket_context_loop(us_socket_get_context((us_socket *) this)));
|
||||||
@@ -68,7 +85,7 @@ struct HttpSocket {
|
|||||||
memcpy(loopData->corkBuffer + loopData->corkOffset, "Content-Length: ", 16);
|
memcpy(loopData->corkBuffer + loopData->corkOffset, "Content-Length: ", 16);
|
||||||
loopData->corkOffset += 16;
|
loopData->corkOffset += 16;
|
||||||
|
|
||||||
loopData->corkOffset += u32toa_naive(contentLength, loopData->corkBuffer + loopData->corkOffset);
|
loopData->corkOffset += u32toa(contentLength, loopData->corkBuffer + loopData->corkOffset);
|
||||||
|
|
||||||
memcpy(loopData->corkBuffer + loopData->corkOffset, "\r\n\r\n", 4);
|
memcpy(loopData->corkBuffer + loopData->corkOffset, "\r\n\r\n", 4);
|
||||||
loopData->corkOffset += 4;
|
loopData->corkOffset += 4;
|
||||||
@@ -86,7 +103,6 @@ struct HttpSocket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loopData->corkOffset = 0;
|
loopData->corkOffset = 0;
|
||||||
|
|
||||||
return written;
|
return written;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +121,7 @@ struct HttpSocket {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this should not be anything other than a simple convenience wrapper of streams!
|
||||||
void end(std::string_view data) {
|
void end(std::string_view data) {
|
||||||
// end should not explicitly flush the cork buffer! delay to when done with all http data!
|
// end should not explicitly flush the cork buffer! delay to when done with all http data!
|
||||||
writeToCorkBufferAndReset(data.data(), data.length(), data.length());
|
writeToCorkBufferAndReset(data.data(), data.length(), data.length());
|
||||||
@@ -157,17 +174,114 @@ struct HttpSocket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// can't create this!
|
void onData(char *data, int length, std::function<void(HttpSocket<SSL> *, HttpRequest *)> &onHttpRequest) {
|
||||||
HttpSocket() = delete;
|
Data *httpData = (Data *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) this);
|
||||||
|
|
||||||
// the HttpParser should maybe be moved out of this into its own HttpProtocol.h like with websocket?
|
//std::cout << std::string_view(data, length) << std::endl;
|
||||||
void parse(char *data, int length) {
|
|
||||||
|
|
||||||
std::cout << "Parsing headers: " << std::string_view(data, length) << std::endl;
|
HttpRequest req;
|
||||||
|
|
||||||
|
req.fenceRegion(data, length);
|
||||||
|
|
||||||
|
if (httpData->contentLength) {
|
||||||
|
// at this point we reset the timeout timer
|
||||||
|
if (httpData->contentLength >= length) {
|
||||||
|
httpData->inStream(std::string_view(data, length));
|
||||||
|
httpData->contentLength -= length;
|
||||||
|
// no change to the socket here!
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
httpData->inStream(std::string_view(data, httpData->contentLength));
|
||||||
|
|
||||||
|
data += httpData->contentLength;
|
||||||
|
length -= httpData->contentLength;
|
||||||
|
|
||||||
|
httpData->contentLength = 0;
|
||||||
|
}
|
||||||
|
} else if (httpData->fallback.length()) {
|
||||||
|
int maxCopyDistance = std::min(MAX_FALLBACK_SIZE - httpData->fallback.length(), (size_t) length);
|
||||||
|
|
||||||
|
httpData->fallback.reserve(maxCopyDistance + 32); // padding should be same as libus
|
||||||
|
httpData->fallback.append(data, maxCopyDistance);
|
||||||
|
|
||||||
|
|
||||||
|
if (int consumed = req.consumePostPadded(httpData->fallback.data(), httpData->fallback.length()); consumed) {
|
||||||
|
httpData->fallback.clear();
|
||||||
|
|
||||||
|
data += consumed;
|
||||||
|
length -= consumed;
|
||||||
|
|
||||||
|
onHttpRequest(this, &req);
|
||||||
|
|
||||||
|
// see if we can read any posted data here (do we have contentLength header set?)
|
||||||
|
} else {
|
||||||
|
if (httpData->fallback.length() < MAX_FALLBACK_SIZE) {
|
||||||
|
std::cout << "Http headers coming in chunks I see, fine I'll pass!" << std::endl;
|
||||||
|
} else {
|
||||||
|
// here we failed to parse any header in the 4kb we were given!
|
||||||
|
std::cout << "INVALID HTTP! no more chances!" << std::endl;
|
||||||
|
}
|
||||||
|
// no change in socket, or a closed socket!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int consumed = 0; length && (consumed = req.consumePostPadded(data, length)); ) {
|
||||||
|
|
||||||
|
//std::cout << "Parsing now <" << std::string_view(data, length) << ">" << std::endl;
|
||||||
|
|
||||||
|
data += consumed;
|
||||||
|
length -= consumed;
|
||||||
|
|
||||||
|
// first emit the request
|
||||||
|
onHttpRequest(this, &req);
|
||||||
|
|
||||||
|
// then consume and stream any data!
|
||||||
|
if (std::string_view contentLength = req.getHeader("content-length"); contentLength.length()) {
|
||||||
|
// can we read everything off right now?
|
||||||
|
|
||||||
|
httpData->contentLength = str2int(contentLength.data(), contentLength.length());
|
||||||
|
|
||||||
|
//std::cout << "content length!" << std::endl;
|
||||||
|
|
||||||
|
|
||||||
|
int emittable = std::min(httpData->contentLength, length);
|
||||||
|
|
||||||
|
|
||||||
|
//std::cout << "Emittable: " << emittable << std::endl;
|
||||||
|
|
||||||
|
httpData->inStream(std::string_view(data, emittable));
|
||||||
|
|
||||||
|
|
||||||
|
httpData->contentLength -= emittable;
|
||||||
|
|
||||||
|
|
||||||
|
length -= emittable;
|
||||||
|
|
||||||
|
// otherwise, enter contentLength state!
|
||||||
|
} else {
|
||||||
|
//std::cout << "We don't have content-length!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length) {
|
||||||
|
if (length < MAX_FALLBACK_SIZE) {
|
||||||
|
// buffer up for next
|
||||||
|
} else {
|
||||||
|
// invalid http!
|
||||||
|
std::cout << "invalid http! fuck off!" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stream(int length, decltype(Data::outStream) stream);
|
void read(decltype(Data::inStream) stream) {
|
||||||
|
Data *httpData = (Data *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) this);
|
||||||
|
|
||||||
|
httpData->inStream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpSocket() = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // HTTP_H
|
#endif // HTTP_H
|
||||||
|
|||||||
+1
-1
Submodule uSockets updated: 4ef0f2ecd1...ad9a5fb11d
Reference in New Issue
Block a user