From 0c7b0f285e89f1efe015d3319c955d9ebc277ddf Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sat, 3 Sep 2022 23:33:11 +0800 Subject: [PATCH] fix(microsoft): retry 5 times when socket timeout. Closes #1691. --- .../java/org/jackhuang/hmcl/auth/OAuth.java | 4 ++ .../hmcl/auth/microsoft/MicrosoftService.java | 3 + .../jackhuang/hmcl/util/io/HttpRequest.java | 63 ++++++++++++++----- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java index f1d89ba5a..f81e2b345 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java @@ -89,6 +89,7 @@ public class OAuth { pair("grant_type", "authorization_code"), pair("client_secret", options.callback.getClientSecret()), pair("redirect_uri", session.getRedirectURI()), pair("scope", options.scope)) .ignoreHttpCode() + .retry(5) .getJson(AuthorizationResponse.class); handleErrorResponse(response); return new Result(response.accessToken, response.refreshToken); @@ -98,6 +99,7 @@ public class OAuth { DeviceTokenResponse deviceTokenResponse = HttpRequest.POST(deviceCodeURL) .form(pair("client_id", options.callback.getClientId()), pair("scope", options.scope)) .ignoreHttpCode() + .retry(5) .getJson(DeviceTokenResponse.class); handleErrorResponse(deviceTokenResponse); @@ -124,6 +126,7 @@ public class OAuth { pair("code", deviceTokenResponse.deviceCode), pair("client_id", options.callback.getClientId())) .ignoreHttpCode() + .retry(5) .getJson(TokenResponse.class); if ("authorization_pending".equals(tokenResponse.error)) { @@ -158,6 +161,7 @@ public class OAuth { .form(query) .accept("application/json") .ignoreHttpCode() + .retry(5) .getJson(RefreshResponse.class); handleErrorResponse(response); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java index 0cc18fe15..ca2f3b1a1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java @@ -119,6 +119,7 @@ public class MicrosoftService { mapOf(pair("AuthMethod", "RPS"), pair("SiteName", "user.auth.xboxlive.com"), pair("RpsTicket", "d=" + liveAccessToken))), pair("RelyingParty", "http://auth.xboxlive.com"), pair("TokenType", "JWT"))) + .retry(5) .accept("application/json").getJson(XBoxLiveAuthenticationResponse.class); String uhs = getUhs(xboxResponse, null); @@ -132,6 +133,7 @@ public class MicrosoftService { pair("UserTokens", Collections.singletonList(xboxResponse.token)))), pair("RelyingParty", "rp://api.minecraftservices.com/"), pair("TokenType", "JWT"))) .ignoreHttpErrorCode(401) + .retry(5) .getJson(XBoxLiveAuthenticationResponse.class); getUhs(minecraftXstsResponse, uhs); @@ -140,6 +142,7 @@ public class MicrosoftService { MinecraftLoginWithXBoxResponse minecraftResponse = HttpRequest .POST("https://api.minecraftservices.com/authentication/login_with_xbox") .json(mapOf(pair("identityToken", "XBL3.0 x=" + uhs + ";" + minecraftXstsResponse.token))) + .retry(5) .accept("application/json").getJson(MinecraftLoginWithXBoxResponse.class); long notAfter = minecraftResponse.expiresIn * 1000L + System.currentTimeMillis(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java index ca5e734bb..913f56531 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java @@ -21,6 +21,7 @@ import com.google.gson.JsonParseException; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.function.ExceptionalBiConsumer; +import org.jackhuang.hmcl.util.function.ExceptionalSupplier; import org.jackhuang.hmcl.util.gson.JsonUtils; import java.io.IOException; @@ -28,6 +29,7 @@ import java.io.OutputStream; import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.SocketTimeoutException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.HashMap; @@ -49,6 +51,7 @@ public abstract class HttpRequest { protected final Map headers = new HashMap<>(); protected ExceptionalBiConsumer responseCodeTester; protected final Set toleratedHttpCodes = new HashSet<>(); + protected int retryTimes = 1; protected boolean ignoreHttpCode; private HttpRequest(String url, String method) { @@ -82,6 +85,14 @@ public abstract class HttpRequest { return this; } + public HttpRequest retry(int retryTimes) { + if (retryTimes < 1) { + throw new IllegalArgumentException("retryTimes >= 1"); + } + this.retryTimes = retryTimes; + return this; + } + public abstract String getString() throws IOException; public CompletableFuture getStringAsync() { @@ -129,9 +140,11 @@ public abstract class HttpRequest { } public String getString() throws IOException { - HttpURLConnection con = createConnection(); - con = resolveConnection(con); - return IOUtils.readFullyAsString(con.getInputStream(), StandardCharsets.UTF_8); + return getStringWithRetry(() -> { + HttpURLConnection con = createConnection(); + con = resolveConnection(con); + return IOUtils.readFullyAsString(con.getInputStream(), StandardCharsets.UTF_8); + }, retryTimes); } } @@ -168,25 +181,27 @@ public abstract class HttpRequest { } public String getString() throws IOException { - HttpURLConnection con = createConnection(); - con.setDoOutput(true); + return getStringWithRetry(() -> { + HttpURLConnection con = createConnection(); + con.setDoOutput(true); - try (OutputStream os = con.getOutputStream()) { - os.write(bytes); - } + try (OutputStream os = con.getOutputStream()) { + os.write(bytes); + } - if (responseCodeTester != null) { - responseCodeTester.accept(new URL(url), con.getResponseCode()); - } else { - if (con.getResponseCode() / 100 != 2) { - if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) { - String data = NetworkUtils.readData(con); - throw new ResponseCodeException(new URL(url), con.getResponseCode(), data); + if (responseCodeTester != null) { + responseCodeTester.accept(new URL(url), con.getResponseCode()); + } else { + if (con.getResponseCode() / 100 != 2) { + if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) { + String data = NetworkUtils.readData(con); + throw new ResponseCodeException(new URL(url), con.getResponseCode(), data); + } } } - } - return NetworkUtils.readData(con); + return NetworkUtils.readData(con); + }, retryTimes); } } @@ -203,6 +218,20 @@ public abstract class HttpRequest { return new HttpPostRequest(url); } + private static String getStringWithRetry(ExceptionalSupplier supplier, int retryTimes) throws IOException { + SocketTimeoutException exception = null; + for (int i = 0; i < retryTimes; i++) { + try { + return supplier.get(); + } catch (SocketTimeoutException e) { + exception = e; + } + } + if (exception != null) + throw exception; + throw new IOException("retry 0"); + } + public interface Authorization { String getTokenType();