2020-05-14 22:23:45 +00:00
|
|
|
#ifndef WebSocketTxRx_h
|
|
|
|
#define WebSocketTxRx_h
|
|
|
|
|
|
|
|
#include <StatefulService.h>
|
|
|
|
#include <ESPAsyncWebServer.h>
|
2020-08-22 12:30:24 +00:00
|
|
|
#include <SecurityManager.h>
|
2020-05-14 22:23:45 +00:00
|
|
|
|
|
|
|
#define WEB_SOCKET_CLIENT_ID_MSG_SIZE 128
|
|
|
|
|
|
|
|
#define WEB_SOCKET_ORIGIN "websocket"
|
|
|
|
#define WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX "websocket:"
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
class WebSocketConnector {
|
|
|
|
protected:
|
|
|
|
StatefulService<T>* _statefulService;
|
|
|
|
AsyncWebServer* _server;
|
|
|
|
AsyncWebSocket _webSocket;
|
2020-05-22 18:26:12 +00:00
|
|
|
size_t _bufferSize;
|
2020-05-14 22:23:45 +00:00
|
|
|
|
|
|
|
WebSocketConnector(StatefulService<T>* statefulService,
|
|
|
|
AsyncWebServer* server,
|
2020-11-21 23:40:31 +00:00
|
|
|
const char* webSocketPath,
|
2020-05-14 22:23:45 +00:00
|
|
|
SecurityManager* securityManager,
|
2020-05-22 18:26:12 +00:00
|
|
|
AuthenticationPredicate authenticationPredicate,
|
|
|
|
size_t bufferSize) :
|
|
|
|
_statefulService(statefulService), _server(server), _webSocket(webSocketPath), _bufferSize(bufferSize) {
|
2020-05-14 22:23:45 +00:00
|
|
|
_webSocket.setFilter(securityManager->filterRequest(authenticationPredicate));
|
|
|
|
_webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent,
|
|
|
|
this,
|
|
|
|
std::placeholders::_1,
|
|
|
|
std::placeholders::_2,
|
|
|
|
std::placeholders::_3,
|
|
|
|
std::placeholders::_4,
|
|
|
|
std::placeholders::_5,
|
|
|
|
std::placeholders::_6));
|
|
|
|
_server->addHandler(&_webSocket);
|
|
|
|
_server->on(webSocketPath, HTTP_GET, std::bind(&WebSocketConnector::forbidden, this, std::placeholders::_1));
|
|
|
|
}
|
|
|
|
|
2020-05-22 18:26:12 +00:00
|
|
|
WebSocketConnector(StatefulService<T>* statefulService,
|
|
|
|
AsyncWebServer* server,
|
2020-11-21 23:40:31 +00:00
|
|
|
const char* webSocketPath,
|
2020-05-22 18:26:12 +00:00
|
|
|
size_t bufferSize) :
|
|
|
|
_statefulService(statefulService), _server(server), _webSocket(webSocketPath), _bufferSize(bufferSize) {
|
2020-05-14 22:23:45 +00:00
|
|
|
_webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent,
|
|
|
|
this,
|
|
|
|
std::placeholders::_1,
|
|
|
|
std::placeholders::_2,
|
|
|
|
std::placeholders::_3,
|
|
|
|
std::placeholders::_4,
|
|
|
|
std::placeholders::_5,
|
|
|
|
std::placeholders::_6));
|
|
|
|
_server->addHandler(&_webSocket);
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void onWSEvent(AsyncWebSocket* server,
|
|
|
|
AsyncWebSocketClient* client,
|
|
|
|
AwsEventType type,
|
|
|
|
void* arg,
|
|
|
|
uint8_t* data,
|
|
|
|
size_t len) = 0;
|
|
|
|
|
|
|
|
String clientId(AsyncWebSocketClient* client) {
|
|
|
|
return WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX + String(client->id());
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void forbidden(AsyncWebServerRequest* request) {
|
|
|
|
request->send(403);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
class WebSocketTx : virtual public WebSocketConnector<T> {
|
|
|
|
public:
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketTx(JsonStateReader<T> stateReader,
|
2020-05-14 22:23:45 +00:00
|
|
|
StatefulService<T>* statefulService,
|
|
|
|
AsyncWebServer* server,
|
2020-11-21 23:40:31 +00:00
|
|
|
const char* webSocketPath,
|
2020-05-14 22:23:45 +00:00
|
|
|
SecurityManager* securityManager,
|
2020-05-22 18:26:12 +00:00
|
|
|
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
|
|
|
|
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
|
|
|
|
WebSocketConnector<T>(statefulService,
|
|
|
|
server,
|
|
|
|
webSocketPath,
|
|
|
|
securityManager,
|
|
|
|
authenticationPredicate,
|
|
|
|
bufferSize),
|
2020-05-29 19:18:43 +00:00
|
|
|
_stateReader(stateReader) {
|
2020-05-21 07:42:21 +00:00
|
|
|
WebSocketConnector<T>::_statefulService->addUpdateHandler(
|
|
|
|
[&](const String& originId) { transmitData(nullptr, originId); }, false);
|
2020-05-14 22:23:45 +00:00
|
|
|
}
|
|
|
|
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketTx(JsonStateReader<T> stateReader,
|
2020-05-14 22:23:45 +00:00
|
|
|
StatefulService<T>* statefulService,
|
|
|
|
AsyncWebServer* server,
|
2020-11-21 23:40:31 +00:00
|
|
|
const char* webSocketPath,
|
2020-05-22 18:26:12 +00:00
|
|
|
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize), _stateReader(stateReader) {
|
|
|
|
WebSocketConnector<T>::_statefulService->addUpdateHandler(
|
|
|
|
[&](const String& originId) { transmitData(nullptr, originId); }, false);
|
2020-05-14 22:23:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
virtual void onWSEvent(AsyncWebSocket* server,
|
|
|
|
AsyncWebSocketClient* client,
|
|
|
|
AwsEventType type,
|
|
|
|
void* arg,
|
|
|
|
uint8_t* data,
|
|
|
|
size_t len) {
|
|
|
|
if (type == WS_EVT_CONNECT) {
|
|
|
|
// when a client connects, we transmit it's id and the current payload
|
|
|
|
transmitId(client);
|
|
|
|
transmitData(client, WEB_SOCKET_ORIGIN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2020-05-29 19:18:43 +00:00
|
|
|
JsonStateReader<T> _stateReader;
|
2020-05-14 22:23:45 +00:00
|
|
|
|
|
|
|
void transmitId(AsyncWebSocketClient* client) {
|
|
|
|
DynamicJsonDocument jsonDocument = DynamicJsonDocument(WEB_SOCKET_CLIENT_ID_MSG_SIZE);
|
|
|
|
JsonObject root = jsonDocument.to<JsonObject>();
|
|
|
|
root["type"] = "id";
|
|
|
|
root["id"] = WebSocketConnector<T>::clientId(client);
|
|
|
|
size_t len = measureJson(jsonDocument);
|
|
|
|
AsyncWebSocketMessageBuffer* buffer = WebSocketConnector<T>::_webSocket.makeBuffer(len);
|
|
|
|
if (buffer) {
|
|
|
|
serializeJson(jsonDocument, (char*)buffer->get(), len + 1);
|
|
|
|
client->text(buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Broadcasts the payload to the destination, if provided. Otherwise broadcasts to all clients except the origin, if
|
|
|
|
* specified.
|
|
|
|
*
|
|
|
|
* Original implementation sent clients their own IDs so they could ignore updates they initiated. This approach
|
|
|
|
* simplifies the client and the server implementation but may not be sufficent for all use-cases.
|
|
|
|
*/
|
2020-05-21 07:42:21 +00:00
|
|
|
void transmitData(AsyncWebSocketClient* client, const String& originId) {
|
2020-05-22 18:26:12 +00:00
|
|
|
DynamicJsonDocument jsonDocument = DynamicJsonDocument(WebSocketConnector<T>::_bufferSize);
|
2020-05-14 22:23:45 +00:00
|
|
|
JsonObject root = jsonDocument.to<JsonObject>();
|
|
|
|
root["type"] = "payload";
|
|
|
|
root["origin_id"] = originId;
|
|
|
|
JsonObject payload = root.createNestedObject("payload");
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketConnector<T>::_statefulService->read(payload, _stateReader);
|
2020-05-14 22:23:45 +00:00
|
|
|
|
|
|
|
size_t len = measureJson(jsonDocument);
|
|
|
|
AsyncWebSocketMessageBuffer* buffer = WebSocketConnector<T>::_webSocket.makeBuffer(len);
|
|
|
|
if (buffer) {
|
|
|
|
serializeJson(jsonDocument, (char*)buffer->get(), len + 1);
|
|
|
|
if (client) {
|
|
|
|
client->text(buffer);
|
|
|
|
} else {
|
|
|
|
WebSocketConnector<T>::_webSocket.textAll(buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
class WebSocketRx : virtual public WebSocketConnector<T> {
|
|
|
|
public:
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketRx(JsonStateUpdater<T> stateUpdater,
|
2020-05-14 22:23:45 +00:00
|
|
|
StatefulService<T>* statefulService,
|
|
|
|
AsyncWebServer* server,
|
2020-11-21 23:40:31 +00:00
|
|
|
const char* webSocketPath,
|
2020-05-14 22:23:45 +00:00
|
|
|
SecurityManager* securityManager,
|
2020-05-22 18:26:12 +00:00
|
|
|
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
|
|
|
|
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
|
|
|
|
WebSocketConnector<T>(statefulService,
|
|
|
|
server,
|
|
|
|
webSocketPath,
|
|
|
|
securityManager,
|
|
|
|
authenticationPredicate,
|
|
|
|
bufferSize),
|
2020-05-29 19:18:43 +00:00
|
|
|
_stateUpdater(stateUpdater) {
|
2020-05-14 22:23:45 +00:00
|
|
|
}
|
|
|
|
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketRx(JsonStateUpdater<T> stateUpdater,
|
2020-05-14 22:23:45 +00:00
|
|
|
StatefulService<T>* statefulService,
|
|
|
|
AsyncWebServer* server,
|
2020-11-21 23:40:31 +00:00
|
|
|
const char* webSocketPath,
|
2020-05-22 18:26:12 +00:00
|
|
|
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize), _stateUpdater(stateUpdater) {
|
2020-05-14 22:23:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
virtual void onWSEvent(AsyncWebSocket* server,
|
|
|
|
AsyncWebSocketClient* client,
|
|
|
|
AwsEventType type,
|
|
|
|
void* arg,
|
|
|
|
uint8_t* data,
|
|
|
|
size_t len) {
|
|
|
|
if (type == WS_EVT_DATA) {
|
|
|
|
AwsFrameInfo* info = (AwsFrameInfo*)arg;
|
|
|
|
if (info->final && info->index == 0 && info->len == len) {
|
|
|
|
if (info->opcode == WS_TEXT) {
|
2020-05-22 18:26:12 +00:00
|
|
|
DynamicJsonDocument jsonDocument = DynamicJsonDocument(WebSocketConnector<T>::_bufferSize);
|
2020-05-14 22:23:45 +00:00
|
|
|
DeserializationError error = deserializeJson(jsonDocument, (char*)data);
|
|
|
|
if (!error && jsonDocument.is<JsonObject>()) {
|
|
|
|
JsonObject jsonObject = jsonDocument.as<JsonObject>();
|
|
|
|
WebSocketConnector<T>::_statefulService->update(
|
2020-05-29 19:18:43 +00:00
|
|
|
jsonObject, _stateUpdater, WebSocketConnector<T>::clientId(client));
|
2020-05-14 22:23:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2020-05-29 19:18:43 +00:00
|
|
|
JsonStateUpdater<T> _stateUpdater;
|
2020-05-14 22:23:45 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> {
|
|
|
|
public:
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketTxRx(JsonStateReader<T> stateReader,
|
|
|
|
JsonStateUpdater<T> stateUpdater,
|
2020-05-14 22:23:45 +00:00
|
|
|
StatefulService<T>* statefulService,
|
|
|
|
AsyncWebServer* server,
|
2020-11-21 23:40:31 +00:00
|
|
|
const char* webSocketPath,
|
2020-05-14 22:23:45 +00:00
|
|
|
SecurityManager* securityManager,
|
2020-05-22 18:26:12 +00:00
|
|
|
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
|
|
|
|
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
|
|
|
|
WebSocketConnector<T>(statefulService,
|
|
|
|
server,
|
|
|
|
webSocketPath,
|
|
|
|
securityManager,
|
|
|
|
authenticationPredicate,
|
|
|
|
bufferSize),
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketTx<T>(stateReader,
|
2020-05-22 18:26:12 +00:00
|
|
|
statefulService,
|
|
|
|
server,
|
|
|
|
webSocketPath,
|
|
|
|
securityManager,
|
|
|
|
authenticationPredicate,
|
|
|
|
bufferSize),
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketRx<T>(stateUpdater,
|
2020-05-14 22:23:45 +00:00
|
|
|
statefulService,
|
|
|
|
server,
|
|
|
|
webSocketPath,
|
|
|
|
securityManager,
|
2020-05-22 18:26:12 +00:00
|
|
|
authenticationPredicate,
|
|
|
|
bufferSize) {
|
2020-05-14 22:23:45 +00:00
|
|
|
}
|
|
|
|
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketTxRx(JsonStateReader<T> stateReader,
|
|
|
|
JsonStateUpdater<T> stateUpdater,
|
2020-05-14 22:23:45 +00:00
|
|
|
StatefulService<T>* statefulService,
|
|
|
|
AsyncWebServer* server,
|
2020-11-21 23:40:31 +00:00
|
|
|
const char* webSocketPath,
|
2020-05-22 18:26:12 +00:00
|
|
|
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
|
|
|
|
WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize),
|
2020-05-29 19:18:43 +00:00
|
|
|
WebSocketTx<T>(stateReader, statefulService, server, webSocketPath, bufferSize),
|
|
|
|
WebSocketRx<T>(stateUpdater, statefulService, server, webSocketPath, bufferSize) {
|
2020-05-14 22:23:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void onWSEvent(AsyncWebSocket* server,
|
|
|
|
AsyncWebSocketClient* client,
|
|
|
|
AwsEventType type,
|
|
|
|
void* arg,
|
|
|
|
uint8_t* data,
|
|
|
|
size_t len) {
|
|
|
|
WebSocketRx<T>::onWSEvent(server, client, type, arg, data, len);
|
|
|
|
WebSocketTx<T>::onWSEvent(server, client, type, arg, data, len);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|