Fix invalid STD retry on 429 without json body

It's getting more and more complicated... We have places where we accept errors but still want relevant JSON body (most of the Steam error-places), and now we also have a place where we expected error to not carry one. Moreover, we still want to account for invalid JSON body on 2xx and retry on them.

So let's make the code even more complicated than it already is by adding yet another endpoint that does exactly the same what the other endpoint does BUT allows us us to optionally accept null/invalid body on success/errors/both, lol. I hate myself.

Maybe we can obsolete the first endpoint eventually and stick with just the second?
This commit is contained in:
JustArchi
2022-05-28 20:41:52 +02:00
parent df95b82b10
commit 6178b12bb1
3 changed files with 139 additions and 15 deletions

View File

@@ -530,7 +530,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.SubmissionInProgress, appTokens.Count, packageTokens.Count, depotKeys.Count));
ObjectResponse<ResponseData>? response = await ASF.WebBrowser.UrlPostToJsonObject<ResponseData, RequestData>(request, data: requestData, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
OptionalObjectResponse<ResponseData>? response = await ASF.WebBrowser.UrlPostToOptionalJsonObject<ResponseData, RequestData>(request, data: requestData, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false);
if (response == null) {
ASF.ArchiLogger.LogGenericWarning(ArchiSteamFarm.Localization.Strings.WarningFailed);
@@ -564,7 +564,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
return;
}
if (!response.Content.Success) {
if (response.Content is not { Success: true }) {
ASF.ArchiLogger.LogGenericError(ArchiSteamFarm.Localization.Strings.WarningFailed);
return;

View File

@@ -0,0 +1,38 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// |
// http://www.apache.org/licenses/LICENSE-2.0
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Web.Responses;
public sealed class OptionalObjectResponse<T> : BasicResponse {
[PublicAPI]
public T? Content { get; }
public OptionalObjectResponse(BasicResponse basicResponse, T? content) : base(basicResponse) {
ArgumentNullException.ThrowIfNull(basicResponse);
Content = content;
}
public OptionalObjectResponse(BasicResponse basicResponse) : base(basicResponse) => ArgumentNullException.ThrowIfNull(basicResponse);
}

View File

@@ -331,12 +331,6 @@ public sealed class WebBrowser : IDisposable {
JsonSerializer serializer = new();
obj = serializer.Deserialize<T>(jsonReader);
if (obj is null) {
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj)));
continue;
}
} catch (Exception e) {
ArchiLogger.LogGenericWarningException(e);
ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request));
@@ -344,6 +338,12 @@ public sealed class WebBrowser : IDisposable {
continue;
}
if (obj is null) {
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj)));
continue;
}
return new ObjectResponse<T>(response, obj);
}
}
@@ -638,12 +638,6 @@ public sealed class WebBrowser : IDisposable {
JsonSerializer serializer = new();
obj = serializer.Deserialize<TResult>(jsonReader);
if (obj is null) {
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj)));
continue;
}
} catch (Exception e) {
ArchiLogger.LogGenericWarningException(e);
ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request));
@@ -651,6 +645,12 @@ public sealed class WebBrowser : IDisposable {
continue;
}
if (obj is null) {
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj)));
continue;
}
return new ObjectResponse<TResult>(response, obj);
}
}
@@ -663,6 +663,90 @@ public sealed class WebBrowser : IDisposable {
return null;
}
[PublicAPI]
public async Task<OptionalObjectResponse<TResult>?> UrlPostToOptionalJsonObject<TResult, TData>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, TData? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) where TData : class {
ArgumentNullException.ThrowIfNull(request);
if (maxTries == 0) {
throw new ArgumentOutOfRangeException(nameof(maxTries));
}
if (rateLimitingDelay < 0) {
throw new ArgumentOutOfRangeException(nameof(rateLimitingDelay));
}
for (byte i = 0; i < maxTries; i++) {
if ((i > 0) && (rateLimitingDelay > 0)) {
await Task.Delay(rateLimitingDelay).ConfigureAwait(false);
}
StreamResponse? response = await UrlPostToStream(request, headers, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay).ConfigureAwait(false);
if (response == null) {
// Request timed out, try again
continue;
}
await using (response.ConfigureAwait(false)) {
if (response.StatusCode.IsRedirectionCode()) {
if (!requestOptions.HasFlag(ERequestOptions.ReturnRedirections)) {
// We're not handling this error, do not try again
break;
}
} else if (response.StatusCode.IsClientErrorCode()) {
if (!requestOptions.HasFlag(ERequestOptions.ReturnClientErrors)) {
// We're not handling this error, do not try again
break;
}
} else if (response.StatusCode.IsServerErrorCode()) {
if (!requestOptions.HasFlag(ERequestOptions.ReturnServerErrors)) {
// We're not handling this error, try again
continue;
}
}
TResult? obj;
try {
using StreamReader steamReader = new(response.Content);
using JsonReader jsonReader = new JsonTextReader(steamReader);
JsonSerializer serializer = new();
obj = serializer.Deserialize<TResult>(jsonReader);
} catch (Exception e) {
if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) {
return new OptionalObjectResponse<TResult>(response);
}
ArchiLogger.LogGenericWarningException(e);
ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request));
continue;
}
if (obj is null) {
if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) {
return new OptionalObjectResponse<TResult>(response);
}
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj)));
continue;
}
return new OptionalObjectResponse<TResult>(response, obj);
}
}
if (maxTries > 1) {
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, maxTries));
ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request));
}
return null;
}
[PublicAPI]
public async Task<StreamResponse?> UrlPostToStream<T>(Uri request, IReadOnlyCollection<KeyValuePair<string, string>>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) where T : class {
ArgumentNullException.ThrowIfNull(request);
@@ -925,6 +1009,8 @@ public sealed class WebBrowser : IDisposable {
None = 0,
ReturnClientErrors = 1,
ReturnServerErrors = 2,
ReturnRedirections = 4
ReturnRedirections = 4,
AllowInvalidBodyOnSuccess = 8,
AllowInvalidBodyOnErrors = 16
}
}