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_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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue