#include "webservernotify.h" #include #include #include #include #include #include HttpServer::HttpServer(QObject *parent) : QObject(parent) , m_server(nullptr) , m_pageBackgroundColor("transparent") { } HttpServer::~HttpServer() { stop(); } bool HttpServer::start(quint16 port) { if (m_server) { stop(); } m_server = new QTcpServer(this); if (!m_server->listen(QHostAddress::Any, port)) { delete m_server; m_server = nullptr; emit serverStarted(false); return false; } connect(m_server, &QTcpServer::newConnection, this, &HttpServer::onNewConnection); emit serverStarted(true); return true; } void HttpServer::stop() { if (m_server) { m_server->close(); m_server->deleteLater(); m_server = nullptr; } // Закрываем всех клиентов for (auto client : m_clients) { if (client->state() == QAbstractSocket::ConnectedState) { client->close(); } client->deleteLater(); } m_clients.clear(); } quint16 HttpServer::port() const { return m_server ? m_server->serverPort() : 0; } void HttpServer::addNotification(const Notification ¬ification) { cleanOldMessages(); // Добавляем временную метку Notification notif = notification; notif.timestamp = QDateTime::currentSecsSinceEpoch(); // Обновляем цвет фона страницы из уведомления if (!notification.pageBackgroundColor.isEmpty()) { m_pageBackgroundColor = notification.pageBackgroundColor; } m_notifications.append(notif); // Обновляем всех подключенных клиентов for (auto client : m_clients) { if (client->state() == QAbstractSocket::ConnectedState) { // Отправляем JavaScript для обновления страницы QString js = ""; sendResponse(client, "text/html", js); } } } void HttpServer::clearNotifications() { m_notifications.clear(); } void HttpServer::cleanOldMessages() { qint64 currentTime = QDateTime::currentSecsSinceEpoch(); for (int i = m_notifications.size() - 1; i >= 0; --i) { if (currentTime - m_notifications[i].timestamp >= m_notifications[i].duration) { m_notifications.removeAt(i); } } } void HttpServer::onNewConnection() { while (m_server->hasPendingConnections()) { QTcpSocket *socket = m_server->nextPendingConnection(); m_clients.append(socket); // Устанавливаем keep-alive connect(socket, &QTcpSocket::readyRead, this, &HttpServer::readClient); connect(socket, &QTcpSocket::disconnected, this, &HttpServer::discardClient); } } void HttpServer::readClient() { QTcpSocket *socket = qobject_cast(sender()); if (!socket) return; QByteArray requestData = socket->readAll(); QString request = QString::fromUtf8(requestData); if (!request.isEmpty()) { processRequest(socket, request); } } void HttpServer::processRequest(QTcpSocket *socket, const QString &request) { QStringList lines = request.split("\r\n"); if (lines.isEmpty()) return; QString firstLine = lines[0]; QStringList parts = firstLine.split(" "); if (parts.size() < 2) return; QString method = parts[0]; QString path = parts[1]; if (method == "GET") { if (path == "/" || path == "/index.html") { sendHtmlPage(socket); } else if (path == "/messages") { sendJSONMessages(socket); } else if (path.startsWith("/sounds/")) { QString filePath = path.mid(1); // Убираем первый слэш serveStaticFile(socket, filePath); } else if (path.startsWith("/imgs/")) { QString filePath = path.mid(1); serveStaticFile(socket, filePath); } else if (path.startsWith("/fonts/")) { QString filePath = path.mid(1); serveStaticFile(socket, filePath); } else if (path == "/clear") { clearNotifications(); sendResponse(socket, "text/html", "Notifications cleared"); } else { // Игнорируем favicon и другие запросы if (!path.contains("favicon.ico") && !path.contains(".well-known")) { } sendResponse(socket, "text/html", "", 404); } } } void HttpServer::sendHtmlPage(QTcpSocket *socket) { QString html = generateHTML(); sendResponse(socket, "text/html", html); } void HttpServer::sendJSONMessages(QTcpSocket *socket) { QString json = generateJSON(); // Убедимся, что это валидный JSON QJsonParseError parseError; QJsonDocument::fromJson(json.toUtf8(), &parseError); if (parseError.error != QJsonParseError::NoError) { qWarning() << "Ошибка парсинга JSON:" << parseError.errorString(); json = "[]"; // Отправляем пустой массив при ошибке } sendResponse(socket, "application/json; charset=utf-8", json); } void HttpServer::serveStaticFile(QTcpSocket *socket, const QString &filePath) { QString fullPath = QCoreApplication::applicationDirPath() + "/" + filePath; QFile file(fullPath); if (file.exists() && file.open(QIODevice::ReadOnly)) { QString mimeType = getMimeType(filePath); QByteArray content = file.readAll(); file.close(); QString response = QString( "HTTP/1.1 200 OK\r\n" "Content-Type: %1\r\n" "Content-Length: %2\r\n" "Cache-Control: no-cache\r\n" "Connection: close\r\n" "\r\n") .arg(mimeType) .arg(content.size()); socket->write(response.toUtf8()); socket->write(content); socket->flush(); } else { sendResponse(socket, "text/html", "

