mirror of
https://github.com/PrismLauncher/PrismLauncher
synced 2026-04-23 09:05:03 +00:00
feat: Auto handle Http 429 Too Many Requests with retry
- Must be explicitly enabled for a request - Uses Retry-After Header if present, falls back to exponential back off starting with 10 seconds - if retry delay is greater than 1 minute or it retries more than 3 times then fail with a "Rate Limited" reason - Sets task status to inform user of retry. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
parent
9171e2b2e1
commit
e8da9ee4fb
|
|
@ -34,6 +34,7 @@ void EntitlementsStep::perform()
|
||||||
m_response.reset(new QByteArray());
|
m_response.reset(new QByteArray());
|
||||||
m_request = Net::Download::makeByteArray(url, m_response.get());
|
m_request = Net::Download::makeByteArray(url, m_response.get());
|
||||||
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
||||||
|
m_request->enableAutoRetry(true);
|
||||||
|
|
||||||
m_task.reset(new NetJob("EntitlementsStep", APPLICATION->network()));
|
m_task.reset(new NetJob("EntitlementsStep", APPLICATION->network()));
|
||||||
m_task->setAskRetry(false);
|
m_task->setAskRetry(false);
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ void GetSkinStep::perform()
|
||||||
|
|
||||||
m_response.reset(new QByteArray());
|
m_response.reset(new QByteArray());
|
||||||
m_request = Net::Download::makeByteArray(url, m_response.get());
|
m_request = Net::Download::makeByteArray(url, m_response.get());
|
||||||
|
m_request->enableAutoRetry(true);
|
||||||
|
|
||||||
m_task.reset(new NetJob("GetSkinStep", APPLICATION->network()));
|
m_task.reset(new NetJob("GetSkinStep", APPLICATION->network()));
|
||||||
m_task->setAskRetry(false);
|
m_task->setAskRetry(false);
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ void LauncherLoginStep::perform()
|
||||||
m_response.reset(new QByteArray());
|
m_response.reset(new QByteArray());
|
||||||
m_request = Net::Upload::makeByteArray(url, m_response.get(), requestBody.toUtf8());
|
m_request = Net::Upload::makeByteArray(url, m_response.get(), requestBody.toUtf8());
|
||||||
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
||||||
|
m_request->enableAutoRetry(true);
|
||||||
|
|
||||||
m_task.reset(new NetJob("LauncherLoginStep", APPLICATION->network()));
|
m_task.reset(new NetJob("LauncherLoginStep", APPLICATION->network()));
|
||||||
m_task->setAskRetry(false);
|
m_task->setAskRetry(false);
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ void MSADeviceCodeStep::perform()
|
||||||
m_response.reset(new QByteArray());
|
m_response.reset(new QByteArray());
|
||||||
m_request = Net::Upload::makeByteArray(url, m_response.get(), payload);
|
m_request = Net::Upload::makeByteArray(url, m_response.get(), payload);
|
||||||
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
||||||
|
m_request->enableAutoRetry(true);
|
||||||
|
|
||||||
m_task.reset(new NetJob("MSADeviceCodeStep", APPLICATION->network()));
|
m_task.reset(new NetJob("MSADeviceCodeStep", APPLICATION->network()));
|
||||||
m_task->setAskRetry(false);
|
m_task->setAskRetry(false);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ void MinecraftProfileStep::perform()
|
||||||
m_response.reset(new QByteArray());
|
m_response.reset(new QByteArray());
|
||||||
m_request = Net::Download::makeByteArray(url, m_response.get());
|
m_request = Net::Download::makeByteArray(url, m_response.get());
|
||||||
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
||||||
|
m_request->enableAutoRetry(true);
|
||||||
|
|
||||||
m_task.reset(new NetJob("MinecraftProfileStep", APPLICATION->network()));
|
m_task.reset(new NetJob("MinecraftProfileStep", APPLICATION->network()));
|
||||||
m_task->setAskRetry(false);
|
m_task->setAskRetry(false);
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ void XboxAuthorizationStep::perform()
|
||||||
m_response.reset(new QByteArray());
|
m_response.reset(new QByteArray());
|
||||||
m_request = Net::Upload::makeByteArray(url, m_response.get(), xbox_auth_data.toUtf8());
|
m_request = Net::Upload::makeByteArray(url, m_response.get(), xbox_auth_data.toUtf8());
|
||||||
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
||||||
|
m_request->enableAutoRetry(true);
|
||||||
|
|
||||||
m_task.reset(new NetJob("XboxAuthorizationStep", APPLICATION->network()));
|
m_task.reset(new NetJob("XboxAuthorizationStep", APPLICATION->network()));
|
||||||
m_task->setAskRetry(false);
|
m_task->setAskRetry(false);
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ void XboxUserStep::perform()
|
||||||
m_response.reset(new QByteArray());
|
m_response.reset(new QByteArray());
|
||||||
m_request = Net::Upload::makeByteArray(url, m_response.get(), xbox_auth_data.toUtf8());
|
m_request = Net::Upload::makeByteArray(url, m_response.get(), xbox_auth_data.toUtf8());
|
||||||
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
|
||||||
|
m_request->enableAutoRetry(true);
|
||||||
|
|
||||||
m_task.reset(new NetJob("XboxUserStep", APPLICATION->network()));
|
m_task.reset(new NetJob("XboxUserStep", APPLICATION->network()));
|
||||||
m_task->setAskRetry(false);
|
m_task->setAskRetry(false);
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,10 @@
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QLocale>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
|
|
@ -55,6 +57,11 @@
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
|
NetRequest::NetRequest() : Task()
|
||||||
|
{
|
||||||
|
connect(&m_retryTimer, &QTimer::timeout, this, &NetRequest::executeTask);
|
||||||
|
}
|
||||||
|
|
||||||
void NetRequest::addValidator(Validator* v)
|
void NetRequest::addValidator(Validator* v)
|
||||||
{
|
{
|
||||||
m_sink->addValidator(v);
|
m_sink->addValidator(v);
|
||||||
|
|
@ -161,6 +168,20 @@ void NetRequest::downloadError(QNetworkReply::NetworkError error)
|
||||||
if (error == QNetworkReply::OperationCanceledError) {
|
if (error == QNetworkReply::OperationCanceledError) {
|
||||||
qCCritical(logCat) << getUid().toString() << "Aborted" << m_url.toString();
|
qCCritical(logCat) << getUid().toString() << "Aborted" << m_url.toString();
|
||||||
m_state = State::Failed;
|
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 {
|
} else {
|
||||||
if (m_options & Option::AcceptLocalFiles) {
|
if (m_options & Option::AcceptLocalFiles) {
|
||||||
if (m_sink->hasLocalData()) {
|
if (m_sink->hasLocalData()) {
|
||||||
|
|
@ -182,7 +203,8 @@ void NetRequest::sslErrors(const QList<QSslError>& errors)
|
||||||
{
|
{
|
||||||
int i = 1;
|
int i = 1;
|
||||||
for (auto error : errors) {
|
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();
|
auto cert = error.certificate();
|
||||||
qCCritical(logCat) << getUid().toString() << "Certificate in question:\n" << cert.toText();
|
qCCritical(logCat) << getUid().toString() << "Certificate in question:\n" << cert.toText();
|
||||||
i++;
|
i++;
|
||||||
|
|
@ -243,8 +265,33 @@ auto NetRequest::handleRedirect() -> bool
|
||||||
return true;
|
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()
|
void NetRequest::downloadFinished()
|
||||||
{
|
{
|
||||||
|
// currently waiting for retry
|
||||||
|
if (m_retryTimer.isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// handle HTTP redirection first
|
// handle HTTP redirection first
|
||||||
if (handleRedirect()) {
|
if (handleRedirect()) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request redirected:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request redirected:" << m_url.toString();
|
||||||
|
|
@ -351,4 +398,14 @@ QString NetRequest::errorString() const
|
||||||
{
|
{
|
||||||
return m_reply ? m_reply->errorString() : "";
|
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
|
} // namespace Net
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
|
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QTimer>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#include "HeaderProxy.h"
|
#include "HeaderProxy.h"
|
||||||
|
|
@ -55,11 +56,11 @@ namespace Net {
|
||||||
class NetRequest : public Task {
|
class NetRequest : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
protected:
|
protected:
|
||||||
explicit NetRequest() : Task() {}
|
explicit NetRequest();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using Ptr = shared_qobject_ptr<class NetRequest>;
|
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)
|
Q_DECLARE_FLAGS(Options, Option)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -71,6 +72,9 @@ class NetRequest : public Task {
|
||||||
void setNetwork(QNetworkAccessManager* network) { m_network = network; }
|
void setNetwork(QNetworkAccessManager* network) { m_network = network; }
|
||||||
void addHeaderProxy(std::unique_ptr<Net::HeaderProxy> proxy) { m_headerProxies.push_back(std::move(proxy)); }
|
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;
|
QUrl url() const;
|
||||||
void setUrl(QUrl url) { m_url = url; }
|
void setUrl(QUrl url) { m_url = url; }
|
||||||
int replyStatusCode() const;
|
int replyStatusCode() const;
|
||||||
|
|
@ -79,6 +83,7 @@ class NetRequest : public Task {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto handleRedirect() -> bool;
|
auto handleRedirect() -> bool;
|
||||||
|
void handleAutoRetry(int64_t delay);
|
||||||
virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
|
virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
|
|
@ -109,6 +114,9 @@ class NetRequest : public Task {
|
||||||
/// source URL
|
/// source URL
|
||||||
QUrl m_url;
|
QUrl m_url;
|
||||||
std::vector<std::unique_ptr<Net::HeaderProxy>> m_headerProxies;
|
std::vector<std::unique_ptr<Net::HeaderProxy>> m_headerProxies;
|
||||||
|
|
||||||
|
int m_retryCount = 0;
|
||||||
|
QTimer m_retryTimer;
|
||||||
};
|
};
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue