- Ergänze Debug-Logs in den Konstruktoren und wichtigen Methoden, um den Ablauf und die Konfiguration während der Server- und Benutzerinitialisierung zu protokollieren. - Verbessere die Nachverfolgbarkeit von WebSocket-Verbindungen und Nachrichtenübertragungen durch zusätzliche Ausgaben in den entsprechenden Methoden. - Diese Änderungen unterstützen die Fehlersuche und verbessern die Transparenz des Systemverhaltens während der Laufzeit.
717 lines
32 KiB
C++
Executable File
717 lines
32 KiB
C++
Executable File
#include "server.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <thread>
|
|
#include <future>
|
|
#include <functional>
|
|
#include <unistd.h>
|
|
#include <json/json.h>
|
|
#include <iostream>
|
|
#include <netinet/tcp.h>
|
|
#include <arpa/inet.h>
|
|
#include <sstream>
|
|
#include <regex>
|
|
#include <errno.h>
|
|
#include <cstring>
|
|
|
|
namespace Yc {
|
|
namespace Lib {
|
|
|
|
Server::Server(std::shared_ptr<Yc::Lib::Config> config, std::shared_ptr<Yc::Lib::Database> database) :
|
|
_config(std::move(config)),
|
|
_database(std::move(database)),
|
|
_stop(false),
|
|
_socket(-1) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Server constructor called" << std::endl;
|
|
#endif
|
|
|
|
int port = 1235;
|
|
try {
|
|
Json::Value p = _config->value("server", "port");
|
|
if (p.isInt()) port = p.asInt();
|
|
} catch (...) {}
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Server port: " << port << std::endl;
|
|
#endif
|
|
int opt = 1;
|
|
// Try IPv6 dual-stack (accepts IPv4 via v4-mapped if v6only=0)
|
|
_socket = socket(AF_INET6, SOCK_STREAM, 0);
|
|
if (_socket >= 0) {
|
|
setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
|
|
int v6only = 0; setsockopt(_socket, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only));
|
|
int flags = 1; setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
|
|
int ka = 1; setsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka));
|
|
struct sockaddr_in6 addr6{}; addr6.sin6_family = AF_INET6; addr6.sin6_addr = in6addr_any; addr6.sin6_port = htons(port);
|
|
if (bind(_socket, (struct sockaddr *)&addr6, sizeof(addr6)) == 0) {
|
|
std::cout << "[YourChat] Server gestartet. Lausche auf Port " << port << " (IPv6 dual-stack)" << std::endl;
|
|
} else {
|
|
// Fallback to IPv4
|
|
close(_socket);
|
|
_socket = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (_socket < 0) {
|
|
std::cout << "socket create failed" << std::endl;
|
|
exit(-1);
|
|
}
|
|
setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
|
|
flags = 1; setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
|
|
ka = 1; setsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka));
|
|
struct sockaddr_in addr4{}; addr4.sin_family = AF_INET; addr4.sin_addr.s_addr = INADDR_ANY; addr4.sin_port = htons(port);
|
|
if (bind(_socket, (struct sockaddr *)&addr4, sizeof(addr4)) != 0) {
|
|
std::cout << "bind not possible on IPv4" << std::endl;
|
|
close(_socket);
|
|
_socket = -1;
|
|
exit(-1);
|
|
}
|
|
std::cout << "[YourChat] Server gestartet. Lausche auf Port " << port << " (IPv4)" << std::endl;
|
|
}
|
|
} else {
|
|
// Fallback to IPv4 directly
|
|
_socket = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (_socket < 0) {
|
|
std::cout << "socket create failed" << std::endl;
|
|
exit(-1);
|
|
}
|
|
setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
|
|
int flags = 1; setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
|
|
int ka = 1; setsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka));
|
|
struct sockaddr_in addr4{}; addr4.sin_family = AF_INET; addr4.sin_addr.s_addr = INADDR_ANY; addr4.sin_port = htons(port);
|
|
if (bind(_socket, (struct sockaddr *)&addr4, sizeof(addr4)) != 0) {
|
|
std::cout << "bind not possible on IPv4" << std::endl;
|
|
close(_socket);
|
|
_socket = -1;
|
|
exit(-1);
|
|
}
|
|
std::cout << "[YourChat] Server gestartet. Lausche auf Port " << port << " (IPv4)" << std::endl;
|
|
}
|
|
}
|
|
|
|
Server::~Server() {
|
|
stop();
|
|
if (_socket >= 0) {
|
|
close(_socket);
|
|
_socket = -1;
|
|
}
|
|
}
|
|
|
|
void Server::stop() {
|
|
if (_stop) {
|
|
return; // Already stopped
|
|
}
|
|
|
|
std::cout << "[YourChat] Stopping regular server..." << std::endl;
|
|
_stop = true;
|
|
|
|
// Stop all rooms
|
|
for (auto& room : _rooms) {
|
|
if (room) {
|
|
room->setStop();
|
|
}
|
|
}
|
|
|
|
// Clear rooms list to ensure proper cleanup order
|
|
_rooms.clear();
|
|
|
|
std::cout << "[YourChat] Regular server stopped." << std::endl;
|
|
}
|
|
|
|
void Server::run() {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Server::run() called" << std::endl;
|
|
#endif
|
|
|
|
if (_socket < 0) {
|
|
std::cout << "Invalid socket, cannot start server" << std::endl;
|
|
return;
|
|
}
|
|
|
|
if (listen(_socket, 5) < 0) {
|
|
std::cout << "listen not possible: " << strerror(errno) << std::endl;
|
|
return;
|
|
}
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Server listening on socket: " << _socket << std::endl;
|
|
#endif
|
|
timeval origTv;
|
|
origTv.tv_sec = 1; // Shorter timeout for more responsive shutdown
|
|
origTv.tv_usec = 0;
|
|
int _maxSd = _socket;
|
|
std::set<int> activeSockets;
|
|
std::mutex socketMutex;
|
|
|
|
while (!_stop) {
|
|
timeval tv(origTv);
|
|
fd_set fd;
|
|
FD_ZERO(&fd);
|
|
FD_SET(_socket, &fd);
|
|
|
|
// Alle aktiven Client-Sockets hinzufügen
|
|
{
|
|
std::lock_guard<std::mutex> lock(socketMutex);
|
|
for (int clientSock : activeSockets) {
|
|
FD_SET(clientSock, &fd);
|
|
if (clientSock > _maxSd) _maxSd = clientSock;
|
|
}
|
|
}
|
|
|
|
int selectResult = select(_maxSd + 1, &fd, NULL, NULL, &tv);
|
|
if (selectResult > 0) {
|
|
// Neue Verbindung?
|
|
if (FD_ISSET(_socket, &fd)) {
|
|
std::thread(&Server::handleRequest, this).detach();
|
|
}
|
|
|
|
// Client-Socket-Aktivität?
|
|
{
|
|
std::lock_guard<std::mutex> lock(socketMutex);
|
|
auto it = activeSockets.begin();
|
|
while (it != activeSockets.end()) {
|
|
if (FD_ISSET(*it, &fd)) {
|
|
// Socket ist aktiv, aber handleRequest läuft bereits in separatem Thread
|
|
// Hier könnten wir Heartbeat/Keepalive prüfen
|
|
++it;
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
} else if (selectResult < 0) {
|
|
if (errno == EINTR) {
|
|
// Interrupted by signal, check if we should stop
|
|
continue;
|
|
}
|
|
std::cerr << "[YourChat] select() error: " << strerror(errno) << std::endl;
|
|
break;
|
|
}
|
|
|
|
// Aufgeräumte Sockets entfernen
|
|
{
|
|
std::lock_guard<std::mutex> lock(socketMutex);
|
|
auto it = activeSockets.begin();
|
|
while (it != activeSockets.end()) {
|
|
if (activeSockets.count(*it) == 0) {
|
|
it = activeSockets.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
std::cout << "[YourChat] Server run loop exiting" << std::endl;
|
|
}
|
|
|
|
std::vector<std::string> Server::roomList() {
|
|
std::vector<std::string> list;
|
|
for (const auto &room: _rooms) {
|
|
list.push_back(room->name());
|
|
}
|
|
return list;
|
|
}
|
|
|
|
Json::Value Server::jsonRoomList() {
|
|
Json::Value list;
|
|
for (auto &room: _rooms) {
|
|
Json::Value roomJson = Json::objectValue;
|
|
roomJson["name"] = room->name();
|
|
roomJson["flags"] = room->flags();
|
|
list.append(roomJson);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
bool Server::roomAllowed(std::string roomName, std::string userName, std::string password){
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] roomAllowed called with roomName: '" << roomName << "', userName: '" << userName << "'" << std::endl;
|
|
std::cout << "[Debug] Available rooms: ";
|
|
for (auto &room: _rooms) {
|
|
std::cout << "'" << room->name() << "' ";
|
|
}
|
|
std::cout << std::endl;
|
|
#endif
|
|
|
|
for (auto &room: _rooms) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Checking room: '" << room->name() << "' against requested: '" << roomName << "'" << std::endl;
|
|
#endif
|
|
if (room->name() == roomName && room->accessAllowed(userName, password)) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Room found and access allowed!" << std::endl;
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Room not found or access denied" << std::endl;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool Server::changeRoom(std::shared_ptr<ChatUser> user, std::string newRoom, std::string password) {
|
|
if (!roomAllowed(newRoom, user->name(), password)) {
|
|
return false;
|
|
}
|
|
|
|
std::string oldRoomName = "";
|
|
// Finde den aktuellen Raum des Users
|
|
for (auto &room: _rooms) {
|
|
if (room->userIsInRoom(user->name())) {
|
|
oldRoomName = room->name();
|
|
break;
|
|
}
|
|
}
|
|
|
|
Json::Value userMsg = Json::objectValue;
|
|
userMsg["tr"] = "room_change_user";
|
|
userMsg["to"] = newRoom;
|
|
|
|
// Nur Nachrichten senden, wenn der User bereits in einem Raum ist (Raumwechsel)
|
|
if (!oldRoomName.empty()) {
|
|
for (auto &room: _rooms) {
|
|
if (room->name() == oldRoomName) {
|
|
// Sende Nachricht an alle User im alten Raum, dass der User den Raum verlassen hat
|
|
Json::Value leaveMsg = Json::objectValue;
|
|
leaveMsg["tr"] = "user_left_room";
|
|
leaveMsg["userName"] = user->name();
|
|
leaveMsg["userColor"] = user->color();
|
|
leaveMsg["destination"] = newRoom;
|
|
room->addMessage(ChatUser::system, leaveMsg, "", "");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Entferne User aus dem alten Raum
|
|
for (auto &room: _rooms) {
|
|
if (room->userIsInRoom(user->name())) {
|
|
room->removeUser(user->getToken(), true); // silent = true, da wir eigene Nachrichten senden
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Füge User zum neuen Raum hinzu
|
|
for (auto &room: _rooms) {
|
|
if (room->name() == newRoom) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] changeRoom: Adding user '" << user->name() << "' to room '" << newRoom << "'" << std::endl;
|
|
std::cout << "[Debug] changeRoom: oldRoomName = '" << (oldRoomName.empty() ? "EMPTY" : oldRoomName) << "'" << std::endl;
|
|
#endif
|
|
|
|
room->addUserWhenQueueEmpty(user);
|
|
|
|
// Nur bei Raumwechsel (nicht beim ersten Beitritt) die Nachricht senden
|
|
if (!oldRoomName.empty()) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] changeRoom: Sending 'user_entered_room' message (room change)" << std::endl;
|
|
#endif
|
|
Json::Value joinMsg = Json::objectValue;
|
|
joinMsg["tr"] = "user_entered_room";
|
|
joinMsg["userName"] = user->name();
|
|
joinMsg["userColor"] = user->color();
|
|
joinMsg["origin"] = oldRoomName;
|
|
room->addMessage(ChatUser::system, joinMsg, "", "");
|
|
} else {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] changeRoom: NOT sending 'user_entered_room' message (first join)" << std::endl;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Server::reloadRooms() {
|
|
std::cout << "[YourChat] Reloading rooms from database..." << std::endl;
|
|
|
|
// Clear existing rooms
|
|
_rooms.clear();
|
|
|
|
// Reload from database
|
|
Json::Value emptyRoomList;
|
|
createRooms(emptyRoomList);
|
|
|
|
std::cout << "[YourChat] Reloaded " << _rooms.size() << " rooms from database" << std::endl;
|
|
}
|
|
|
|
void Server::createRooms(Json::Value roomList) {
|
|
auto self = shared_from_this();
|
|
bool created = false;
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] createRooms called with roomList size: " << roomList.size() << std::endl;
|
|
std::cout << "[Debug] roomList content: " << roomList << std::endl;
|
|
#endif
|
|
|
|
try {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Attempting to load rooms from database..." << std::endl;
|
|
#endif
|
|
std::string query = R"(
|
|
SELECT r.id, r.title, r.password_hash, r.room_type_id, r.is_public, r.owner_id, r.min_age, r.max_age, r.created_at, r.updated_at, rt.tr as room_type
|
|
FROM chat.room r
|
|
LEFT JOIN chat.room_type rt ON r.room_type_id = rt.id
|
|
)";
|
|
auto result = _database->exec(query);
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Database query result size: " << result.size() << std::endl;
|
|
#endif
|
|
for (const auto& row : result) {
|
|
Json::Value room;
|
|
room["id"] = row["id"].as<int>();
|
|
room["name"] = row["title"].c_str();
|
|
room["password"] = row["password_hash"].is_null() ? "" : row["password_hash"].c_str();
|
|
room["type"] = row["room_type_id"].is_null() ? 0 : row["room_type_id"].as<int>();
|
|
room["is_public"] = row["is_public"].as<bool>();
|
|
room["owner_id"] = row["owner_id"].is_null() ? 0 : row["owner_id"].as<int>();
|
|
room["min_age"] = row["min_age"].is_null() ? 0 : row["min_age"].as<int>();
|
|
room["max_age"] = row["max_age"].is_null() ? 0 : row["max_age"].as<int>();
|
|
room["created_at"] = row["created_at"].c_str();
|
|
room["updated_at"] = row["updated_at"].c_str();
|
|
room["room_type"] = row["room_type"].is_null() ? "" : row["room_type"].c_str();
|
|
// Platzhalter für Felder, die im Konstruktor benötigt werden
|
|
room["allowed"] = Json::arrayValue; // ggf. später befüllen
|
|
room["roundlength"] = 60; // Default-Wert
|
|
auto newRoom = std::make_shared<ChatRoom>(self, room);
|
|
newRoom->setDatabase(_database);
|
|
_rooms.push_back(newRoom);
|
|
created = true;
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Created room from database: " << room["name"].asString() << std::endl;
|
|
#endif
|
|
}
|
|
} catch (const std::exception& e) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Database error: " << e.what() << std::endl;
|
|
#endif
|
|
} catch (...) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Unknown database error occurred" << std::endl;
|
|
#endif
|
|
}
|
|
|
|
if (!created) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Database loading failed, trying fallback rooms..." << std::endl;
|
|
#endif
|
|
// fallback to provided JSON room list (if any)
|
|
if (roomList.isArray() && roomList.size() > 0) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Loading " << roomList.size() << " fallback rooms from config" << std::endl;
|
|
#endif
|
|
for (const auto& room : roomList) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Creating fallback room: " << room["name"].asString() << std::endl;
|
|
#endif
|
|
auto newRoom = std::make_shared<ChatRoom>(self, room);
|
|
newRoom->setDatabase(_database);
|
|
_rooms.push_back(newRoom);
|
|
created = true;
|
|
}
|
|
} else {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] No fallback rooms in config, creating default room" << std::endl;
|
|
#endif
|
|
// final fallback: builtin default room
|
|
Json::Value room;
|
|
room["name"] = "Halle";
|
|
room["password"] = "";
|
|
room["allowed"] = Json::arrayValue;
|
|
room["type"] = 0;
|
|
room["roundlength"] = 0;
|
|
auto newRoom = std::make_shared<ChatRoom>(self, room);
|
|
newRoom->setDatabase(_database);
|
|
_rooms.push_back(newRoom);
|
|
created = true;
|
|
}
|
|
}
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Total rooms created: " << _rooms.size() << std::endl;
|
|
for (const auto& room : _rooms) {
|
|
std::cout << "[Debug] Room: " << room->name() << std::endl;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Server::handleRequest() {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] handleRequest called" << std::endl;
|
|
#endif
|
|
|
|
struct sockaddr_in sockAddr;
|
|
socklen_t sockAddrLen = sizeof(sockAddr);
|
|
int userSock = accept(_socket, (struct sockaddr *)&sockAddr, &sockAddrLen);
|
|
if (userSock < 0) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] accept failed: " << strerror(errno) << std::endl;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] New connection accepted, socket: " << userSock << std::endl;
|
|
#endif
|
|
|
|
// Neuen Socket zur Überwachung hinzufügen
|
|
{
|
|
std::lock_guard<std::mutex> lock(socketMutex);
|
|
activeSockets.insert(userSock);
|
|
}
|
|
// Log jede akzeptierte Verbindung
|
|
char clientIP[INET_ADDRSTRLEN];
|
|
inet_ntop(AF_INET, &(sockAddr.sin_addr), clientIP, INET_ADDRSTRLEN);
|
|
std::cout << "[YourChat] Verbindung akzeptiert von " << clientIP << ":" << ntohs(sockAddr.sin_port) << " (fd=" << userSock << ")" << std::endl;
|
|
int flags = 1;
|
|
setsockopt(userSock, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
|
|
|
|
// Aggressive TCP Keep-Alive für schnelle Verbindungsabbruch-Erkennung
|
|
int ka2 = 1;
|
|
setsockopt(userSock, SOL_SOCKET, SO_KEEPALIVE, &ka2, sizeof(ka2));
|
|
|
|
// Keep-Alive Parameter: 5 Sekunden bis zum ersten Probe, dann alle 2 Sekunden
|
|
int keepalive_time = 5; // 5 Sekunden bis zum ersten Probe
|
|
int keepalive_intvl = 2; // 2 Sekunden zwischen Probes
|
|
int keepalive_probes = 3; // 3 Probes bevor Verbindung als tot betrachtet wird
|
|
|
|
setsockopt(userSock, IPPROTO_TCP, TCP_KEEPIDLE, &keepalive_time, sizeof(keepalive_time));
|
|
setsockopt(userSock, IPPROTO_TCP, TCP_KEEPINTVL, &keepalive_intvl, sizeof(keepalive_intvl));
|
|
setsockopt(userSock, IPPROTO_TCP, TCP_KEEPCNT, &keepalive_probes, sizeof(keepalive_probes));
|
|
|
|
// Begrenze Blockierzeit beim Senden, um langsame Clients nicht alle zu verzögern
|
|
timeval sendTimeout; sendTimeout.tv_sec = 0; sendTimeout.tv_usec = 500000; // 500ms
|
|
setsockopt(userSock, SOL_SOCKET, SO_SNDTIMEO, &sendTimeout, sizeof(sendTimeout));
|
|
|
|
// Socket-Optionen für schnellere Fehlererkennung
|
|
int reuseAddr = 1;
|
|
setsockopt(userSock, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr));
|
|
|
|
// LINGER-Option: Sofort schließen bei Verbindungsabbruch
|
|
struct linger linger_opt;
|
|
linger_opt.l_onoff = 1;
|
|
linger_opt.l_linger = 0; // 0 Sekunden = sofort schließen
|
|
setsockopt(userSock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt));
|
|
|
|
// TCP_NODELAY bereits gesetzt (oben)
|
|
// TCP Keep-Alive bereits konfiguriert (oben)
|
|
std::string msg = readSocket(userSock);
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Neue Anfrage erhalten: " << msg << std::endl;
|
|
#endif
|
|
if (msg == "") {
|
|
return;
|
|
}
|
|
// OPTIONS Request (CORS Preflight)
|
|
if (msg.rfind("OPTIONS ", 0) == 0) {
|
|
std::ostringstream resp;
|
|
resp << "HTTP/1.1 200 OK\r\n"
|
|
<< "Access-Control-Allow-Origin: *\r\n"
|
|
<< "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n"
|
|
<< "Access-Control-Allow-Headers: Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, Sec-WebSocket-Protocol, Origin\r\n"
|
|
<< "Access-Control-Allow-Credentials: true\r\n"
|
|
<< "Access-Control-Max-Age: 86400\r\n"
|
|
<< "Content-Length: 0\r\n"
|
|
<< "\r\n";
|
|
::send(userSock, resp.str().c_str(), resp.str().size(), 0);
|
|
close(userSock);
|
|
return;
|
|
}
|
|
|
|
// WebSocket Upgrade?
|
|
if (msg.rfind("GET ", 0) == 0 && msg.find("Upgrade: websocket") != std::string::npos) {
|
|
// sehr einfacher Header-Parser
|
|
std::string key;
|
|
std::string subprotocol;
|
|
std::string origin;
|
|
std::string version;
|
|
std::string extensions;
|
|
std::istringstream iss(msg);
|
|
std::string line;
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] === WebSocket Headers ===" << std::endl;
|
|
#endif
|
|
|
|
while (std::getline(iss, line)) {
|
|
if (!line.empty() && (line.back() == '\r' || line.back() == '\n')) line.pop_back();
|
|
auto pos = line.find(":");
|
|
if (pos != std::string::npos) {
|
|
std::string h = line.substr(0, pos);
|
|
std::string v = line.substr(pos+1);
|
|
// trim
|
|
auto ltrim = [](std::string &s){ s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch){return !std::isspace(ch);}));};
|
|
auto rtrim = [](std::string &s){ s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch){return !std::isspace(ch);}).base(), s.end());};
|
|
ltrim(h); rtrim(h); ltrim(v); rtrim(v);
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Header: '" << h << "' = '" << v << "'" << std::endl;
|
|
#endif
|
|
|
|
if (strcasecmp(h.c_str(), "Sec-WebSocket-Key") == 0) {
|
|
key = v;
|
|
} else if (strcasecmp(h.c_str(), "Sec-WebSocket-Protocol") == 0) {
|
|
subprotocol = v;
|
|
} else if (strcasecmp(h.c_str(), "Origin") == 0) {
|
|
origin = v;
|
|
} else if (strcasecmp(h.c_str(), "Sec-WebSocket-Version") == 0) {
|
|
version = v;
|
|
} else if (strcasecmp(h.c_str(), "Sec-WebSocket-Extensions") == 0) {
|
|
extensions = v;
|
|
}
|
|
}
|
|
if (line.empty()) break;
|
|
}
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] === Parsed Values ===" << std::endl;
|
|
std::cout << "[Debug] Key: " << (key.empty() ? "MISSING" : key) << std::endl;
|
|
std::cout << "[Debug] Protocol: " << (subprotocol.empty() ? "NONE" : subprotocol) << std::endl;
|
|
std::cout << "[Debug] Origin: " << (origin.empty() ? "MISSING" : origin) << std::endl;
|
|
std::cout << "[Debug] Version: " << (version.empty() ? "MISSING" : version) << std::endl;
|
|
std::cout << "[Debug] Extensions: " << (extensions.empty() ? "NONE" : extensions) << std::endl;
|
|
std::cout << "[Debug] ======================" << std::endl;
|
|
#endif
|
|
if (!key.empty()) {
|
|
std::string accept = Base::webSocketAcceptKey(key);
|
|
std::ostringstream resp;
|
|
resp << "HTTP/1.1 101 Switching Protocols\r\n"
|
|
<< "Upgrade: websocket\r\n"
|
|
<< "Connection: Upgrade\r\n"
|
|
<< "Sec-WebSocket-Accept: " << accept << "\r\n";
|
|
|
|
// CORS-Header hinzufügen
|
|
resp << "Access-Control-Allow-Origin: *\r\n"
|
|
<< "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n"
|
|
<< "Access-Control-Allow-Headers: Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, Sec-WebSocket-Protocol, Origin\r\n"
|
|
<< "Access-Control-Allow-Credentials: true\r\n";
|
|
|
|
// Subprotokoll-Unterstützung
|
|
if (!subprotocol.empty()) {
|
|
resp << "Sec-WebSocket-Protocol: " << subprotocol << "\r\n";
|
|
} else {
|
|
// Fallback: Standard-Subprotokoll anbieten
|
|
resp << "Sec-WebSocket-Protocol: chat\r\n";
|
|
}
|
|
|
|
resp << "\r\n";
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] === WebSocket Response ===" << std::endl;
|
|
std::cout << "[Debug] " << resp.str() << std::endl;
|
|
std::cout << "[Debug] =========================" << std::endl;
|
|
#endif
|
|
|
|
::send(userSock, resp.str().c_str(), resp.str().size(), 0);
|
|
Base::markWebSocket(userSock);
|
|
|
|
// Sofort eine Willkommen-Nachricht senden, um Firefox's Timing-Problem zu lösen
|
|
// Verwende korrektes WebSocket-Framing
|
|
std::string welcomeMsg = "{\"type\":\"welcome\",\"message\":\"WebSocket-Verbindung hergestellt\"}";
|
|
Base::sendWebSocketMessage(userSock, welcomeMsg);
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] WS upgrade ok, welcome message sent, waiting for init... fd=" << userSock << std::endl;
|
|
#endif
|
|
|
|
// Jetzt WebSocket Nachrichten lesen und an inputSwitcher weitergeben
|
|
bool ownedByUser = false;
|
|
while (true) {
|
|
std::string wmsg = readSocket(userSock);
|
|
if (wmsg.empty()) break;
|
|
ownedByUser = inputSwitcher(userSock, wmsg);
|
|
if (ownedByUser) break;
|
|
}
|
|
if (ownedByUser) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Ownership transferred to ChatUser, stop reading in Server for fd=" << userSock << std::endl;
|
|
#endif
|
|
}
|
|
if (!ownedByUser) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] WS closing (no ownership) fd=" << userSock << std::endl;
|
|
#endif
|
|
Base::unmarkWebSocket(userSock);
|
|
close(userSock);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// Fallback: Plain JSON
|
|
bool owned = inputSwitcher(userSock, msg);
|
|
if (!owned) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Plain JSON path without ownership, closing fd=" << userSock << std::endl;
|
|
#endif
|
|
close(userSock);
|
|
}
|
|
}
|
|
|
|
bool Server::inputSwitcher(int userSocket, std::string input) {
|
|
Json::Value inputTree = getJsonTree(input);
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] inputSwitcher: type=" << inputTree["type"].asString() << std::endl;
|
|
#endif
|
|
if (inputTree["type"] == "init") {
|
|
initUser(userSocket, inputTree);
|
|
return true; // ChatUser übernimmt nun den Socket
|
|
} else {
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Server::userExists(std::string userName) {
|
|
for (const auto &room: _rooms) {
|
|
if (room->userNameExists(userName)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Server::initUser(int userSocket, Json::Value data) {
|
|
std::string name = data.isMember("name") ? data["name"].asString() : "";
|
|
std::string room = data.isMember("room") ? data["room"].asString() : "";
|
|
std::string color = data.isMember("color") ? data["color"].asString() : "#000000";
|
|
std::string password = data.isMember("password") ? data["password"].asString() : "";
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] initUser: name=" << name << ", room=" << room << ", color=" << color << std::endl;
|
|
#endif
|
|
if (name.empty() || room.empty()) {
|
|
Json::Value errorJson;
|
|
errorJson["type"] = ChatUser::error;
|
|
errorJson["message"] = "missing_fields";
|
|
errorJson["detail"] = "'name' und 'room' müssen gesetzt sein.";
|
|
send(userSocket, errorJson);
|
|
close(userSocket);
|
|
return;
|
|
}
|
|
if (userExists(name)) {
|
|
Json::Value errorJson;
|
|
errorJson["type"] = ChatUser::error;
|
|
errorJson["message"] = "loggedin";
|
|
send(userSocket, errorJson);
|
|
close(userSocket);
|
|
return;
|
|
}
|
|
bool added(false);
|
|
for (auto &roomObj: _rooms) {
|
|
if (roomObj->name() == room) {
|
|
if (roomObj->addUser(name, color, password, userSocket)) {
|
|
added = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!added) {
|
|
Json::Value errorJson;
|
|
errorJson["type"] = ChatUser::error;
|
|
errorJson["message"] = "room_not_found_or_join_failed";
|
|
send(userSocket, errorJson);
|
|
close(userSocket);
|
|
}
|
|
}
|
|
} // namespace Lib
|
|
} // namespace Yp
|