310 lines
8.7 KiB
C++
310 lines
8.7 KiB
C++
#include <Client/HttpRequestManager.h>
|
|
#include <Client/AuthProfile.h>
|
|
|
|
HttpRequestManager& HttpRequestManager::instance()
|
|
{
|
|
static HttpRequestManager instance;
|
|
return instance;
|
|
}
|
|
|
|
HttpRequestManager::HttpRequestManager(QObject* parent)
|
|
: QObject(parent)
|
|
, m_manager(new QNetworkAccessManager(this))
|
|
, m_requestIdCounter(0)
|
|
{
|
|
setupSslConfig();
|
|
}
|
|
|
|
HttpRequestManager::~HttpRequestManager()
|
|
{
|
|
cancelAll();
|
|
}
|
|
|
|
void HttpRequestManager::setupSslConfig()
|
|
{
|
|
m_sslConfig = QSslConfiguration::defaultConfiguration();
|
|
m_sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
|
|
m_sslConfig.setProtocol(QSsl::TlsV1_2OrLater);
|
|
}
|
|
|
|
QString HttpRequestManager::buildFullUrl(const QString& baseUrl, const QString& endpoint) const
|
|
{
|
|
if (endpoint.startsWith("/"))
|
|
return baseUrl + endpoint;
|
|
return baseUrl + "/" + endpoint;
|
|
}
|
|
|
|
QNetworkRequest HttpRequestManager::createRequest(const QString& url, const QString& accessToken)
|
|
{
|
|
QNetworkRequest request{QUrl(url)};
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
request.setSslConfiguration(m_sslConfig);
|
|
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
|
|
|
|
if (!accessToken.isEmpty()) {
|
|
QString bearerToken = "Bearer " + accessToken;
|
|
request.setRawHeader("Authorization", bearerToken.toUtf8());
|
|
}
|
|
|
|
return request;
|
|
}
|
|
|
|
int HttpRequestManager::post(const QString& baseUrl, const QString& endpoint, const QString& accessToken, const QByteArray& jsonData, const HttpCallback &callback, int timeout)
|
|
{
|
|
return postWithRetry(baseUrl, endpoint, accessToken, jsonData, callback, 0, timeout);
|
|
}
|
|
|
|
void HttpRequestManager::postFireAndForget(const QString& baseUrl, const QString& endpoint, const QString& accessToken, const QByteArray& jsonData)
|
|
{
|
|
QString fullUrl = buildFullUrl(baseUrl, endpoint);
|
|
QNetworkRequest request = createRequest(fullUrl, accessToken);
|
|
|
|
QNetworkReply* reply = m_manager->post(request, jsonData);
|
|
if (!reply)
|
|
return;
|
|
|
|
connect(reply, &QNetworkReply::finished, reply, &QObject::deleteLater);
|
|
connect(reply, &QNetworkReply::sslErrors, this, [reply](const QList<QSslError>& errors) {
|
|
Q_UNUSED(errors)
|
|
if (reply)
|
|
reply->ignoreSslErrors();
|
|
});
|
|
}
|
|
|
|
int HttpRequestManager::postWithRetry(const QString& baseUrl, const QString& endpoint, const QString& accessToken, const QByteArray& jsonData, const HttpCallback &callback, const int maxRetries, const int timeout)
|
|
{
|
|
int requestId = ++m_requestIdCounter;
|
|
|
|
QString fullUrl = buildFullUrl(baseUrl, endpoint);
|
|
QNetworkRequest request = createRequest(fullUrl, accessToken);
|
|
|
|
QNetworkReply* reply = m_manager->post(request, jsonData);
|
|
|
|
RequestContext ctx;
|
|
ctx.requestId = requestId;
|
|
ctx.url = fullUrl;
|
|
ctx.accessToken = accessToken;
|
|
ctx.callback = callback;
|
|
ctx.reply = reply;
|
|
ctx.startTime = QDateTime::currentDateTime();
|
|
ctx.retryCount = 0;
|
|
ctx.maxRetries = maxRetries;
|
|
ctx.requestData = jsonData;
|
|
ctx.timeout = timeout;
|
|
|
|
m_pendingRequests[requestId] = ctx;
|
|
m_replyToRequestId[reply] = requestId;
|
|
|
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
|
onReplyFinished(reply);
|
|
});
|
|
connect(reply, &QNetworkReply::sslErrors, this, [this, reply](const QList<QSslError>& errors) {
|
|
onSslErrors(reply, errors);
|
|
});
|
|
|
|
if (timeout > 0) {
|
|
QTimer* timer = new QTimer(this);
|
|
timer->setSingleShot(true);
|
|
timer->setProperty("requestId", requestId);
|
|
connect(timer, &QTimer::timeout, this, &HttpRequestManager::onRequestTimeout);
|
|
m_timeoutTimers[requestId] = timer;
|
|
timer->start(timeout);
|
|
}
|
|
|
|
Q_EMIT requestStarted(requestId);
|
|
|
|
return requestId;
|
|
}
|
|
|
|
bool HttpRequestManager::cancel(int requestId)
|
|
{
|
|
if (!m_pendingRequests.contains(requestId))
|
|
return false;
|
|
|
|
RequestContext& ctx = m_pendingRequests[requestId];
|
|
if (ctx.reply) {
|
|
ctx.reply->abort();
|
|
}
|
|
|
|
cleanupRequest(requestId);
|
|
return true;
|
|
}
|
|
|
|
void HttpRequestManager::cancelAll()
|
|
{
|
|
QList<int> requestIds = m_pendingRequests.keys();
|
|
for (int requestId : requestIds) {
|
|
cancel(requestId);
|
|
}
|
|
}
|
|
|
|
int HttpRequestManager::pendingRequestsCount() const
|
|
{
|
|
return m_pendingRequests.size();
|
|
}
|
|
|
|
void HttpRequestManager::onReplyFinished(QNetworkReply* reply)
|
|
{
|
|
if (!reply)
|
|
return;
|
|
|
|
if (!m_replyToRequestId.contains(reply)) {
|
|
reply->deleteLater();
|
|
return;
|
|
}
|
|
|
|
processResponse(reply);
|
|
}
|
|
|
|
void HttpRequestManager::processResponse(QNetworkReply* reply)
|
|
{
|
|
int requestId = m_replyToRequestId.value(reply, -1);
|
|
if (requestId == -1) {
|
|
reply->deleteLater();
|
|
return;
|
|
}
|
|
|
|
if (!m_pendingRequests.contains(requestId)) {
|
|
reply->deleteLater();
|
|
return;
|
|
}
|
|
|
|
RequestContext ctx = m_pendingRequests[requestId];
|
|
|
|
if (m_timeoutTimers.contains(requestId)) {
|
|
m_timeoutTimers[requestId]->stop();
|
|
}
|
|
|
|
bool success = false;
|
|
QString message;
|
|
QJsonObject jsonResponse;
|
|
|
|
if (reply->error() == QNetworkReply::NoError) {
|
|
QByteArray responseData = reply->readAll();
|
|
QJsonParseError parseError;
|
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &parseError);
|
|
|
|
if (parseError.error == QJsonParseError::NoError && jsonDoc.isObject()) {
|
|
jsonResponse = jsonDoc.object();
|
|
|
|
if (jsonResponse.contains("ok")) {
|
|
success = jsonResponse["ok"].toBool();
|
|
} else {
|
|
success = true;
|
|
}
|
|
|
|
if (jsonResponse.contains("message")) {
|
|
message = jsonResponse["message"].toString();
|
|
}
|
|
} else {
|
|
message = "Invalid JSON response";
|
|
}
|
|
} else if (reply->error() == QNetworkReply::OperationCanceledError) {
|
|
message = "Request canceled";
|
|
} else {
|
|
message = reply->errorString();
|
|
|
|
if (ctx.retryCount < ctx.maxRetries) {
|
|
m_replyToRequestId.remove(reply);
|
|
reply->deleteLater();
|
|
retryRequest(requestId);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ctx.callback) {
|
|
ctx.callback(success, message, jsonResponse);
|
|
}
|
|
|
|
Q_EMIT requestFinished(requestId, success, message);
|
|
|
|
cleanupRequest(requestId);
|
|
reply->deleteLater();
|
|
}
|
|
|
|
void HttpRequestManager::retryRequest(int requestId)
|
|
{
|
|
if (!m_pendingRequests.contains(requestId))
|
|
return;
|
|
|
|
RequestContext& ctx = m_pendingRequests[requestId];
|
|
ctx.retryCount++;
|
|
|
|
QNetworkRequest request = createRequest(ctx.url, ctx.accessToken);
|
|
QNetworkReply* newReply = m_manager->post(request, ctx.requestData);
|
|
|
|
ctx.reply = newReply;
|
|
m_replyToRequestId[newReply] = requestId;
|
|
|
|
connect(newReply, &QNetworkReply::finished, this, [this, newReply]() {
|
|
onReplyFinished(newReply);
|
|
});
|
|
connect(newReply, &QNetworkReply::sslErrors, this, [this, newReply](const QList<QSslError>& errors) {
|
|
onSslErrors(newReply, errors);
|
|
});
|
|
|
|
if (ctx.timeout > 0 && m_timeoutTimers.contains(requestId)) {
|
|
m_timeoutTimers[requestId]->start(ctx.timeout);
|
|
}
|
|
}
|
|
|
|
void HttpRequestManager::cleanupRequest(int requestId)
|
|
{
|
|
if (m_pendingRequests.contains(requestId)) {
|
|
QNetworkReply* reply = m_pendingRequests[requestId].reply;
|
|
if (reply) {
|
|
disconnect(reply, nullptr, this, nullptr);
|
|
m_replyToRequestId.remove(reply);
|
|
}
|
|
m_pendingRequests.remove(requestId);
|
|
}
|
|
|
|
if (m_timeoutTimers.contains(requestId)) {
|
|
QTimer* timer = m_timeoutTimers.take(requestId);
|
|
timer->stop();
|
|
timer->deleteLater();
|
|
}
|
|
}
|
|
|
|
void HttpRequestManager::onSslErrors(QNetworkReply* reply, const QList<QSslError>& errors)
|
|
{
|
|
Q_UNUSED(errors)
|
|
if (reply) {
|
|
reply->ignoreSslErrors();
|
|
}
|
|
}
|
|
|
|
void HttpRequestManager::onRequestTimeout()
|
|
{
|
|
QTimer* timer = qobject_cast<QTimer*>(sender());
|
|
if (!timer)
|
|
return;
|
|
|
|
int requestId = timer->property("requestId").toInt();
|
|
|
|
if (m_pendingRequests.contains(requestId)) {
|
|
RequestContext& ctx = m_pendingRequests[requestId];
|
|
|
|
if (ctx.retryCount < ctx.maxRetries) {
|
|
if (ctx.reply) {
|
|
m_replyToRequestId.remove(ctx.reply);
|
|
ctx.reply->abort();
|
|
ctx.reply->deleteLater();
|
|
ctx.reply = nullptr;
|
|
}
|
|
retryRequest(requestId);
|
|
} else {
|
|
if (ctx.reply) {
|
|
ctx.reply->abort();
|
|
}
|
|
|
|
if (ctx.callback) {
|
|
ctx.callback(false, "Request timeout", QJsonObject());
|
|
}
|
|
|
|
Q_EMIT requestFinished(requestId, false, "Request timeout");
|
|
cleanupRequest(requestId);
|
|
}
|
|
}
|
|
}
|