diff --git a/misc/15.pro b/misc/15.pro index 44a4063..56c4811 100644 --- a/misc/15.pro +++ b/misc/15.pro @@ -29,7 +29,9 @@ HEADERS += \ ../src/AsyncSocketData.h \ ../src/Loop.h \ ../src/App.h \ - ../src/Utilities.h + ../src/Utilities.h \ + ../src/WebSocketContext.h \ + ../src/WebSocketContextData.h INCLUDEPATH += ../uSockets/src ../src QMAKE_CXXFLAGS += -fsanitize=address diff --git a/misc/main.cpp b/misc/main.cpp index 54ce5e5..6b5eb77 100644 --- a/misc/main.cpp +++ b/misc/main.cpp @@ -5,6 +5,20 @@ int main(int argc, char **argv) { + // do websockets here + + uWS::App().get("/hello", [](auto *res, auto *req) { + res->end("Hello HTTP!"); + }).ws("/*", [](auto *ws, auto *req) { + std::cout << "WebSocket conntected to URL: " << req->getUrl() << std::endl; + }).listen(3000, [](auto *token) { + if (token) { + std::cout << "Listening on port " << 3000 << std::endl; + } + }).run(); + + return 0; + AsyncFileStreamer *asyncFileStreamer = new AsyncFileStreamer("/home/alexhultman/v0.15/public"); uWS::/*SSL*/App(/*{ diff --git a/src/App.h b/src/App.h index 7557fbf..1c1cad0 100644 --- a/src/App.h +++ b/src/App.h @@ -22,6 +22,9 @@ #include "HttpContext.h" #include "HttpResponse.h" +#include "WebSocketContext.h" + +#include "websocket/libwshandshake.hpp" namespace uWS { template @@ -29,6 +32,8 @@ struct TemplatedApp { private: HttpContext *httpContext; + // the app does not own a websocket context, it is created on .ws(...) calls on demand! + public: ~TemplatedApp() { @@ -41,6 +46,73 @@ public: TemplatedApp(us_ssl_socket_context_options sslOptions = {}) { httpContext = uWS::HttpContext::create(uWS::Loop::defaultLoop(), &sslOptions); + + // construct the websocket cintext? no! on demand! + } + + // this method creates a new websocket context and attaches it to a path + TemplatedApp &ws(std::string pattern, std::function connectHandler) { + // init the websocket context here! + uWS::WebSocketContext *webSocketContext = uWS::WebSocketContext::create(uWS::Loop::defaultLoop(), (typename StaticDispatch::SOCKET_CONTEXT_TYPE *) httpContext); + + return get(pattern, [webSocketContext, this, connectHandler](auto *res, auto *req) { + + std::string_view secWebSocketKey = req->getHeader("sec-websocket-key"); + if (secWebSocketKey.length()) { + + // note: OpenSSL can be used here to speed this up somewhat + char secWebSocketAccept[29] = {}; + WebSocketHandshake::generate(secWebSocketKey.data(), secWebSocketAccept); + + res->writeStatus("101 Switching Protocols") + ->writeHeader("Upgrade", "websocket") + ->writeHeader("Connection", "Upgrade") + ->writeHeader("Sec-WebSocket-Accept", secWebSocketAccept) + ->end(); + + std::cout << "Adopting" << std::endl; + + // adopting will immediately delete the socket! we cannot rely on reading anything on it + // rely on http context data + + + //typename StaticDispatch::SOCKET_CONTEXT_TYPE *socketContext = (typename StaticDispatch::SOCKET_CONTEXT_TYPE *) StaticDispatch::static_dispatch(us_ssl_socket_get_context, us_socket_get_context)((typename StaticDispatch::SOCKET_TYPE *) res); + //StaticDispatch::static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(socketContext); + + + + void *newSocket = StaticDispatch::static_dispatch(us_ssl_socket_context_adopt_socket, us_socket_context_adopt_socket)( + (typename StaticDispatch::SOCKET_CONTEXT_TYPE *) webSocketContext, (typename StaticDispatch::SOCKET_TYPE *) res, 15); + + httpContext->upgradeToWebSocket( + newSocket + ); + + std::cout << "Adopted!" << std::endl; + + + // we should hand the new socket to the handler + connectHandler(newSocket, req); + + + /*res->upgradeToWebSocket( + StaticDispatch::static_dispatch(us_ssl_socket_context_adopt_socket, us_socket_context_adopt_socket)( + (typename StaticDispatch::SOCKET_CONTEXT_TYPE *) webSocketContext, (typename StaticDispatch::SOCKET_TYPE *) res, 15));*/ + + + + } else { + + std::cout << "This is not a websocket so fuck off!" << std::endl; + + // maybe pass this one to a HTTP handler on the websocket + + // note: this calls the http close handler inline + res->close(); + } + + + }); } TemplatedApp &get(std::string pattern, std::function *, HttpRequest *)> handler) { diff --git a/src/HttpContext.h b/src/HttpContext.h index 73fae4d..8d2f52d 100644 --- a/src/HttpContext.h +++ b/src/HttpContext.h @@ -117,6 +117,8 @@ private: /* Cork this socket */ ((AsyncSocket *) s)->cork(); + // clients need to know the cursor after http parse, not servers! + // how far did we read then? we need to know to continue with websocket parsing data? or? void *returnedSocket = httpResponseData->consumePostPadded(data, length, s, [httpContextData](void *s, uWS::HttpRequest *httpRequest) -> void * { /* For every request we reset the timeout and hang until user makes action */ /* Warning: if we are in shutdown state, resetting the timer is a security issue! */ @@ -132,6 +134,24 @@ private: (HttpResponse *) s, httpRequest }); + /* First of all we need to check if this socket was deleted due to upgrade */ + if (httpContextData->upgradedWebSocket) { + return httpContextData->upgradedWebSocket; + } + + /* Did we upgrade this guy? */ +// if (httpResponseData->state & HttpResponseData::HTTP_UPGRADED_TO_WEBSOCKET) { +// std::cout << "we upgraded from the handler!" << std::endl; + + +// std::cout << "We were upgraded to: " << httpContextData->upgradedWebSocket << std::endl; + +// return httpContextData->upgradedWebSocket; + + +// // here we should adopt the socket and transition to websocket parsing, returning a socket different will halt parsing +// } + /* Was the socket closed? */ if (us_internal_socket_is_closed((struct us_socket *) s)) { return nullptr; @@ -169,6 +189,23 @@ private: return nullptr; }); + // basically we need to uncork in all cases, except for nullptr + if (returnedSocket != nullptr) { + /* Timeout on uncork failure */ + auto [written, failed] = ((AsyncSocket *) returnedSocket)->uncork(); + if (failed) { + // do we have the same timeout for websockets? + ((AsyncSocket *) s)->timeout(HTTP_IDLE_TIMEOUT_S); + } + + return (SOCKET_TYPE *) returnedSocket; + } else { + // we cannot return nullptr to the underlying stack in any case + return s; + } + + // below is never reached + /* Only uncork still valid sockets */ if (returnedSocket == s) { /* Timeout on uncork failure */ @@ -286,6 +323,13 @@ public: }); } + // this should not be public + void upgradeToWebSocket(void *newSocket) { + HttpContextData *httpContextData = getSocketContextData(); + + httpContextData->upgradedWebSocket = newSocket; + } + /* Listen to port using this HttpContext */ us_listen_socket *listen(const char *host, int port, int options) { return static_dispatch(us_ssl_socket_context_listen, us_socket_context_listen)(getSocketContext(), host, port, options, sizeof(HttpResponseData)); diff --git a/src/HttpContextData.h b/src/HttpContextData.h index db361e7..9011512 100644 --- a/src/HttpContextData.h +++ b/src/HttpContextData.h @@ -28,6 +28,7 @@ struct HttpRequest; template struct HttpContextData { template friend struct HttpContext; + template friend struct HttpResponse; private: struct RouterData { HttpResponse *httpResponse; @@ -35,6 +36,7 @@ private: }; HttpRouter router; + void *upgradedWebSocket; }; } diff --git a/src/HttpResponse.h b/src/HttpResponse.h index 9c7f185..754b28b 100644 --- a/src/HttpResponse.h +++ b/src/HttpResponse.h @@ -21,6 +21,7 @@ #include "AsyncSocket.h" #include "HttpResponseData.h" +#include "HttpContextData.h" #include "Utilities.h" /* todo: tryWrite is missing currently, only send smaller segments with write */ @@ -131,6 +132,47 @@ private: public: + // this should probably not be public + bool upgradeToWebSocket(void *newSocket) { + + HttpResponseData *httpResponseData = getHttpResponseData(); + + + // this flag is not really needed to keep state of, could be per http content state + httpResponseData->state |= HttpResponseData::HTTP_UPGRADED_TO_WEBSOCKET; + + // also set pointer to the websocketcontext? + + // you can just check if the context of the socket changed? buy youi don't know the socket ptr! + + HttpContextData *httpContextData = (HttpContextData *) Super::static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)( + + Super::static_dispatch(us_ssl_socket_get_context, us_socket_get_context)((typename Super::SOCKET_TYPE *) this) + + ); + + httpContextData->upgradedWebSocket = newSocket; + + + /*SOCKET_CONTEXT_TYPE *getSocketContext() { + return (SOCKET_CONTEXT_TYPE *) this; + } + + static SOCKET_CONTEXT_TYPE *getSocketContext(SOCKET_TYPE *s) { + return (SOCKET_CONTEXT_TYPE *) static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s); + } + + HttpContextData *getSocketContextData() { + return (HttpContextData *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(getSocketContext()); + } + + static HttpContextData *getSocketContextDataS(SOCKET_TYPE *s) { + return (HttpContextData *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(getSocketContext(s)); + }*/ + + //return (HttpContextData *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(getSocketContext(s)); + } + /* Immediately terminate this Http response */ using Super::close; diff --git a/src/HttpResponseData.h b/src/HttpResponseData.h index 2c6b283..026c8d0 100644 --- a/src/HttpResponseData.h +++ b/src/HttpResponseData.h @@ -35,7 +35,7 @@ private: HTTP_STATUS_CALLED = 1, // used HTTP_WRITE_CALLED = 2, // used HTTP_END_CALLED = 4, // used - HTTP_PAUSED_STREAM_OUT = 8, // not used + HTTP_UPGRADED_TO_WEBSOCKET = 8, // used HTTP_ENDED_STREAM_OUT = 16 // not used }; diff --git a/src/WebSocketContext.h b/src/WebSocketContext.h new file mode 100644 index 0000000..63b762a --- /dev/null +++ b/src/WebSocketContext.h @@ -0,0 +1,104 @@ +#ifndef WEBSOCKETCONTEXT_H +#define WEBSOCKETCONTEXT_H + +#include "StaticDispatch.h" +#include "WebSocketContextData.h" + +// the context depend on the PARSER but not the formatter! +#include "websocket/WebSocketProtocol.h" + +namespace uWS { + +template +struct WebSocketContext : StaticDispatch { +private: + using SOCKET_CONTEXT_TYPE = typename StaticDispatch::SOCKET_CONTEXT_TYPE; + using SOCKET_TYPE = typename StaticDispatch::SOCKET_TYPE; + using StaticDispatch::static_dispatch; + WebSocketContext() = delete; + + SOCKET_CONTEXT_TYPE *getSocketContext() { + return (SOCKET_CONTEXT_TYPE *) this; + } + + WebSocketContext *init() { + + /* I guess open is never called */ + + /* Handle socket disconnections */ + static_dispatch(us_ssl_socket_context_on_close, us_socket_context_on_close)(getSocketContext(), [](auto *s) { + + std::cout << "close!" << std::endl; + + return s; + }); + + /* Handle HTTP data streams */ + static_dispatch(us_ssl_socket_context_on_data, us_socket_context_on_data)(getSocketContext(), [](auto *s, char *data, int length) { + + + // the socket is a websocket parser just like an http socket is an http parser + + std::cout << "data: " << std::endl; + + return s; + }); + + /* Handle HTTP write out (note: SSL_read may trigger this spuriously, the app need to handle spurious calls) */ + static_dispatch(us_ssl_socket_context_on_writable, us_socket_context_on_writable)(getSocketContext(), [](auto *s) { + + std::cout << "websocket writable" << std::endl; + + return s; + }); + + /* Handle FIN, HTTP does not support half-closed sockets, so simply close */ + static_dispatch(us_ssl_socket_context_on_end, us_socket_context_on_end)(getSocketContext(), [](auto *s) { + + std::cout << "websopcket fin" << std::endl; + + /* We do not care for half closed sockets */ + //AsyncSocket *asyncSocket = (AsyncSocket *) s; + //return asyncSocket->close(); + + return s; + }); + + /* Handle socket timeouts, simply close them so to not confuse client with FIN */ + static_dispatch(us_ssl_socket_context_on_timeout, us_socket_context_on_timeout)(getSocketContext(), [](auto *s) { + + std::cout << "websocket timeout" << std::endl; + + /* Force close rather than gracefully shutdown and risk confusing the client with a complete download */ + //AsyncSocket *asyncSocket = (AsyncSocket *) s; + //return asyncSocket->close(); + + + return s; + }); + + return this; + } + +public: + + // we do not need SSL options as we come from adoptions + static WebSocketContext *create(Loop *loop, SOCKET_CONTEXT_TYPE *parentSocketContext) { + WebSocketContext *webSocketContext; + + // todo: sizeof + webSocketContext = (WebSocketContext *) static_dispatch(us_create_child_ssl_socket_context, us_create_child_socket_context)(parentSocketContext, 15); + if (!webSocketContext) { + return nullptr; + } + + /* Init socket context data */ + new ((WebSocketContextData *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)((SOCKET_CONTEXT_TYPE *) webSocketContext)) WebSocketContextData(); + return webSocketContext->init(); + } + +}; + +} + +#endif // WEBSOCKETCONTEXT_H diff --git a/src/WebSocketContextData.h b/src/WebSocketContextData.h new file mode 100644 index 0000000..d12fc74 --- /dev/null +++ b/src/WebSocketContextData.h @@ -0,0 +1,13 @@ +#ifndef WEBSOCKETCONTEXTDATA_H +#define WEBSOCKETCONTEXTDATA_H + +namespace uWS { + +template +struct WebSocketContextData { + +}; + +} + +#endif // WEBSOCKETCONTEXTDATA_H