Improve error handling and null checks in WebSocket server callbacks

- Add null checks for user data in various WebSocket callback functions to prevent crashes and improve stability.
- Enhance error logging to provide clearer insights into issues related to user data and connection management.
- Refactor the handling of active connections to ensure robust error handling during data processing and message sending.
This commit is contained in:
Torsten Schulz (local)
2025-11-20 16:25:29 +01:00
committed by Torsten (PC)
parent dafdbf0a84
commit 9c7b682a36

View File

@@ -237,8 +237,13 @@ int WebSocketServer::wsCallback(struct lws *wsi,
if (!instance) return 0; if (!instance) return 0;
auto *ud = reinterpret_cast<WebSocketUserData*>(user); auto *ud = reinterpret_cast<WebSocketUserData*>(user);
switch (reason) { switch (reason) {
case LWS_CALLBACK_ESTABLISHED: { case LWS_CALLBACK_ESTABLISHED: {
if (!ud) {
std::cerr << "[ESTABLISHED] ud ist nullptr" << std::endl;
return 0;
}
ud->pongReceived = true; ud->pongReceived = true;
ud->connectionTime = std::chrono::steady_clock::now(); ud->connectionTime = std::chrono::steady_clock::now();
ud->lastPingTime = std::chrono::steady_clock::now(); ud->lastPingTime = std::chrono::steady_clock::now();
@@ -260,6 +265,10 @@ int WebSocketServer::wsCallback(struct lws *wsi,
} }
case LWS_CALLBACK_RECEIVE_PONG: case LWS_CALLBACK_RECEIVE_PONG:
// WebSocket Pong-Frame empfangen (automatische Antwort auf Ping) // WebSocket Pong-Frame empfangen (automatische Antwort auf Ping)
if (!ud) {
std::cerr << "[RECEIVE_PONG] ud ist nullptr" << std::endl;
return 0;
}
ud->pongReceived = true; ud->pongReceived = true;
ud->lastPongTime = std::chrono::steady_clock::now(); ud->lastPongTime = std::chrono::steady_clock::now();
ud->pingTimeoutCount = 0; ud->pingTimeoutCount = 0;
@@ -306,38 +315,57 @@ int WebSocketServer::wsCallback(struct lws *wsi,
} }
} else if (event == "getConnections") { } else if (event == "getConnections") {
// Admin-Funktion: Liste aller aktiven Verbindungen // Admin-Funktion: Liste aller aktiven Verbindungen
if (!ud) {
std::cerr << "[RECEIVE] getConnections: ud ist nullptr" << std::endl;
break;
}
if (ud->userId.empty()) { if (ud->userId.empty()) {
std::cerr << "[RECEIVE] getConnections: User-ID nicht gesetzt" << std::endl; std::cerr << "[RECEIVE] getConnections: User-ID nicht gesetzt" << std::endl;
json errorResponse = { try {
{"event", "getConnectionsResponse"}, json errorResponse = {
{"success", false}, {"event", "getConnectionsResponse"},
{"error", "User-ID nicht gesetzt"} {"success", false},
}; {"error", "User-ID nicht gesetzt"}
instance->sendMessageToConnection(wsi, errorResponse.dump()); };
if (instance && wsi) {
instance->sendMessageToConnection(wsi, errorResponse.dump());
}
} catch (const std::exception &e) {
std::cerr << "[RECEIVE] Fehler beim Senden der Fehlerantwort: " << e.what() << std::endl;
}
break; break;
} }
// Prüfe Mainadmin-Rechte // Prüfe Mainadmin-Rechte
if (!instance->isMainAdmin(ud->userId)) { try {
std::cerr << "[RECEIVE] getConnections: Zugriff verweigert für User " << ud->userId << std::endl; if (!instance || !instance->isMainAdmin(ud->userId)) {
json errorResponse = { std::cerr << "[RECEIVE] getConnections: Zugriff verweigert für User " << ud->userId << std::endl;
json errorResponse = {
{"event", "getConnectionsResponse"},
{"success", false},
{"error", "Zugriff verweigert: Nur Mainadmin-User können Verbindungen abfragen"}
};
if (instance && wsi) {
instance->sendMessageToConnection(wsi, errorResponse.dump());
}
break;
}
// Hole aktive Verbindungen
json connections = instance->getActiveConnections();
json response = {
{"event", "getConnectionsResponse"}, {"event", "getConnectionsResponse"},
{"success", false}, {"success", true},
{"error", "Zugriff verweigert: Nur Mainadmin-User können Verbindungen abfragen"} {"data", connections}
}; };
instance->sendMessageToConnection(wsi, errorResponse.dump()); if (instance && wsi) {
break; instance->sendMessageToConnection(wsi, response.dump());
std::cout << "[RECEIVE] getConnections: Verbindungen an Mainadmin gesendet" << std::endl;
}
} catch (const std::exception &e) {
std::cerr << "[RECEIVE] Fehler bei getConnections: " << e.what() << std::endl;
} }
// Hole aktive Verbindungen
json connections = instance->getActiveConnections();
json response = {
{"event", "getConnectionsResponse"},
{"success", true},
{"data", connections}
};
instance->sendMessageToConnection(wsi, response.dump());
std::cout << "[RECEIVE] getConnections: Verbindungen an Mainadmin gesendet" << std::endl;
} else { } else {
std::cout << "[RECEIVE] Unbekanntes Event: " << event << std::endl; std::cout << "[RECEIVE] Unbekanntes Event: " << event << std::endl;
} }
@@ -350,6 +378,10 @@ int WebSocketServer::wsCallback(struct lws *wsi,
break; break;
} }
case LWS_CALLBACK_SERVER_WRITEABLE: { case LWS_CALLBACK_SERVER_WRITEABLE: {
if (!ud) {
std::cerr << "[WRITEABLE] ud ist nullptr" << std::endl;
return 0;
}
// Prüfe ob es eine Nachricht zum Senden gibt // Prüfe ob es eine Nachricht zum Senden gibt
std::string messageToSend; std::string messageToSend;
{ {
@@ -575,12 +607,19 @@ nlohmann::json WebSocketServer::getActiveConnections() {
}; };
for (auto* wsi : connList) { for (auto* wsi : connList) {
if (!wsi) continue;
auto* ud = reinterpret_cast<WebSocketUserData*>(lws_wsi_user(wsi)); auto* ud = reinterpret_cast<WebSocketUserData*>(lws_wsi_user(wsi));
if (ud) { if (!ud) continue;
try {
// Berechne Verbindungsdauer seit ESTABLISHED // Berechne Verbindungsdauer seit ESTABLISHED
// Verwende lastPongTime als Fallback, falls connectionTime nicht gesetzt ist
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
auto connectionTime = ud->connectionTime.time_since_epoch().count() != 0
? ud->connectionTime
: ud->lastPongTime;
auto connectionDuration = std::chrono::duration_cast<std::chrono::seconds>( auto connectionDuration = std::chrono::duration_cast<std::chrono::seconds>(
now - ud->connectionTime).count(); now - connectionTime).count();
// Berechne Zeit seit letztem Pong // Berechne Zeit seit letztem Pong
auto timeSinceLastPong = std::chrono::duration_cast<std::chrono::seconds>( auto timeSinceLastPong = std::chrono::duration_cast<std::chrono::seconds>(
@@ -593,6 +632,8 @@ nlohmann::json WebSocketServer::getActiveConnections() {
{"pongReceived", ud->pongReceived} {"pongReceived", ud->pongReceived}
}; };
userConnections["connections"].push_back(connInfo); userConnections["connections"].push_back(connInfo);
} catch (const std::exception &e) {
std::cerr << "[getActiveConnections] Fehler beim Verarbeiten einer Verbindung: " << e.what() << std::endl;
} }
} }
@@ -608,11 +649,18 @@ nlohmann::json WebSocketServer::getActiveConnections() {
}; };
for (auto* wsi : allConnections) { for (auto* wsi : allConnections) {
if (!wsi) continue;
auto* ud = reinterpret_cast<WebSocketUserData*>(lws_wsi_user(wsi)); auto* ud = reinterpret_cast<WebSocketUserData*>(lws_wsi_user(wsi));
if (ud && ud->userId.empty()) { if (!ud || !ud->userId.empty()) continue;
try {
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
// Verwende lastPongTime als Fallback, falls connectionTime nicht gesetzt ist
auto connectionTime = ud->connectionTime.time_since_epoch().count() != 0
? ud->connectionTime
: ud->lastPongTime;
auto connectionDuration = std::chrono::duration_cast<std::chrono::seconds>( auto connectionDuration = std::chrono::duration_cast<std::chrono::seconds>(
now - ud->connectionTime).count(); now - connectionTime).count();
auto timeSinceLastPong = std::chrono::duration_cast<std::chrono::seconds>( auto timeSinceLastPong = std::chrono::duration_cast<std::chrono::seconds>(
now - ud->lastPongTime).count(); now - ud->lastPongTime).count();
@@ -624,6 +672,8 @@ nlohmann::json WebSocketServer::getActiveConnections() {
{"status", "unauthenticated"} {"status", "unauthenticated"}
}; };
unauthenticatedConnections["connections"].push_back(connInfo); unauthenticatedConnections["connections"].push_back(connInfo);
} catch (const std::exception &e) {
std::cerr << "[getActiveConnections] Fehler beim Verarbeiten einer unauthentifizierten Verbindung: " << e.what() << std::endl;
} }
} }
@@ -634,17 +684,42 @@ nlohmann::json WebSocketServer::getActiveConnections() {
} }
void WebSocketServer::sendMessageToConnection(struct lws *wsi, const std::string &message) { void WebSocketServer::sendMessageToConnection(struct lws *wsi, const std::string &message) {
if (!wsi) return; if (!wsi) {
std::cerr << "[sendMessageToConnection] wsi ist nullptr" << std::endl;
auto* ud = reinterpret_cast<WebSocketUserData*>(lws_wsi_user(wsi)); return;
if (!ud) return;
{
std::lock_guard<std::mutex> lock(ud->messageQueueMutex);
ud->messageQueue.push(message);
} }
lws_callback_on_writable(wsi); if (!context) {
std::cerr << "[sendMessageToConnection] context ist nullptr" << std::endl;
return;
}
auto* ud = reinterpret_cast<WebSocketUserData*>(lws_wsi_user(wsi));
if (!ud) {
std::cerr << "[sendMessageToConnection] ud ist nullptr" << std::endl;
return;
}
try {
bool wasEmpty = false;
{
std::lock_guard<std::mutex> lock(ud->messageQueueMutex);
wasEmpty = ud->messageQueue.empty();
ud->messageQueue.push(message);
}
// Nur wenn die Queue leer war, den Callback aufrufen
// (sonst wird er bereits durch den WRITEABLE-Handler aufgerufen)
if (wasEmpty) {
// Verwende lws_cancel_service, um den Service zu benachrichtigen, anstatt
// lws_callback_on_writable direkt aufzurufen (sicherer während Callbacks)
lws_cancel_service(context);
// Alternativ: lws_callback_on_writable(wsi) sollte auch funktionieren
lws_callback_on_writable(wsi);
}
} catch (const std::exception &e) {
std::cerr << "[sendMessageToConnection] Fehler: " << e.what() << std::endl;
}
} }
void WebSocketServer::setWorkers(const std::vector<std::unique_ptr<Worker>> &workerList) { void WebSocketServer::setWorkers(const std::vector<std::unique_ptr<Worker>> &workerList) {