Make WebSockets benchmarkable
This commit is contained in:
@@ -16,8 +16,8 @@ examples:
|
||||
main:
|
||||
rm *.o
|
||||
clang -flto -O3 -c -IuSockets/src uSockets/src/*.c uSockets/src/eventing/*.c
|
||||
clang++ -flto -O3 -c -std=c++17 -Isrc -IuSockets/src main.cpp
|
||||
clang++ -flto -O3 -s *.o -o uWS_main -lssl -lcrypto -lpthread
|
||||
clang++ -flto -O3 -c -std=c++17 -Isrc -IuSockets/src misc/main.cpp
|
||||
clang++ -flto -O3 -s *.o -o Main -lssl -lcrypto -lpthread
|
||||
|
||||
# I don't have any tests yet
|
||||
tests:
|
||||
|
||||
+4
-4
@@ -15,10 +15,8 @@ SOURCES += \
|
||||
HEADERS += \
|
||||
../src/HttpRouter.h \
|
||||
../src/HttpParser.h \
|
||||
../src/websocket/libwshandshake.hpp \
|
||||
../src/websocket/WebSocketProtocol.h \
|
||||
../src/websocket/WebSocket.h \
|
||||
../src/websocket/WebSocketApp.h \
|
||||
../src/libwshandshake.hpp \
|
||||
../src/WebSocketProtocol.h \
|
||||
../src/HttpContext.h \
|
||||
../src/HttpContextData.h \
|
||||
../src/HttpResponseData.h \
|
||||
@@ -30,6 +28,8 @@ HEADERS += \
|
||||
../src/Loop.h \
|
||||
../src/App.h \
|
||||
../src/Utilities.h \
|
||||
../src/WebSocket.h \
|
||||
../src/WebSocketData.h \
|
||||
../src/WebSocketContext.h \
|
||||
../src/WebSocketContextData.h
|
||||
|
||||
|
||||
@@ -23,8 +23,9 @@
|
||||
#include "HttpContext.h"
|
||||
#include "HttpResponse.h"
|
||||
#include "WebSocketContext.h"
|
||||
#include "WebSocket.h"
|
||||
|
||||
#include "websocket/libwshandshake.hpp"
|
||||
#include "libwshandshake.hpp"
|
||||
|
||||
namespace uWS {
|
||||
template <bool SSL>
|
||||
@@ -75,31 +76,20 @@ public:
|
||||
// adopting will immediately delete the socket! we cannot rely on reading anything on it
|
||||
// rely on http context data
|
||||
|
||||
// todo: sizeof websocket
|
||||
WebSocket<SSL> *webSocket = (WebSocket<SSL> *) 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, 150);
|
||||
|
||||
//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);
|
||||
webSocket->init();
|
||||
|
||||
|
||||
|
||||
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);
|
||||
std::cout << "adopted" << std::endl;
|
||||
|
||||
httpContext->upgradeToWebSocket(
|
||||
newSocket
|
||||
webSocket
|
||||
);
|
||||
|
||||
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));*/
|
||||
|
||||
|
||||
connectHandler(webSocket, req);
|
||||
|
||||
} else {
|
||||
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
#ifndef WEBSOCKET_H
|
||||
#define WEBSOCKET_H
|
||||
|
||||
#include "WebSocketData.h"
|
||||
|
||||
namespace uWS {
|
||||
|
||||
template <bool SSL>
|
||||
struct WebSocket {
|
||||
private:
|
||||
|
||||
public:
|
||||
void init() {
|
||||
// construct us
|
||||
|
||||
new (us_socket_ext((us_socket *) this)) WebSocketData;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // WEBSOCKET_H
|
||||
|
||||
+40
-5
@@ -5,7 +5,9 @@
|
||||
#include "WebSocketContextData.h"
|
||||
|
||||
// the context depend on the PARSER but not the formatter!
|
||||
#include "websocket/WebSocketProtocol.h"
|
||||
#include "WebSocketProtocol.h"
|
||||
|
||||
#include "WebSocketData.h"
|
||||
|
||||
namespace uWS {
|
||||
|
||||
@@ -21,6 +23,38 @@ private:
|
||||
return (SOCKET_CONTEXT_TYPE *) this;
|
||||
}
|
||||
|
||||
// I don't even.. merge this with the context itself!
|
||||
template <bool isServer>
|
||||
struct WebSocketProtcolImplementation {
|
||||
static bool setCompressed(uWS::WebSocketState<isServer> *wState) {
|
||||
std::cout << "set compressed" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void forceClose(uWS::WebSocketState<isServer> *wState) {
|
||||
std::cout << "force close" << std::endl;
|
||||
}
|
||||
|
||||
static bool handleFragment(char *data, size_t length, unsigned int remainingBytes, int opCode, bool fin, uWS::WebSocketState<isServer> *webSocketState, void *s) {
|
||||
|
||||
// this path should use AsyncSocket with cork and everything
|
||||
|
||||
// format the response
|
||||
char buf[100];
|
||||
int writeLength = WebSocketProtocol<isServer, WebSocketProtcolImplementation<isServer>>::formatMessage(buf, data, length, (uWS::OpCode) opCode, length, false);
|
||||
us_socket_write((SOCKET_TYPE *) s, buf, writeLength, false);
|
||||
|
||||
// why does it not do anything immediately on true?
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
static bool refusePayloadLength(uint64_t length, uWS::WebSocketState<isServer> *wState) {
|
||||
//std::cout << "refusepayloadlength" << std::endl;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
WebSocketContext<SSL> *init() {
|
||||
|
||||
/* I guess open is never called */
|
||||
@@ -36,10 +70,11 @@ private:
|
||||
/* Handle HTTP data streams */
|
||||
static_dispatch(us_ssl_socket_context_on_data, us_socket_context_on_data)(getSocketContext(), [](auto *s, char *data, int length) {
|
||||
|
||||
// get the data
|
||||
WebSocketData *wsState = (WebSocketData *) us_socket_ext(s);
|
||||
|
||||
// the socket is a websocket parser just like an http socket is an http parser
|
||||
|
||||
std::cout << "data: " << std::endl;
|
||||
// this parser requires almost no time -> 215k req/sec of 215k possible
|
||||
uWS::WebSocketProtocol<true, WebSocketProtcolImplementation<true>>::consume(data, length, wsState, s);
|
||||
|
||||
return s;
|
||||
});
|
||||
@@ -87,7 +122,7 @@ public:
|
||||
WebSocketContext *webSocketContext;
|
||||
|
||||
// todo: sizeof
|
||||
webSocketContext = (WebSocketContext *) static_dispatch(us_create_child_ssl_socket_context, us_create_child_socket_context)(parentSocketContext, 15);
|
||||
webSocketContext = (WebSocketContext *) static_dispatch(us_create_child_ssl_socket_context, us_create_child_socket_context)(parentSocketContext, 100);
|
||||
if (!webSocketContext) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
#ifndef WEBSOCKETDATA_H
|
||||
#define WEBSOCKETDATA_H
|
||||
|
||||
#include "WebSocketProtocol.h"
|
||||
|
||||
namespace uWS {
|
||||
|
||||
struct WebSocketData : WebSocketState<true> {
|
||||
private:
|
||||
|
||||
public:
|
||||
WebSocketData() : WebSocketState<true>() {
|
||||
std::cout << "init websocket data!" << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // WEBSOCKETDATA_H
|
||||
|
||||
@@ -138,7 +138,7 @@ protected:
|
||||
};
|
||||
|
||||
template <unsigned int MESSAGE_HEADER, typename T>
|
||||
static inline bool consumeMessage(T payLength, char *&src, unsigned int &length, WebSocketState<isServer> *wState) {
|
||||
static inline bool consumeMessage(T payLength, char *&src, unsigned int &length, WebSocketState<isServer> *wState, void *user) {
|
||||
if (getOpCode(src)) {
|
||||
if (wState->state.opStack == 1 || (!wState->state.lastFin && getOpCode(src) < 2)) {
|
||||
Impl::forceClose(wState);
|
||||
@@ -159,11 +159,11 @@ protected:
|
||||
if (payLength + MESSAGE_HEADER <= length) {
|
||||
if (isServer) {
|
||||
unmaskImpreciseCopyMask(src + MESSAGE_HEADER - 4, src + MESSAGE_HEADER, src + MESSAGE_HEADER - 4, (unsigned int) payLength);
|
||||
if (Impl::handleFragment(src + MESSAGE_HEADER - 4, payLength, 0, wState->state.opCode[wState->state.opStack], isFin(src), wState)) {
|
||||
if (Impl::handleFragment(src + MESSAGE_HEADER - 4, payLength, 0, wState->state.opCode[wState->state.opStack], isFin(src), wState, user)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (Impl::handleFragment(src + MESSAGE_HEADER, payLength, 0, wState->state.opCode[wState->state.opStack], isFin(src), wState)) {
|
||||
if (Impl::handleFragment(src + MESSAGE_HEADER, payLength, 0, wState->state.opCode[wState->state.opStack], isFin(src), wState, user)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -188,12 +188,12 @@ protected:
|
||||
} else {
|
||||
src += MESSAGE_HEADER;
|
||||
}
|
||||
Impl::handleFragment(src, length - MESSAGE_HEADER, wState->remainingBytes, wState->state.opCode[wState->state.opStack], fin, wState);
|
||||
Impl::handleFragment(src, length - MESSAGE_HEADER, wState->remainingBytes, wState->state.opCode[wState->state.opStack], fin, wState, user);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool consumeContinuation(char *&src, unsigned int &length, WebSocketState<isServer> *wState) {
|
||||
static inline bool consumeContinuation(char *&src, unsigned int &length, WebSocketState<isServer> *wState, void *user) {
|
||||
if (wState->remainingBytes <= length) {
|
||||
if (isServer) {
|
||||
int n = wState->remainingBytes >> 2;
|
||||
@@ -203,7 +203,7 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
if (Impl::handleFragment(src, wState->remainingBytes, 0, wState->state.opCode[wState->state.opStack], wState->state.lastFin, wState)) {
|
||||
if (Impl::handleFragment(src, wState->remainingBytes, 0, wState->state.opCode[wState->state.opStack], wState->state.lastFin, wState, user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ protected:
|
||||
}
|
||||
|
||||
wState->remainingBytes -= length;
|
||||
if (Impl::handleFragment(src, length, wState->remainingBytes, wState->state.opCode[wState->state.opStack], wState->state.lastFin, wState)) {
|
||||
if (Impl::handleFragment(src, length, wState->remainingBytes, wState->state.opCode[wState->state.opStack], wState->state.lastFin, wState, user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -357,7 +357,7 @@ public:
|
||||
return messageLength;
|
||||
}
|
||||
|
||||
static inline void consume(char *src, unsigned int length, WebSocketState<isServer> *wState) {
|
||||
static inline void consume(char *src, unsigned int length, WebSocketState<isServer> *wState, void *user) {
|
||||
if (wState->state.spillLength) {
|
||||
src -= wState->state.spillLength;
|
||||
length += wState->state.spillLength;
|
||||
@@ -375,18 +375,18 @@ public:
|
||||
}
|
||||
|
||||
if (payloadLength(src) < 126) {
|
||||
if (consumeMessage<SHORT_MESSAGE_HEADER, uint8_t>(payloadLength(src), src, length, wState)) {
|
||||
if (consumeMessage<SHORT_MESSAGE_HEADER, uint8_t>(payloadLength(src), src, length, wState, user)) {
|
||||
return;
|
||||
}
|
||||
} else if (payloadLength(src) == 126) {
|
||||
if (length < MEDIUM_MESSAGE_HEADER) {
|
||||
break;
|
||||
} else if(consumeMessage<MEDIUM_MESSAGE_HEADER, uint16_t>(ntohs(*(uint16_t *) &src[2]), src, length, wState)) {
|
||||
} else if(consumeMessage<MEDIUM_MESSAGE_HEADER, uint16_t>(ntohs(*(uint16_t *) &src[2]), src, length, wState, user)) {
|
||||
return;
|
||||
}
|
||||
} else if (length < LONG_MESSAGE_HEADER) {
|
||||
break;
|
||||
} else if (consumeMessage<LONG_MESSAGE_HEADER, uint64_t>(be64toh(*(uint64_t *) &src[2]), src, length, wState)) {
|
||||
} else if (consumeMessage<LONG_MESSAGE_HEADER, uint64_t>(be64toh(*(uint64_t *) &src[2]), src, length, wState, user)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -394,7 +394,7 @@ public:
|
||||
memcpy(wState->state.spill, src, length);
|
||||
wState->state.spillLength = length;
|
||||
}
|
||||
} else if (consumeContinuation(src, length, wState)) {
|
||||
} else if (consumeContinuation(src, length, wState, user)) {
|
||||
goto parseNext;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Alex Hultman and contributors.
|
||||
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef WEBSOCKET_H
|
||||
#define WEBSOCKET_H
|
||||
|
||||
#include "libusockets.h"
|
||||
#include "Socket.h"
|
||||
#include "WebSocketProtocol.h"
|
||||
|
||||
// client or server?
|
||||
template <bool SSL, bool isServer>
|
||||
struct WebSocket : public Socket<SSL> {
|
||||
|
||||
// this needs to hold
|
||||
struct Data : uWS::WebSocketState<isServer> {
|
||||
|
||||
};
|
||||
|
||||
static bool setCompressed(uWS::WebSocketState<isServer> *wState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void forceClose(uWS::WebSocketState<isServer> *wState) {
|
||||
|
||||
}
|
||||
|
||||
static bool handleFragment(char *data, size_t length, unsigned int remainingBytes, int opCode, bool fin, uWS::WebSocketState<isServer> *webSocketState) {
|
||||
|
||||
std::cout << std::string_view(data, length) << std::endl;
|
||||
|
||||
//Data *webSocketData = (Data *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) this);
|
||||
|
||||
|
||||
//Socket<SSL>::getSocketContextExt();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
static bool refusePayloadLength(uint64_t length, uWS::WebSocketState<isServer> *wState) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//why is this here? events are handled and emitted from the app, the app depends on websocket, not two way deps!
|
||||
void onData(char *data, int length) {
|
||||
|
||||
Data *webSocketData = (Data *) Socket<SSL>::static_dispatch(us_ssl_socket_ext, us_socket_ext)((typename Socket<SSL>::SOCKET_TYPE *) this);
|
||||
|
||||
uWS::WebSocketProtocol<isServer, WebSocket<SSL, isServer>>::consume(data, length, webSocketData);
|
||||
}
|
||||
|
||||
WebSocket() = delete;
|
||||
};
|
||||
|
||||
#endif // WEBSOCKET_H
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Alex Hultman and contributors.
|
||||
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifdef WEBSOCKETAPP_H
|
||||
#define WEBSOCKETAPP_H
|
||||
|
||||
#include "http/HttpApp.h"
|
||||
#include <vector>
|
||||
|
||||
// basically you have one of this for server, one for client!?
|
||||
template <bool SSL>
|
||||
struct WebSocketApp : HttpApp<SSL, WebSocketApp<SSL>> {
|
||||
|
||||
// usings
|
||||
using HttpApp<SSL, WebSocketApp<SSL>>::static_dispatch;
|
||||
typedef typename HttpApp<SSL, WebSocketApp<SSL>>::SOCKET_CONTEXT_TYPE SOCKET_CONTEXT_TYPE;
|
||||
typedef typename HttpApp<SSL, WebSocketApp<SSL>>::SOCKET_TYPE SOCKET_TYPE;
|
||||
|
||||
// constructor
|
||||
WebSocketApp(SOCKET_CONTEXT_TYPE *httpServerContext) : HttpApp<SSL, WebSocketApp<SSL>>(httpServerContext) {
|
||||
|
||||
}
|
||||
|
||||
// per-context "WebSocketApp" data
|
||||
template <bool isServer>
|
||||
struct WebSocketServerContextData {
|
||||
|
||||
WebSocketServerContextData() {
|
||||
|
||||
}
|
||||
|
||||
std::function<void(WebSocket<SSL, isServer> *, std::string_view)> onMessage;
|
||||
};
|
||||
|
||||
// all server contexts created with below functions
|
||||
std::vector<SOCKET_CONTEXT_TYPE *> webSocketServerContexts;
|
||||
bool lastContextIsServer;
|
||||
|
||||
// register a new (server) protocol
|
||||
template <class UserData>
|
||||
WebSocketApp &onWebSocket(std::string pattern, std::function<void(HttpSocket<SSL> *, HttpRequest *, std::vector<std::string_view> *)> handler) {
|
||||
|
||||
// we are going to push a server context
|
||||
lastContextIsServer = true;
|
||||
|
||||
// create a new websocket child context
|
||||
SOCKET_CONTEXT_TYPE *webSocketServerContext = static_dispatch(us_create_child_ssl_socket_context, us_create_child_socket_context)(HttpApp<SSL, WebSocketApp<SSL>>::httpServerContext, sizeof(WebSocketServerContextData<true>));
|
||||
new ((WebSocketServerContextData<true> *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(webSocketServerContext)) WebSocketServerContextData<true>();
|
||||
WebSocketApp<SSL>::webSocketServerContexts.push_back(webSocketServerContext);
|
||||
|
||||
// add the behavior of it
|
||||
static_dispatch(us_ssl_socket_context_on_data, us_socket_context_on_data)(webSocketServerContext, [](auto *s, char *data, int length) {
|
||||
//WebSocketServerContextData<true> *webSocketServerContextData = (WebSocketServerContextData<true> *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s));
|
||||
|
||||
((WebSocket<SSL, true> *) s)->onData(data, length/*, webSocketServerContextData->onMessage*/);
|
||||
|
||||
return s;
|
||||
});
|
||||
|
||||
// todo: GET should probably be get since the parser only leaves lower case
|
||||
HttpApp<SSL, WebSocketApp<SSL>>::data->r.add("GET", pattern.c_str(), [webSocketServerContext, handler](typename HttpApp<SSL, WebSocketApp<SSL>>::Data::UserData *user, auto *args) {
|
||||
|
||||
std::string_view secWebSocketKey = user->httpRequest->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);
|
||||
|
||||
user->httpSocket->writeStatus("101 Switching Protocols")
|
||||
->writeHeader("Upgrade", "websocket")
|
||||
->writeHeader("Connection", "Upgrade")
|
||||
->writeHeader("Sec-WebSocket-Accept", secWebSocketAccept)
|
||||
->end("");
|
||||
|
||||
// todo: transform the socket into a websocket and hand it over
|
||||
static_dispatch(us_ssl_socket_context_adopt_socket, us_socket_context_adopt_socket)(webSocketServerContext, (SOCKET_TYPE *) user->httpSocket, sizeof(typename WebSocket<SSL, true>::Data) + sizeof(UserData));
|
||||
|
||||
// init the websocket data
|
||||
|
||||
handler(user->httpSocket, user->httpRequest, args);
|
||||
} else {
|
||||
|
||||
// maybe pass this one to a HTTP handler on the websocket
|
||||
|
||||
// note: this calls the http close handler inline
|
||||
user->httpSocket->close();
|
||||
}
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// this function does in fact determine whether we are client or not based on the websocket type passed!
|
||||
template <bool isServer>
|
||||
WebSocketApp &onMessage(std::function<void(WebSocket<SSL, isServer> *, std::string_view)> handler) {
|
||||
|
||||
// pop last context on the stack
|
||||
SOCKET_CONTEXT_TYPE *context = lastContextIsServer ? webSocketServerContexts.back() : nullptr;
|
||||
|
||||
// get its data
|
||||
if (lastContextIsServer) {
|
||||
WebSocketServerContextData<true> *data = (WebSocketServerContextData<true> *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(context);
|
||||
|
||||
data->onMessage = handler;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
WebSocketApp &onClose(std::function<void()>) {
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // WEBSOCKETAPP_H
|
||||
Reference in New Issue
Block a user