Initial WebSocket hook up
This commit is contained in:
+3
-1
@@ -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
|
||||
|
||||
@@ -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(/*{
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
|
||||
#include "HttpContext.h"
|
||||
#include "HttpResponse.h"
|
||||
#include "WebSocketContext.h"
|
||||
|
||||
#include "websocket/libwshandshake.hpp"
|
||||
|
||||
namespace uWS {
|
||||
template <bool SSL>
|
||||
@@ -29,6 +32,8 @@ struct TemplatedApp {
|
||||
private:
|
||||
HttpContext<SSL> *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<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) {
|
||||
|
||||
@@ -117,6 +117,8 @@ private:
|
||||
/* Cork this socket */
|
||||
((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 * {
|
||||
/* 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<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? */
|
||||
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<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 */
|
||||
if (returnedSocket == s) {
|
||||
/* 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 */
|
||||
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>));
|
||||
|
||||
@@ -28,6 +28,7 @@ struct HttpRequest;
|
||||
template <bool SSL>
|
||||
struct HttpContextData {
|
||||
template <bool> friend struct HttpContext;
|
||||
template <bool> friend struct HttpResponse;
|
||||
private:
|
||||
struct RouterData {
|
||||
HttpResponse<SSL> *httpResponse;
|
||||
@@ -35,6 +36,7 @@ private:
|
||||
};
|
||||
|
||||
HttpRouter<RouterData> router;
|
||||
void *upgradedWebSocket;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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<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 */
|
||||
using Super::close;
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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