1
0
forked from BRT/arc
arc/services/net/NetworkErrorUtils.h

117 lines
7.5 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <QNetworkReply>
#include <QString>
// Хелперы для информативного логирования сетевых ошибок. Используются
// и в NetworkService, и в DeviceScreener — категория сразу показывает
// характер сбоя: timeout / refused / DNS / SSL / HTTP-уровень.
namespace NetworkErrorUtils {
// Удаляет схему+хост из URL, оставляет только путь (для логов/UI без секретов).
inline QString sanitizePath(const QString &fullUrl) {
const int schemeEnd = fullUrl.indexOf(QStringLiteral("://"));
if (schemeEnd < 0) return fullUrl;
const int pathStart = fullUrl.indexOf(QLatin1Char('/'), schemeEnd + 3);
return pathStart >= 0 ? fullUrl.mid(pathStart) : QStringLiteral("/");
}
// Категория ошибки в виде короткого ярлыка. См. QNetworkReply::NetworkError.
inline QString networkErrorCategory(QNetworkReply::NetworkError err) {
switch (err) {
case QNetworkReply::NoError: return QStringLiteral("NO_ERROR");
case QNetworkReply::ConnectionRefusedError: return QStringLiteral("CONNECTION_REFUSED");
case QNetworkReply::RemoteHostClosedError: return QStringLiteral("REMOTE_CLOSED");
case QNetworkReply::HostNotFoundError: return QStringLiteral("DNS_FAIL");
case QNetworkReply::TimeoutError: return QStringLiteral("QT_TIMEOUT");
case QNetworkReply::OperationCanceledError: return QStringLiteral("CANCELLED");
case QNetworkReply::SslHandshakeFailedError: return QStringLiteral("SSL_HANDSHAKE");
case QNetworkReply::TemporaryNetworkFailureError: return QStringLiteral("TEMP_NET_FAIL");
case QNetworkReply::NetworkSessionFailedError: return QStringLiteral("NO_NETWORK_SESSION");
case QNetworkReply::BackgroundRequestNotAllowedError: return QStringLiteral("BG_NOT_ALLOWED");
case QNetworkReply::TooManyRedirectsError: return QStringLiteral("TOO_MANY_REDIRECTS");
case QNetworkReply::InsecureRedirectError: return QStringLiteral("INSECURE_REDIRECT");
case QNetworkReply::UnknownNetworkError: return QStringLiteral("UNKNOWN_NETWORK");
case QNetworkReply::ProxyConnectionRefusedError: return QStringLiteral("PROXY_REFUSED");
case QNetworkReply::ProxyConnectionClosedError: return QStringLiteral("PROXY_CLOSED");
case QNetworkReply::ProxyNotFoundError: return QStringLiteral("PROXY_NOT_FOUND");
case QNetworkReply::ProxyTimeoutError: return QStringLiteral("PROXY_TIMEOUT");
case QNetworkReply::ProxyAuthenticationRequiredError: return QStringLiteral("PROXY_AUTH_REQ");
case QNetworkReply::ContentAccessDenied: return QStringLiteral("HTTP_403");
case QNetworkReply::ContentOperationNotPermittedError:return QStringLiteral("OP_NOT_PERMITTED");
case QNetworkReply::ContentNotFoundError: return QStringLiteral("HTTP_404");
case QNetworkReply::AuthenticationRequiredError: return QStringLiteral("HTTP_401");
case QNetworkReply::ContentReSendError: return QStringLiteral("RESEND_REQUIRED");
case QNetworkReply::ContentConflictError: return QStringLiteral("HTTP_409");
case QNetworkReply::ContentGoneError: return QStringLiteral("HTTP_410");
case QNetworkReply::InternalServerError: return QStringLiteral("HTTP_500");
case QNetworkReply::OperationNotImplementedError: return QStringLiteral("HTTP_501");
case QNetworkReply::ServiceUnavailableError: return QStringLiteral("HTTP_503");
case QNetworkReply::UnknownServerError: return QStringLiteral("UNKNOWN_SERVER");
case QNetworkReply::ProtocolUnknownError: return QStringLiteral("PROTO_UNKNOWN");
case QNetworkReply::ProtocolInvalidOperationError: return QStringLiteral("PROTO_INVALID_OP");
case QNetworkReply::UnknownContentError: return QStringLiteral("UNKNOWN_CONTENT");
case QNetworkReply::ProtocolFailure: return QStringLiteral("PROTO_FAILURE");
default: return QStringLiteral("err_%1").arg(static_cast<int>(err));
}
}
// Транзиентная ошибка CONNECT-фазы: сбой произошёл ДО того, как байты запроса
// ушли на сервер (соединение не установилось), поэтому повтор безопасен даже для
// неидемпотентных POST — дубля быть не может. Сюда же ETIMEDOUT-connect, который
// Qt отдаёт как UnknownNetworkError "Connection timed out".
inline bool isConnectPhaseTransient(QNetworkReply::NetworkError err) {
switch (err) {
case QNetworkReply::ConnectionRefusedError:
case QNetworkReply::HostNotFoundError:
case QNetworkReply::ProxyConnectionRefusedError:
case QNetworkReply::ProxyNotFoundError:
case QNetworkReply::ProxyTimeoutError:
case QNetworkReply::UnknownNetworkError:
case QNetworkReply::TemporaryNetworkFailureError:
case QNetworkReply::NetworkSessionFailedError:
return true;
default:
return false;
}
}
// Более широкий набор транзиентных ошибок — безопасен ТОЛЬКО для идемпотентных
// запросов (GET/PATCH/DELETE, аплоады), т.к. сбой мог случиться уже ПОСЛЕ отправки
// тела (сервер мог его обработать) → повтор неидемпотентного POST дал бы дубль.
inline bool isIdempotentTransient(QNetworkReply::NetworkError err) {
if (isConnectPhaseTransient(err)) return true;
switch (err) {
case QNetworkReply::RemoteHostClosedError:
case QNetworkReply::TimeoutError:
case QNetworkReply::ProxyConnectionClosedError:
case QNetworkReply::ServiceUnavailableError:
return true;
default:
return false;
}
}
// Развёрнутое описание сетевой ошибки: HTTP-код + категория или просто
// категория + Qt errorString. Эндпоинт — без хоста.
inline QString describeNetError(QNetworkReply *reply, const QString &path) {
const int httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
const QString endpoint = sanitizePath(path);
const QString category = networkErrorCategory(reply->error());
if (httpCode > 0) {
return QStringLiteral("HTTP %1 [%2] %3").arg(httpCode).arg(category, endpoint);
}
return QStringLiteral("[%1] %2 (qt: %3)").arg(category, endpoint, reply->errorString());
}
// Случай нашего QTimer-таймаута (reply ещё не завершился). reply->error()
// обычно ещё NoError — помечаем категорию явно как CLIENT_TIMEOUT_30s.
inline QString describeClientTimeout(QNetworkReply *reply, const QString &path) {
const QString endpoint = sanitizePath(path);
const QString qtCategory = networkErrorCategory(reply->error());
return QStringLiteral("[CLIENT_TIMEOUT_30s] %1 (qt-state=%2 errorString=\"%3\")")
.arg(endpoint, qtCategory, reply->errorString());
}
} // namespace NetworkErrorUtils