404 Not Found

" + filePath + "

", 404); } } QString HttpServer::generateHTML() { QString html = "\n" "\n" "\n" "\n" "Web Notifications\n" "\n" "\n" "\n" "
\n" "\n" "\n" ""; return html.arg(m_pageBackgroundColor); // Подставляем цвет фона } QString HttpServer::generateJSON() { cleanOldMessages(); QJsonArray jsonArray; for (const Notification ¬if : m_notifications) { QJsonObject obj; obj["nickname"] = notif.nickname; // Исправляем пути - добавляем слеш в начале if (!notif.url.isEmpty()) { obj["url"] = notif.url.startsWith("/") ? notif.url : "/" + notif.url; } else { obj["url"] = ""; } obj["content"] = notif.content; obj["timestamp"] = notif.timestamp; // Исправляем путь к звуку if (!notif.soundURL.isEmpty()) { obj["sound"] = notif.soundURL.startsWith("/") ? notif.soundURL : "/" + notif.soundURL; } else { obj["sound"] = ""; } obj["duration"] = notif.duration; obj["color"] = notif.blockColor; obj["borderColor"] = notif.borderColor; obj["borderSize"] = notif.borderSize; obj["titleColor"] = notif.titleColor; obj["titleFamily"] = notif.titleFamily; obj["titleSize"] = notif.titleSize; obj["contentColor"] = notif.contentColor; obj["contentFamily"] = notif.contentFamily; obj["contentSize"] = notif.contentSize; jsonArray.append(obj); } QJsonDocument doc(jsonArray); QString jsonStr = QString::fromUtf8(doc.toJson()); return jsonStr; } void HttpServer::sendResponse(QTcpSocket *socket, const QString &contentType, const QString &content, int statusCode) { QString statusText; switch(statusCode) { case 200: statusText = "OK"; break; case 404: statusText = "Not Found"; break; default: statusText = "OK"; break; } QByteArray responseData = QString( "HTTP/1.1 %1 %2\r\n" "Content-Type: %3\r\n" "Content-Length: %4\r\n" "Cache-Control: no-cache, no-store, must-revalidate\r\n" "Access-Control-Allow-Origin: *\r\n" "Connection: keep-alive\r\n" "\r\n" "%5") .arg(statusCode) .arg(statusText) .arg(contentType) .arg(content.toUtf8().size()) .arg(content) .toUtf8(); socket->write(responseData); // Не закрываем соединение сразу для keep-alive if (contentType == "text/html" || contentType.startsWith("application/json")) { // Для HTML и JSON оставляем соединение открытым socket->flush(); } else { // Для статических файлов закрываем после отправки socket->flush(); socket->close(); } } QString HttpServer::getMimeType(const QString &filePath) { if (filePath.endsWith(".html")) return "text/html"; if (filePath.endsWith(".css")) return "text/css"; if (filePath.endsWith(".js")) return "application/javascript"; if (filePath.endsWith(".png")) return "image/png"; if (filePath.endsWith(".jpg") || filePath.endsWith(".jpeg")) return "image/jpeg"; if (filePath.endsWith(".gif")) return "image/gif"; if (filePath.endsWith(".svg")) return "image/svg+xml"; if (filePath.endsWith(".ico")) return "image/x-icon"; if (filePath.endsWith(".ttf")) return "font/ttf"; if (filePath.endsWith(".otf")) return "font/otf"; if (filePath.endsWith(".woff")) return "font/woff"; if (filePath.endsWith(".woff2")) return "font/woff2"; if (filePath.endsWith(".mp3")) return "audio/mpeg"; if (filePath.endsWith(".wav")) return "audio/wav"; if (filePath.endsWith(".ogg")) return "audio/ogg"; return "text/plain"; } void HttpServer::discardClient() { QTcpSocket *socket = qobject_cast(sender()); if (socket) { m_clients.removeOne(socket); socket->deleteLater(); } }