diff --git a/15.pro b/15.pro index 67a612a..08f9c13 100644 --- a/15.pro +++ b/15.pro @@ -15,7 +15,6 @@ SOURCES += \ HEADERS += \ src/new_design/HttpRouter.h \ src/Loop.h \ - src/http/HttpSocket.h \ src/new_design/HttpParser.h \ src/websocket/libwshandshake.hpp \ src/websocket/WebSocketProtocol.h \ @@ -25,7 +24,9 @@ HEADERS += \ src/new_design/HttpContextData.h \ src/new_design/HttpResponseData.h \ src/new_design/HttpResponse.h \ - src/new_design/StaticDispatch.h + src/new_design/StaticDispatch.h \ + src/new_design/LoopData.h \ + src/new_design/AsyncSocket.h INCLUDEPATH += uSockets/src src #QMAKE_CXXFLAGS += -fsanitize=address diff --git a/design.dia b/design.dia index 9e05a8d..a12b6e4 100644 Binary files a/design.dia and b/design.dia differ diff --git a/main.cpp b/main.cpp index e553dfb..25ee63c 100644 --- a/main.cpp +++ b/main.cpp @@ -59,18 +59,18 @@ int main(int argc, char **argv) { // req, res? httpContext->onGet("/", [](auto *res, auto *req) { // maybe use the terminology of HttpRequest for both? - std::cout << "URL: <" << req->getUrl() << ">" << std::endl; + /*std::cout << "URL: <" << req->getUrl() << ">" << std::endl; std::cout << "Query: <" << req->getQuery() << ">" << std::endl; std::cout << "User-Agent: <" << req->getHeader("user-agent") << ">" << std::endl; - +*/ // read some data being passed - res->read([](std::string_view chunk) { + /*res->read([](std::string_view chunk) { std::cout << "Reading some streamed in data:" << chunk << std::endl; - }); + });*/ - // res->writeHeader()->write(); - - // req + res->writeStatus(uWS::HTTP_200_OK)->write([](int offset) { + return std::string_view("Hello world!"); + }, 12); }); diff --git a/src/Loop.h b/src/Loop.h index 3901871..c5caa00 100644 --- a/src/Loop.h +++ b/src/Loop.h @@ -1,31 +1,16 @@ #ifndef HUB_H #define HUB_H -#include "libusockets.h" -#include -#include -#include -#include -#include +#include "new_design/LoopData.h" + +#include + +// Loop is a bit different, it holds the loop pointer, not as "this" namespace uWS { struct Loop { us_loop *loop; - static const int CORK_BUFFER_SIZE = 16 * 1024; - static const int MAX_COPY_DISTANCE = 4096; - - struct Data { - - char *corkBuffer = new char[CORK_BUFFER_SIZE]; - int corkOffset = 0; - - Data() { - - } - - } *data; - static void wakeupCb(us_loop *loop) { } @@ -38,8 +23,9 @@ struct Loop { } - Loop() : loop(us_create_loop(1, wakeupCb, preCb, postCb, sizeof(Data))) { - new (data = (Data *) us_loop_ext(loop)) Data(); + Loop() : loop(us_create_loop(1, wakeupCb, preCb, postCb, sizeof(LoopData))) { + + new (us_loop_ext(loop)) LoopData(); } void run() { @@ -47,7 +33,7 @@ struct Loop { } ~Loop() { - + // deconstruct the loopdata } }; diff --git a/src/http/HttpSocket.h b/src/http/HttpSocket.h deleted file mode 100644 index 29fdd30..0000000 --- a/src/http/HttpSocket.h +++ /dev/null @@ -1,176 +0,0 @@ -#ifdef HTTP_H -#define HTTP_H - -#include "Socket.h" -#include "../new_design/HttpParser.h" - -#include -#include -#include - -template -struct HttpSocket : Socket { - - const size_t MAX_FALLBACK_SIZE = 4096; - - typedef typename Socket::SOCKET_TYPE SOCKET_TYPE; - using Socket::static_dispatch; - - int u32toa(uint32_t value, char *dst) { - char temp[10]; - char *p = temp; - do { - *p++ = (char) (value % 10) + '0'; - value /= 10; - } while (value > 0); - - int ret = p - temp; - - do { - *dst++ = *--p; - } while (p != temp); - - 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 - - struct Data { - uWS::HttpParser httpParser; - - std::function inStream; - - // out streaming (.end should be a wrapper of this!) - int offset = 0; - std::function 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! - int writeToCorkBufferAndReset(const char *src, int length, int contentLength, bool expectMore) { - 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, "Content-Length: ", 16); - loopData->corkOffset += 16; - - loopData->corkOffset += u32toa(contentLength, loopData->corkBuffer + loopData->corkOffset); - - memcpy(loopData->corkBuffer + loopData->corkOffset, "\r\n\r\n", 4); - loopData->corkOffset += 4; - - - memcpy(loopData->corkBuffer + loopData->corkOffset, src, length); - loopData->corkOffset += length; - - int written = static_dispatch(us_ssl_socket_write, us_socket_write)((SOCKET_TYPE *) this, loopData->corkBuffer, loopData->corkOffset, expectMore); - - loopData->corkOffset = 0; - return written; - } - - HttpSocket *writeStatus(std::string_view status) { - writeToCorkBuffer("HTTP/1.1 ", 9); - writeToCorkBuffer(status.data(), status.length()); - writeToCorkBuffer("\r\n", 2); - return this; - } - - HttpSocket *writeHeader(std::string_view key, std::string_view value) { - writeToCorkBuffer(key.data(), key.length()); - writeToCorkBuffer(": ", 2); - writeToCorkBuffer(value.data(), value.length()); - writeToCorkBuffer("\r\n", 2); - return this; - } - - // this should not be anything other than a simple convenience wrapper of streams! - void end(std::string_view data) { - // end should not explicitly flush the cork buffer! delay to when done with all http data! - writeToCorkBufferAndReset(data.data(), data.length(), data.length(), false); - } - - // stream out (todo: fix up large sends and benchmark it again) - void write(std::function cb, int length) { - - std::string_view chunk = cb(0); - - // kopiera upp till (SSL eller icke-ssl) max copy distance - - // om mer än detta, fortsätt skicka - - - // this strategy can be simplified to one, we can even have MAX_COPY_DISTANCE_SSL and MAX_COPY_DISTANCE - if (length < uWS::Loop::MAX_COPY_DISTANCE) { - // what if the streamer cannot return any data? - // then it should return something to pause write, and then start it again - // basically we need throttling - writeToCorkBufferAndReset(chunk.data(), chunk.length(), length, false); - } else { - // copying some data with the headers is a good idea for SSL but probably not for non-SSL - writeToCorkBufferAndReset(chunk.data(), uWS::Loop::MAX_COPY_DISTANCE, length, true); - - // just assume this went fine - Data *httpData = (Data *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) this); - - // write that off! - static_dispatch(us_ssl_socket_write, us_socket_write)((SOCKET_TYPE *) this, chunk.data() + uWS::Loop::MAX_COPY_DISTANCE, chunk.length() - uWS::Loop::MAX_COPY_DISTANCE, 0); - - // if offset is at the end, we are done - if (httpData->offset < length) { - httpData->outStream = cb; - } - } - } - - // this thing should only be reachable from App! - void onWritable() { - Data *httpData = (Data *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) this); - - - // now we start streaming as much as possible in each call! - std::string_view chunk = httpData->outStream(httpData->offset); - - // write that off! - static_dispatch(us_ssl_socket_write, us_socket_write)((SOCKET_TYPE *) this, chunk.data(), chunk.length(), 0); - } - - void onData(char *data, int length, std::function *, uWS::HttpRequest *)> &onHttpRequest) { - Data *httpData = (Data *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) this); - - // todo: this is where the HttpSocket binds together HttpParser and HttpRouter into one - httpData->httpParser.consumePostPadded(data, length, this, [&onHttpRequest](void *user, HttpRequest *httpRequest) { - onHttpRequest((HttpSocket *) user, httpRequest); - }, [httpData](void *user, std::string_view data) { - if (httpData->inStream) { - httpData->inStream(data); - } - }, [](void *user) { - std::cout << "INVALID HTTP!" << std::endl; - }); - } - - void read(decltype(Data::inStream) stream) { - Data *httpData = (Data *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) this); - - httpData->inStream = stream; - } - - // typical shared function? - void close() { - static_dispatch(us_ssl_socket_close, us_socket_close)((SOCKET_TYPE *) this); - } - - HttpSocket() = delete; -}; - -#endif // HTTP_H diff --git a/src/new_design/AsyncSocket.h b/src/new_design/AsyncSocket.h new file mode 100644 index 0000000..b993208 --- /dev/null +++ b/src/new_design/AsyncSocket.h @@ -0,0 +1,121 @@ +#ifndef ASYNCSOCKET_H +#define ASYNCSOCKET_H + +#include "StaticDispatch.h" +#include "LoopData.h" + +// todo: this is where the magic happens + +namespace uWS { + +template +struct AsyncSocket : StaticDispatch { + + using SOCKET_TYPE = typename StaticDispatch::SOCKET_TYPE; + using StaticDispatch::static_dispatch; + + // control everything with write, cork, uncork, close + // have HttpResponseData derive from AsyncSocketData? + + // HttpResponse will only need one buffer for outgoing - the corked up + + // maybe better name would be CorkableSocket? + + // this does not belong here! + int u32toa(uint32_t value, char *dst) { + char temp[10]; + char *p = temp; + do { + *p++ = (char) (value % 10) + '0'; + value /= 10; + } while (value > 0); + + int ret = p - temp; + + do { + *dst++ = *--p; + } while (p != temp); + + return ret; + } + + void cork() { + LoopData *loopData = (LoopData *) us_loop_ext(us_socket_context_loop(us_socket_get_context((us_socket *) this))); + + loopData->corked = true; + + } + + void uncork() { + LoopData *loopData = (LoopData *) us_loop_ext(us_socket_context_loop(us_socket_get_context((us_socket *) this))); + + loopData->corked = false; + + // send it off now! + + int written = static_dispatch(us_ssl_socket_write, us_socket_write)((SOCKET_TYPE *) this, loopData->corkBuffer, loopData->corkOffset, false); + + loopData->corkOffset = 0; + + // buffer the rest up in the outbuffer of this asynsocket! + } + + void writeUnsigned(unsigned int value) { + LoopData *loopData = (LoopData *) us_loop_ext(us_socket_context_loop(us_socket_get_context((us_socket *) this))); + + loopData->corkOffset += u32toa(value, loopData->corkBuffer + loopData->corkOffset); + } + + void write(const char *src, int length) { + LoopData *loopData = (LoopData *) us_loop_ext(us_socket_context_loop(us_socket_get_context((us_socket *) this))); + + memcpy(loopData->corkBuffer + loopData->corkOffset, src, length); + loopData->corkOffset += length; + } + + int writeOptionally(const char *src, int length) { + + // not optional for now + AsyncSocket::write(src, length); + + return length; + +/* + // kopiera upp till (SSL eller icke-ssl) max copy distance + + // om mer än detta, fortsätt skicka + + // this entire strategy should be made entirely in AsyncSocket! + + // this strategy can be simplified to one, we can even have MAX_COPY_DISTANCE_SSL and MAX_COPY_DISTANCE + if (length < LoopData::MAX_COPY_DISTANCE) { + AsyncSocket::write("Content-Length: ", 16); + AsyncSocket::writeUnsigned(chunk.length()); + AsyncSocket::write("\r\n\r\n", 4); + AsyncSocket::write(chunk.data(), chunk.length()); + } else { + // copying some data with the headers is a good idea for SSL but probably not for non-SSL + //writeToCorkBufferAndReset(chunk.data(), LoopData::MAX_COPY_DISTANCE, length, true); + + // just assume this went fine + HttpResponseData *httpData = (HttpResponseData *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) this); + + // write that off! (should never happen here!) + //static_dispatch(us_ssl_socket_write, us_socket_write)((SOCKET_TYPE *) this, chunk.data() + LoopData::MAX_COPY_DISTANCE, chunk.length() - LoopData::MAX_COPY_DISTANCE, 0); + + // if offset is at the end, we are done + if (httpData->offset < length) { + httpData->outStream = cb; + } + }*/ + + } + + void close() { + static_dispatch(us_ssl_socket_close, us_socket_close)((SOCKET_TYPE *) this); + } +}; + +} + +#endif // ASYNCSOCKET_H diff --git a/src/new_design/HttpContext.h b/src/new_design/HttpContext.h index fa133c8..57aab96 100644 --- a/src/new_design/HttpContext.h +++ b/src/new_design/HttpContext.h @@ -4,6 +4,7 @@ #include "Loop.h" #include "HttpContextData.h" #include "HttpResponseData.h" +#include "AsyncSocket.h" #include "StaticDispatch.h" #include @@ -69,6 +70,9 @@ public: static_dispatch(us_ssl_socket_context_on_data, us_socket_context_on_data)(getSocketContext(), [](auto *s, char *data, int length) { HttpContextData *httpContextData = getSocketContextData(s); + // cork this socket (move this to loop?) + ((AsyncSocket *) s)->cork(); + HttpResponseData *httpResponseData = (HttpResponseData *) static_dispatch(us_ssl_socket_ext, us_socket_ext)(s); httpResponseData->consumePostPadded(data, length, s, [httpContextData](void *s, uWS::HttpRequest *httpRequest) { @@ -82,8 +86,8 @@ public: httpContextData->router.route("get", 3, httpRequest->getUrl().data(), httpRequest->getUrl().length(), &userData); }, [httpResponseData](void *user, std::string_view data) { - if (httpResponseData->readHandler) { - httpResponseData->readHandler(data); + if (httpResponseData->inStream) { + httpResponseData->inStream(data); } }, [](void *user) { std::cout << "INVALID HTTP!" << std::endl; @@ -91,6 +95,9 @@ public: // close it down }); + // uncork + ((AsyncSocket *) s)->uncork(); + return s; }); @@ -105,6 +112,18 @@ public: // why? WE should emit this event via the socket data that we access! //((HttpSocket *) s)->onWritable(); + // this thing should only be reachable from App! + /*void onWritable() { + Data *httpData = (Data *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) this); + + + // now we start streaming as much as possible in each call! + std::string_view chunk = httpData->outStream(httpData->offset); + + // write that off! + static_dispatch(us_ssl_socket_write, us_socket_write)((SOCKET_TYPE *) this, chunk.data(), chunk.length(), 0); + }*/ + return s; }); @@ -133,33 +152,30 @@ public: } static HttpContext *create(us_loop *loop) { - HttpContext *httpContext = (HttpContext *) us_create_socket_context(loop, sizeof(HttpContextData)); new ((HttpContextData *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)((SOCKET_CONTEXT_TYPE *) httpContext)) HttpContextData(); - return httpContext->init(); } void free() { + + // call destructor! + static_dispatch(us_ssl_socket_context_free, us_socket_context_free)(getSocketContext()); } void onGet(std::string pattern, std::function *, uWS::HttpRequest *)> handler) { - HttpContextData *data = getSocketContextData(); + HttpContextData *httpContextData = getSocketContextData(); - // add things to the router - - data->router.add("get", pattern.c_str(), [handler](typename HttpContextData::UserData *user, auto *args) { + httpContextData->router.add("get", pattern.c_str(), [handler](typename HttpContextData::UserData *user, auto *args) { handler(user->httpResponse, user->httpRequest); }); - } void listen(const char *host, int port, int options) { - static_dispatch(us_ssl_socket_context_listen, us_socket_context_listen)(getSocketContext(), host, port, options, sizeof(HttpContextData)); + static_dispatch(us_ssl_socket_context_listen, us_socket_context_listen)(getSocketContext(), host, port, options, sizeof(HttpResponseData)); } - }; } diff --git a/src/new_design/HttpResponse.h b/src/new_design/HttpResponse.h index 9cef5d3..320b4f8 100644 --- a/src/new_design/HttpResponse.h +++ b/src/new_design/HttpResponse.h @@ -1,40 +1,68 @@ #ifndef HTTPRESPONSE_H #define HTTPRESPONSE_H +#include "AsyncSocket.h" #include "HttpResponseData.h" -#include "StaticDispatch.h" - -// we will most probably depend on the LoopData to do corking and such namespace uWS { +const char *HTTP_200_OK = "200 OK"; + template -struct HttpResponse : StaticDispatch { +struct HttpResponse : public AsyncSocket { private: using SOCKET_TYPE = typename StaticDispatch::SOCKET_TYPE; using StaticDispatch::static_dispatch; - // helpers HttpResponseData *getHttpResponseData() { return (HttpResponseData *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) this); } public: - void writeHeader() { + HttpResponse *writeStatus(std::string_view status) { + AsyncSocket::write("HTTP/1.1 ", 9); + AsyncSocket::write(status.data(), status.length()); + AsyncSocket::write("\r\n", 2); + return this; + } + HttpResponse *writeHeader(std::string_view key, std::string_view value) { + AsyncSocket::write(key.data(), key.length()); + AsyncSocket::write(": ", 2); + AsyncSocket::write(value.data(), value.length()); + AsyncSocket::write("\r\n", 2); + return this; + } + + void write(std::function cb, int length) { + // what if the streamer cannot return any data? + // then it should return something to pause write, and then start it again + // basically we need throttling + std::string_view chunk = cb(0); + + AsyncSocket::write("Content-Length: ", 16); + AsyncSocket::writeUnsigned(chunk.length()); + AsyncSocket::write("\r\n\r\n", 4); + if (AsyncSocket::writeOptionally(chunk.data(), chunk.length()) < length) { + getHttpResponseData()->outStream = cb; + } } // this will probably not be this clean: it will most probably want to do some active pulling of data? void read(std::function handler) { HttpResponseData *data = getHttpResponseData(); - data->readHandler = handler; + data->inStream = handler; } - // read and write streams most definitely will be called by the context, thus the context absolutely depends on the httpresponse! - // or, we both depend on each others data structures only? + // this should not be anything other than a simple convenience wrapper of streams! + void end(std::string_view data) { + writeStatus("200 OK")->write([data](int offset) { + return std::string_view(data.data() + offset, data.length() - offset); + }, data.length()); + } }; } diff --git a/src/new_design/HttpResponseData.h b/src/new_design/HttpResponseData.h index 6f1f78e..01956c5 100644 --- a/src/new_design/HttpResponseData.h +++ b/src/new_design/HttpResponseData.h @@ -1,8 +1,6 @@ #ifndef HTTPRESPONSEDATA_H #define HTTPRESPONSEDATA_H -// so what do we depend on? - #include "HttpParser.h" #include @@ -11,7 +9,12 @@ namespace uWS { template struct HttpResponseData : HttpParser { - std::function readHandler; + // inStream, outStream + std::function inStream; + std::function outStream; + + int offset = 0; + // writeHandler }; diff --git a/src/new_design/LoopData.h b/src/new_design/LoopData.h new file mode 100644 index 0000000..8abc96d --- /dev/null +++ b/src/new_design/LoopData.h @@ -0,0 +1,19 @@ +#ifndef LOOPDATA_H +#define LOOPDATA_H + +struct LoopData { + +private: + +public: + + static const int CORK_BUFFER_SIZE = 16 * 1024; + static const int MAX_COPY_DISTANCE = 4096; + + char *corkBuffer = new char[CORK_BUFFER_SIZE]; + int corkOffset = 0; + bool corked = false; + +}; + +#endif // LOOPDATA_H diff --git a/src/new_design/StaticDispatch.h b/src/new_design/StaticDispatch.h index db889f2..75077ca 100644 --- a/src/new_design/StaticDispatch.h +++ b/src/new_design/StaticDispatch.h @@ -6,6 +6,8 @@ #include #include +namespace uWS { + template struct StaticDispatch { template @@ -21,4 +23,6 @@ struct StaticDispatch { typedef typename std::conditional::type SOCKET_CONTEXT_TYPE; }; +} + #endif // STATICDISPATCH_H