Initial WebSocket hook up

This commit is contained in:
Alex Hultman
2018-10-29 23:06:32 +01:00
parent ab6abaf2e6
commit 1613c5020d
9 changed files with 295 additions and 2 deletions
+3 -1
View File
@@ -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
+14
View File
@@ -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(/*{
+72
View File
@@ -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) {
+44
View File
@@ -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>));
+2
View File
@@ -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;
};
}
+42
View File
@@ -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;
+1 -1
View File
@@ -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
};
+104
View File
@@ -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
+13
View File
@@ -0,0 +1,13 @@
#ifndef WEBSOCKETCONTEXTDATA_H
#define WEBSOCKETCONTEXTDATA_H
namespace uWS {
template <bool SSL>
struct WebSocketContextData {
};
}
#endif // WEBSOCKETCONTEXTDATA_H