From 9a5e04d2811ea992b96723a0018562597bb0459f Mon Sep 17 00:00:00 2001 From: dela Date: Mon, 22 Jun 2026 13:48:35 +0800 Subject: [PATCH] fix: keep mpv relay routes isolated --- .../components/mpvhttpstreamrelay.cpp | 62 ++++++++++++++++--- src/qEmbyApp/components/mpvhttpstreamrelay.h | 11 ++++ 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/qEmbyApp/components/mpvhttpstreamrelay.cpp b/src/qEmbyApp/components/mpvhttpstreamrelay.cpp index 6ea08bc..2d3a00a 100644 --- a/src/qEmbyApp/components/mpvhttpstreamrelay.cpp +++ b/src/qEmbyApp/components/mpvhttpstreamrelay.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -55,7 +56,6 @@ QUrl MpvHttpStreamRelay::prepare(const QUrl &targetUrl, const QString &serverId, return {}; } - stop(); if (!m_server->isListening() && !m_server->listen(QHostAddress::LocalHost, 0)) { qWarning() << "[MpvHttpStreamRelay] failed to listen" @@ -66,9 +66,12 @@ QUrl MpvHttpStreamRelay::prepare(const QUrl &targetUrl, const QString &serverId, m_targetUrl = targetUrl; m_serverId = serverId; m_streamToken = QUuid::createUuid().toString(QUuid::WithoutBraces); - m_network->setProxy(proxy); + m_routes.insert(m_streamToken, RouteState{targetUrl, serverId, proxy}); m_bytesRelayedSinceLastTick = 0; - m_speedTimer->start(); + if (!m_speedTimer->isActive()) + { + m_speedTimer->start(); + } QUrl localUrl; localUrl.setScheme(QStringLiteral("http")); @@ -102,6 +105,7 @@ void MpvHttpStreamRelay::stop() Q_EMIT upstreamSpeedChanged(0); } m_connections.clear(); + m_routes.clear(); m_targetUrl.clear(); m_serverId.clear(); m_streamToken.clear(); @@ -176,7 +180,7 @@ void MpvHttpStreamRelay::onSocketDisconnected(QTcpSocket *socket) void MpvHttpStreamRelay::processRequest(QTcpSocket *socket, const QByteArray &requestData) { - if (m_targetUrl.isEmpty() || m_streamToken.isEmpty()) + if (m_routes.isEmpty()) { writeError(socket, 503, "Relay target is not ready"); return; @@ -198,14 +202,24 @@ void MpvHttpStreamRelay::processRequest(QTcpSocket *socket, const QByteArray &re const QByteArray method = requestParts.at(0).toUpper(); const QByteArray path = requestParts.at(1); - const QByteArray expectedPrefix = QByteArray("/") + m_streamToken.toUtf8() + QByteArray("/"); - if (!(method == "GET" || method == "HEAD") || !path.startsWith(expectedPrefix)) + QByteArray requestPath = path; + const int queryStart = requestPath.indexOf('?'); + if (queryStart >= 0) + { + requestPath.truncate(queryStart); + } + const QList pathParts = requestPath.split('/'); + const QByteArray tokenBytes = pathParts.size() >= 2 ? pathParts.at(1) : QByteArray(); + const QString routeToken = QString::fromUtf8(tokenBytes); + const auto routeIt = m_routes.constFind(routeToken); + if (!(method == "GET" || method == "HEAD") || routeToken.isEmpty() || routeIt == m_routes.constEnd()) { writeError(socket, 404, "Not found"); return; } - QNetworkRequest request(m_targetUrl); + const RouteState route = routeIt.value(); + QNetworkRequest request(route.targetUrl); request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); for (int i = 1; i < lines.size(); ++i) @@ -239,12 +253,16 @@ void MpvHttpStreamRelay::processRequest(QTcpSocket *socket, const QByteArray &re { return; } + it->routeToken = routeToken; + it->targetUrl = route.targetUrl; + it->serverId = route.serverId; it->headOnly = method == "HEAD"; + m_network->setProxy(route.proxy); it->reply = it->headOnly ? m_network->head(request) : m_network->get(request); it->reply->setReadBufferSize(kReplyReadBufferBytes); qDebug() << "[MpvHttpStreamRelay] request" - << "| method:" << method << "| target:" << LogRedactionUtils::url(m_targetUrl) + << "| method:" << method << "| target:" << LogRedactionUtils::url(route.targetUrl) << "| range:" << request.rawHeader("Range"); QNetworkReply *reply = it->reply; @@ -293,7 +311,7 @@ void MpvHttpStreamRelay::processRequest(QTcpSocket *socket, const QByteArray &re reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 0) { qWarning() << "[MpvHttpStreamRelay] upstream failed" - << "| target:" << LogRedactionUtils::url(m_targetUrl) + << "| target:" << LogRedactionUtils::url(it->targetUrl) << "| error:" << reply->errorString(); } @@ -475,6 +493,7 @@ void MpvHttpStreamRelay::closeConnection(QTcpSocket *socket) socket->disconnectFromHost(); } socket->deleteLater(); + cleanupUnusedRoutes(); } void MpvHttpStreamRelay::recordRelayedBytes(qint64 bytes) @@ -485,6 +504,31 @@ void MpvHttpStreamRelay::recordRelayedBytes(qint64 bytes) } } +void MpvHttpStreamRelay::cleanupUnusedRoutes() +{ + QStringList activeTokens; + activeTokens.reserve(m_connections.size()); + for (auto it = m_connections.cbegin(); it != m_connections.cend(); ++it) + { + if (!it->routeToken.isEmpty()) + { + activeTokens.append(it->routeToken); + } + } + + for (auto it = m_routes.begin(); it != m_routes.end();) + { + if (it.key() != m_streamToken && !activeTokens.contains(it.key())) + { + it = m_routes.erase(it); + } + else + { + ++it; + } + } +} + QByteArray MpvHttpStreamRelay::reasonPhrase(int statusCode) { switch (statusCode) diff --git a/src/qEmbyApp/components/mpvhttpstreamrelay.h b/src/qEmbyApp/components/mpvhttpstreamrelay.h index 84f9277..2b68a8c 100644 --- a/src/qEmbyApp/components/mpvhttpstreamrelay.h +++ b/src/qEmbyApp/components/mpvhttpstreamrelay.h @@ -29,11 +29,20 @@ private: struct ConnectionState { QByteArray buffer; QNetworkReply *reply = nullptr; + QString routeToken; + QUrl targetUrl; + QString serverId; bool headersSent = false; bool headOnly = false; bool upstreamFinished = false; }; + struct RouteState { + QUrl targetUrl; + QString serverId; + QNetworkProxy proxy; + }; + void onNewConnection(); void onSocketReadyRead(QTcpSocket *socket); void onSocketDisconnected(QTcpSocket *socket); @@ -43,6 +52,7 @@ private: void writeError(QTcpSocket *socket, int statusCode, const QByteArray &message); void closeConnection(QTcpSocket *socket); void recordRelayedBytes(qint64 bytes); + void cleanupUnusedRoutes(); static QByteArray reasonPhrase(int statusCode); static bool isHopByHopHeader(QByteArray name); @@ -51,6 +61,7 @@ private: QNetworkAccessManager *m_network = nullptr; QTimer *m_speedTimer = nullptr; QHash m_connections; + QHash m_routes; QUrl m_targetUrl; QString m_serverId; QString m_streamToken;