Initial WebSocket hook up
This commit is contained in:
+3
-1
@@ -29,7 +29,9 @@ HEADERS += \
|
|||||||
../src/AsyncSocketData.h \
|
../src/AsyncSocketData.h \
|
||||||
../src/Loop.h \
|
../src/Loop.h \
|
||||||
../src/App.h \
|
../src/App.h \
|
||||||
../src/Utilities.h
|
../src/Utilities.h \
|
||||||
|
../src/WebSocketContext.h \
|
||||||
|
../src/WebSocketContextData.h
|
||||||
|
|
||||||
INCLUDEPATH += ../uSockets/src ../src
|
INCLUDEPATH += ../uSockets/src ../src
|
||||||
QMAKE_CXXFLAGS += -fsanitize=address
|
QMAKE_CXXFLAGS += -fsanitize=address
|
||||||
|
|||||||
@@ -5,6 +5,20 @@
|
|||||||
|
|
||||||
int main(int argc, char **argv) {
|
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");
|
AsyncFileStreamer *asyncFileStreamer = new AsyncFileStreamer("/home/alexhultman/v0.15/public");
|
||||||
|
|
||||||
uWS::/*SSL*/App(/*{
|
uWS::/*SSL*/App(/*{
|
||||||
|
|||||||
@@ -22,6 +22,9 @@
|
|||||||
|
|
||||||
#include "HttpContext.h"
|
#include "HttpContext.h"
|
||||||
#include "HttpResponse.h"
|
#include "HttpResponse.h"
|
||||||
|
#include "WebSocketContext.h"
|
||||||
|
|
||||||
|
#include "websocket/libwshandshake.hpp"
|
||||||
|
|
||||||
namespace uWS {
|
namespace uWS {
|
||||||
template <bool SSL>
|
template <bool SSL>
|
||||||
@@ -29,6 +32,8 @@ struct TemplatedApp {
|
|||||||
private:
|
private:
|
||||||
HttpContext<SSL> *httpContext;
|
HttpContext<SSL> *httpContext;
|
||||||
|
|
||||||
|
// the app does not own a websocket context, it is created on .ws(...) calls on demand!
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
~TemplatedApp() {
|
~TemplatedApp() {
|
||||||
@@ -41,6 +46,73 @@ public:
|
|||||||
|
|
||||||
TemplatedApp(us_ssl_socket_context_options sslOptions = {}) {
|
TemplatedApp(us_ssl_socket_context_options sslOptions = {}) {
|
||||||
httpContext = uWS::HttpContext<SSL>::create(uWS::Loop::defaultLoop(), &sslOptions);
|
httpContext = uWS::HttpContext<SSL>::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<void(void *, HttpRequest *)> connectHandler) {
|
||||||
|
// init the websocket context here!
|
||||||
|
uWS::WebSocketContext<SSL> *webSocketContext = uWS::WebSocketContext<SSL>::create(uWS::Loop::defaultLoop(), (typename StaticDispatch<SSL>::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<SSL>::SOCKET_CONTEXT_TYPE *socketContext = (typename StaticDispatch<SSL>::SOCKET_CONTEXT_TYPE *) StaticDispatch<SSL>::static_dispatch(us_ssl_socket_get_context, us_socket_get_context)((typename StaticDispatch<SSL>::SOCKET_TYPE *) res);
|
||||||
|
//StaticDispatch<SSL>::static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(socketContext);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void *newSocket = StaticDispatch<SSL>::static_dispatch(us_ssl_socket_context_adopt_socket, us_socket_context_adopt_socket)(
|
||||||
|
(typename StaticDispatch<SSL>::SOCKET_CONTEXT_TYPE *) webSocketContext, (typename StaticDispatch<SSL>::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<SSL>::static_dispatch(us_ssl_socket_context_adopt_socket, us_socket_context_adopt_socket)(
|
||||||
|
(typename StaticDispatch<SSL>::SOCKET_CONTEXT_TYPE *) webSocketContext, (typename StaticDispatch<SSL>::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<void(HttpResponse<SSL> *, HttpRequest *)> handler) {
|
TemplatedApp &get(std::string pattern, std::function<void(HttpResponse<SSL> *, HttpRequest *)> handler) {
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ private:
|
|||||||
/* Cork this socket */
|
/* Cork this socket */
|
||||||
((AsyncSocket<SSL> *) s)->cork();
|
((AsyncSocket<SSL> *) 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 * {
|
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 */
|
/* 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! */
|
/* Warning: if we are in shutdown state, resetting the timer is a security issue! */
|
||||||
@@ -132,6 +134,24 @@ private:
|
|||||||
(HttpResponse<SSL> *) s, httpRequest
|
(HttpResponse<SSL> *) 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<SSL>::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? */
|
/* Was the socket closed? */
|
||||||
if (us_internal_socket_is_closed((struct us_socket *) s)) {
|
if (us_internal_socket_is_closed((struct us_socket *) s)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -169,6 +189,23 @@ private:
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// basically we need to uncork in all cases, except for nullptr
|
||||||
|
if (returnedSocket != nullptr) {
|
||||||
|
/* Timeout on uncork failure */
|
||||||
|
auto [written, failed] = ((AsyncSocket<SSL> *) returnedSocket)->uncork();
|
||||||
|
if (failed) {
|
||||||
|
// do we have the same timeout for websockets?
|
||||||
|
((AsyncSocket<SSL> *) 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 */
|
/* Only uncork still valid sockets */
|
||||||
if (returnedSocket == s) {
|
if (returnedSocket == s) {
|
||||||
/* Timeout on uncork failure */
|
/* Timeout on uncork failure */
|
||||||
@@ -286,6 +323,13 @@ public:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this should not be public
|
||||||
|
void upgradeToWebSocket(void *newSocket) {
|
||||||
|
HttpContextData<SSL> *httpContextData = getSocketContextData();
|
||||||
|
|
||||||
|
httpContextData->upgradedWebSocket = newSocket;
|
||||||
|
}
|
||||||
|
|
||||||
/* Listen to port using this HttpContext */
|
/* Listen to port using this HttpContext */
|
||||||
us_listen_socket *listen(const char *host, int port, int options) {
|
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<SSL>));
|
return static_dispatch(us_ssl_socket_context_listen, us_socket_context_listen)(getSocketContext(), host, port, options, sizeof(HttpResponseData<SSL>));
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ struct HttpRequest;
|
|||||||
template <bool SSL>
|
template <bool SSL>
|
||||||
struct HttpContextData {
|
struct HttpContextData {
|
||||||
template <bool> friend struct HttpContext;
|
template <bool> friend struct HttpContext;
|
||||||
|
template <bool> friend struct HttpResponse;
|
||||||
private:
|
private:
|
||||||
struct RouterData {
|
struct RouterData {
|
||||||
HttpResponse<SSL> *httpResponse;
|
HttpResponse<SSL> *httpResponse;
|
||||||
@@ -35,6 +36,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
HttpRouter<RouterData> router;
|
HttpRouter<RouterData> router;
|
||||||
|
void *upgradedWebSocket;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "AsyncSocket.h"
|
#include "AsyncSocket.h"
|
||||||
#include "HttpResponseData.h"
|
#include "HttpResponseData.h"
|
||||||
|
#include "HttpContextData.h"
|
||||||
#include "Utilities.h"
|
#include "Utilities.h"
|
||||||
|
|
||||||
/* todo: tryWrite is missing currently, only send smaller segments with write */
|
/* todo: tryWrite is missing currently, only send smaller segments with write */
|
||||||
@@ -131,6 +132,47 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
// this should probably not be public
|
||||||
|
bool upgradeToWebSocket(void *newSocket) {
|
||||||
|
|
||||||
|
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
|
||||||
|
|
||||||
|
|
||||||
|
// this flag is not really needed to keep state of, could be per http content state
|
||||||
|
httpResponseData->state |= HttpResponseData<SSL>::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<SSL> *httpContextData = (HttpContextData<SSL> *) 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<SSL> *getSocketContextData() {
|
||||||
|
return (HttpContextData<SSL> *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(getSocketContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
static HttpContextData<SSL> *getSocketContextDataS(SOCKET_TYPE *s) {
|
||||||
|
return (HttpContextData<SSL> *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(getSocketContext(s));
|
||||||
|
}*/
|
||||||
|
|
||||||
|
//return (HttpContextData<SSL> *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(getSocketContext(s));
|
||||||
|
}
|
||||||
|
|
||||||
/* Immediately terminate this Http response */
|
/* Immediately terminate this Http response */
|
||||||
using Super::close;
|
using Super::close;
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ private:
|
|||||||
HTTP_STATUS_CALLED = 1, // used
|
HTTP_STATUS_CALLED = 1, // used
|
||||||
HTTP_WRITE_CALLED = 2, // used
|
HTTP_WRITE_CALLED = 2, // used
|
||||||
HTTP_END_CALLED = 4, // 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
|
HTTP_ENDED_STREAM_OUT = 16 // not used
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 <bool SSL>
|
||||||
|
struct WebSocketContext : StaticDispatch<SSL> {
|
||||||
|
private:
|
||||||
|
using SOCKET_CONTEXT_TYPE = typename StaticDispatch<SSL>::SOCKET_CONTEXT_TYPE;
|
||||||
|
using SOCKET_TYPE = typename StaticDispatch<SSL>::SOCKET_TYPE;
|
||||||
|
using StaticDispatch<SSL>::static_dispatch;
|
||||||
|
WebSocketContext() = delete;
|
||||||
|
|
||||||
|
SOCKET_CONTEXT_TYPE *getSocketContext() {
|
||||||
|
return (SOCKET_CONTEXT_TYPE *) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketContext<SSL> *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<SSL> *asyncSocket = (AsyncSocket<SSL> *) 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<SSL> *asyncSocket = (AsyncSocket<SSL> *) 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<SSL> *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)((SOCKET_CONTEXT_TYPE *) webSocketContext)) WebSocketContextData<SSL>();
|
||||||
|
return webSocketContext->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // WEBSOCKETCONTEXT_H
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef WEBSOCKETCONTEXTDATA_H
|
||||||
|
#define WEBSOCKETCONTEXTDATA_H
|
||||||
|
|
||||||
|
namespace uWS {
|
||||||
|
|
||||||
|
template <bool SSL>
|
||||||
|
struct WebSocketContextData {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // WEBSOCKETCONTEXTDATA_H
|
||||||
Reference in New Issue
Block a user