feat: Auto handle Http 429 Too Many Requests with retry (#4946)

This commit is contained in:
DioEgizio 2026-02-06 16:59:29 +00:00 committed by GitHub
commit 0229c0fd0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 75 additions and 3 deletions

View file

@ -34,6 +34,7 @@ void EntitlementsStep::perform()
m_response.reset(new QByteArray());
m_request = Net::Download::makeByteArray(url, m_response.get());
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
m_request->enableAutoRetry(true);
m_task.reset(new NetJob("EntitlementsStep", APPLICATION->network()));
m_task->setAskRetry(false);

View file

@ -18,6 +18,7 @@ void GetSkinStep::perform()
m_response.reset(new QByteArray());
m_request = Net::Download::makeByteArray(url, m_response.get());
m_request->enableAutoRetry(true);
m_task.reset(new NetJob("GetSkinStep", APPLICATION->network()));
m_task->setAskRetry(false);

View file

@ -39,6 +39,7 @@ void LauncherLoginStep::perform()
m_response.reset(new QByteArray());
m_request = Net::Upload::makeByteArray(url, m_response.get(), requestBody.toUtf8());
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
m_request->enableAutoRetry(true);
m_task.reset(new NetJob("LauncherLoginStep", APPLICATION->network()));
m_task->setAskRetry(false);

View file

@ -69,6 +69,7 @@ void MSADeviceCodeStep::perform()
m_response.reset(new QByteArray());
m_request = Net::Upload::makeByteArray(url, m_response.get(), payload);
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
m_request->enableAutoRetry(true);
m_task.reset(new NetJob("MSADeviceCodeStep", APPLICATION->network()));
m_task->setAskRetry(false);

View file

@ -24,6 +24,7 @@ void MinecraftProfileStep::perform()
m_response.reset(new QByteArray());
m_request = Net::Download::makeByteArray(url, m_response.get());
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
m_request->enableAutoRetry(true);
m_task.reset(new NetJob("MinecraftProfileStep", APPLICATION->network()));
m_task->setAskRetry(false);

View file

@ -45,6 +45,7 @@ void XboxAuthorizationStep::perform()
m_response.reset(new QByteArray());
m_request = Net::Upload::makeByteArray(url, m_response.get(), xbox_auth_data.toUtf8());
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
m_request->enableAutoRetry(true);
m_task.reset(new NetJob("XboxAuthorizationStep", APPLICATION->network()));
m_task->setAskRetry(false);

View file

@ -40,6 +40,7 @@ void XboxUserStep::perform()
m_response.reset(new QByteArray());
m_request = Net::Upload::makeByteArray(url, m_response.get(), xbox_auth_data.toUtf8());
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
m_request->enableAutoRetry(true);
m_task.reset(new NetJob("XboxUserStep", APPLICATION->network()));
m_task->setAskRetry(false);

View file

@ -41,8 +41,10 @@
#include <QDateTime>
#include <QFileInfo>
#include <QLocale>
#include <QNetworkReply>
#include <QUrl>
#include <cstdint>
#include <memory>
#if defined(LAUNCHER_APPLICATION)
@ -55,6 +57,11 @@
namespace Net {
NetRequest::NetRequest() : Task()
{
connect(&m_retryTimer, &QTimer::timeout, this, &NetRequest::executeTask);
}
void NetRequest::addValidator(Validator* v)
{
m_sink->addValidator(v);
@ -161,6 +168,20 @@ void NetRequest::downloadError(QNetworkReply::NetworkError error)
if (error == QNetworkReply::OperationCanceledError) {
qCCritical(logCat) << getUid().toString() << "Aborted" << m_url.toString();
m_state = State::Failed;
} else if (replyStatusCode() == 429 /* HTTP Too Many Requests*/ && m_options & Option::AutoRetry) {
qCDebug(logCat) << getUid().toString() << "Rate Limited!";
int64_t delay = 10 * std::pow(2, m_retryCount);
if (m_reply->hasRawHeader("Retry-After")) {
auto retryAfter = m_reply->rawHeader("Retry-After");
if (retryAfter.trimmed().endsWith("GMT")) /* HTTP Date format */ {
auto afterTimestamp = QDateTime::fromString(QString::fromUtf8(retryAfter.trimmed()), "ddd, dd MMM yyyy HH:mm:ss 'GMT'");
auto now = QDateTime::currentDateTime();
delay = now.secsTo(afterTimestamp);
} else {
delay = retryAfter.toLong();
}
}
handleAutoRetry(delay);
} else {
if (m_options & Option::AcceptLocalFiles) {
if (m_sink->hasLocalData()) {
@ -182,7 +203,8 @@ void NetRequest::sslErrors(const QList<QSslError>& errors)
{
int i = 1;
for (auto error : errors) {
qCCritical(logCat).nospace() << getUid().toString() << " Request " << m_url.toString() << " SSL Error #" << i << ": " << error.errorString();
qCCritical(logCat).nospace() << getUid().toString() << " Request " << m_url.toString() << " SSL Error #" << i << ": "
<< error.errorString();
auto cert = error.certificate();
qCCritical(logCat) << getUid().toString() << "Certificate in question:\n" << cert.toText();
i++;
@ -243,8 +265,33 @@ auto NetRequest::handleRedirect() -> bool
return true;
}
void NetRequest::handleAutoRetry(int64_t delay)
{
m_retryCount++;
if (delay > 60 || m_retryCount > 4) {
/* 1 minute is too long to wait for retry, fail for now */
m_state = State::Failed;
auto retryAfter = QDateTime::currentDateTime().addSecs(delay);
emitFailed(tr("Request Rate Limited for %n second(s): Retry After %1", "seconds", delay)
.arg(retryAfter.toLocalTime().toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat))));
return;
} else {
qCDebug(logCat) << getUid().toString() << "Retyring Request in" << delay << "seconds";
setStatus(tr("Rate Limited: Waiting %n second(s)", "seconds", delay));
m_retryTimer.setTimerType(Qt::VeryCoarseTimer);
m_retryTimer.setSingleShot(true);
m_retryTimer.setInterval(delay * 1000);
m_retryTimer.start();
}
}
void NetRequest::downloadFinished()
{
// currently waiting for retry
if (m_retryTimer.isActive()) {
return;
}
// handle HTTP redirection first
if (handleRedirect()) {
qCDebug(logCat) << getUid().toString() << "Request redirected:" << m_url.toString();
@ -351,4 +398,14 @@ QString NetRequest::errorString() const
{
return m_reply ? m_reply->errorString() : "";
}
void NetRequest::enableAutoRetry(bool enable)
{
if (enable) {
m_options |= Option::AutoRetry;
} else {
m_options &= ~static_cast<int>(Option::AutoRetry);
}
}
} // namespace Net

View file

@ -41,6 +41,7 @@
#include <QNetworkReply>
#include <QUrl>
#include <QTimer>
#include <chrono>
#include "HeaderProxy.h"
@ -55,11 +56,11 @@ namespace Net {
class NetRequest : public Task {
Q_OBJECT
protected:
explicit NetRequest() : Task() {}
explicit NetRequest();
public:
using Ptr = shared_qobject_ptr<class NetRequest>;
enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 };
enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2, AutoRetry = 4 };
Q_DECLARE_FLAGS(Options, Option)
public:
@ -71,6 +72,9 @@ class NetRequest : public Task {
void setNetwork(QNetworkAccessManager* network) { m_network = network; }
void addHeaderProxy(std::unique_ptr<Net::HeaderProxy> proxy) { m_headerProxies.push_back(std::move(proxy)); }
// automatically handle HTTP 429 Too Many Requests errors and retry
void enableAutoRetry(bool enable);
QUrl url() const;
void setUrl(QUrl url) { m_url = url; }
int replyStatusCode() const;
@ -79,6 +83,7 @@ class NetRequest : public Task {
private:
auto handleRedirect() -> bool;
void handleAutoRetry(int64_t delay);
virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
protected slots:
@ -109,6 +114,9 @@ class NetRequest : public Task {
/// source URL
QUrl m_url;
std::vector<std::unique_ptr<Net::HeaderProxy>> m_headerProxies;
int m_retryCount = 0;
QTimer m_retryTimer;
};
} // namespace Net