From 4acbdedf70c8121222cdb665f5c4329a1b2a420e Mon Sep 17 00:00:00 2001 From: SashegDev Date: Thu, 30 Jul 2026 16:09:44 +0000 Subject: [PATCH] =?UTF-8?q?v1.0.14.2=20=E2=80=94=20fix=20JFX=20hang=20(For?= =?UTF-8?q?kJoinPool),=20retry=20logic=20for=20connection=20reset,=20libra?= =?UTF-8?q?ry=20logging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../minecraft/installer/VersionInstaller.java | 23 +- .../zernmc/launcher/ui/jfx/JFXLauncher.java | 2 +- .../zernmc/launcher/utils/ZHttpClient.java | 194 ++++++---- launcher/pom.xml | 356 +++++++++--------- 4 files changed, 318 insertions(+), 257 deletions(-) diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java index 1bdb478..1a88902 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java @@ -65,17 +65,28 @@ public class VersionInstaller { if (versionUrl == null) throw new Exception("Version " + versionId + " not found"); ProgressBar.show("Fetching version info", 0, 1, "files"); - String versionJson = ZHttpClient.getWithSmartProxy(versionUrl); - Files.writeString(versionDir.resolve(versionId + ".json"), versionJson); + String versionJson; + try { + versionJson = ZHttpClient.getWithSmartProxy(versionUrl); + Files.writeString(versionDir.resolve(versionId + ".json"), versionJson); + } catch (Exception e) { + System.err.println(ZAnsi.red("[VERSION] Failed to fetch version info: " + e.getMessage())); + throw e; + } ProgressBar.show("Version info", 1, 1, "files"); JSONObject versionData = new JSONObject(versionJson); // client.jar ProgressBar.show("Downloading client.jar", 0, 1, "files"); - ZHttpClient.downloadFileWithSmartProxy( - versionData.getJSONObject("downloads").getJSONObject("client").getString("url"), - versionDir.resolve(versionId + ".jar")); + try { + ZHttpClient.downloadFileWithSmartProxy( + versionData.getJSONObject("downloads").getJSONObject("client").getString("url"), + versionDir.resolve(versionId + ".jar")); + } catch (Exception e) { + System.err.println(ZAnsi.red("[VERSION] Failed to download client.jar: " + e.getMessage())); + throw e; + } ProgressBar.show("Client.jar", 1, 1, "files"); // Libraries @@ -197,7 +208,7 @@ public class VersionInstaller { try { ZHttpClient.downloadFileWithSmartProxy(url, target); } catch (Exception e) { - // Skip problematic libraries + System.err.println(ZAnsi.yellow("[LIB] Failed to download " + path + ": " + e.getMessage())); } } count++; diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java index 3435bb6..b5c0251 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java @@ -802,7 +802,7 @@ public class JFXLauncher extends Application { setInstallProgressWithStage("Installation failed", 0, 100, "Failed", 0, 1); log("Install error: " + name); } - } catch (Exception e) { + } catch (Throwable e) { log("Install error: " + e.getMessage()); setInstallInProgress(false); setInstallProgressWithStage("Error: " + e.getMessage(), 0, 100, "Error", 0, 1); diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ZHttpClient.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ZHttpClient.java index 3b26cc0..bb12d64 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ZHttpClient.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ZHttpClient.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; public class ZHttpClient { @@ -25,6 +26,7 @@ public class ZHttpClient { private static final HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(15)) .version(HttpClient.Version.HTTP_1_1) + .executor(Executors.newCachedThreadPool()) .build(); private static String BASE_URL = "https://api.zernmc.ru"; @@ -237,6 +239,9 @@ public class ZHttpClient { if (url.contains("maven.neoforged.net")) return ServiceType.NEOFORGE_MAVEN; if (url.contains("google.com")) return ServiceType.GOOGLE; if (url.contains("cloudflare.com")) return ServiceType.CLOUDFLARE; + if (url.contains("libraries.minecraft.net")) return ServiceType.MOJANG_RESOURCES; + if (url.contains("piston-data.mojang.com")) return ServiceType.MOJANG_META; + if (url.contains("repo1.maven.org") || url.contains("repo.maven.apache.org")) return ServiceType.MOJANG_RESOURCES; return null; } @@ -256,10 +261,13 @@ public class ZHttpClient { return cause instanceof java.net.ConnectException || cause instanceof java.net.UnknownHostException || cause instanceof java.nio.channels.ClosedChannelException || + cause instanceof java.net.SocketException || msg.contains("connection") || msg.contains("timeout") || msg.contains("refused") || - msg.contains("closed"); + msg.contains("closed") || + msg.contains("reset") || + msg.contains("abort"); } private static void markServiceAsBlocked(String url) { @@ -278,106 +286,148 @@ public class ZHttpClient { public static String getWithSmartProxy(String url) throws IOException, InterruptedException { if (!shouldUseProxyForUrl(url)) { + int directRetries = 3; + for (int directAttempt = 1; directAttempt <= directRetries; directAttempt++) { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .timeout(Duration.ofSeconds(25)) + .header("User-Agent", "ZernMC-Launcher/1.0") + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == 200) { + directSuccessCount++; + return response.body(); + } + + if (response.statusCode() >= 400) { + throw new IOException("HTTP " + response.statusCode()); + } + } catch (Exception e) { + if (isConnectionError(e)) { + directFailCount++; + if (directAttempt < directRetries) { + System.out.println(ZAnsi.yellow("[NET] Direct retry " + directAttempt + "/" + directRetries + " for " + url.substring(0, Math.min(60, url.length())) + "...")); + try { Thread.sleep(1000 * directAttempt); } catch (InterruptedException ie) { break; } + continue; + } + markServiceAsBlocked(url); + } else { + throw e; + } + } + break; + } + } + + int maxRetries = 3; + for (int attempt = 1; attempt <= maxRetries; attempt++) { try { + String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8); + String proxyUrl = BASE_URL + "/proxy/download?url=" + encodedUrl; + HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .timeout(Duration.ofSeconds(25)) + .uri(URI.create(proxyUrl)) + .timeout(Duration.ofSeconds(40)) .header("User-Agent", "ZernMC-Launcher/1.0") .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() == 200) { - directSuccessCount++; - return response.body(); + if (response.statusCode() != 200) { + throw new IOException("Proxy HTTP " + response.statusCode()); } - if (response.statusCode() >= 400) { - throw new IOException("HTTP " + response.statusCode()); - } + proxySuccessCount++; + return response.body(); + } catch (Exception e) { - if (isConnectionError(e)) { - directFailCount++; - markServiceAsBlocked(url); + if (attempt == maxRetries || !isConnectionError(e)) { + throw new IOException("Failed to fetch data directly or via proxy: " + e.getMessage(), e); } + try { Thread.sleep(1000 * attempt); } catch (InterruptedException ie) { break; } } } - try { - String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8); - String proxyUrl = BASE_URL + "/proxy/download?url=" + encodedUrl; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(proxyUrl)) - .timeout(Duration.ofSeconds(40)) - .header("User-Agent", "ZernMC-Launcher/1.0") - .GET() - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() != 200) { - throw new IOException("Proxy HTTP " + response.statusCode()); - } - - proxySuccessCount++; - return response.body(); - - } catch (Exception e) { - throw new IOException("Failed to fetch data directly or via proxy: " + e.getMessage(), e); - } + throw new IOException("Failed to fetch data: " + url); } public static void downloadFileWithSmartProxy(String url, Path target) throws Exception { if (!shouldUseProxyForUrl(url)) { - try { - HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() - .uri(URI.create(url)) - .timeout(Duration.ofSeconds(40)) - .header("User-Agent", "ZernMC-Launcher/1.0") - .GET(); + int directRetries = 3; + for (int directAttempt = 1; directAttempt <= directRetries; directAttempt++) { + try { + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .uri(URI.create(url)) + .timeout(Duration.ofSeconds(40)) + .header("User-Agent", "ZernMC-Launcher/1.0") + .GET(); - if (url.startsWith(BASE_URL)) { - String accessToken = AuthManager.getAccessToken(); - if (accessToken != null && !accessToken.equals("0")) { - requestBuilder.header("Authorization", "Bearer " + accessToken); + if (url.startsWith(BASE_URL)) { + String accessToken = AuthManager.getAccessToken(); + if (accessToken != null && !accessToken.equals("0")) { + requestBuilder.header("Authorization", "Bearer " + accessToken); + } + } + + HttpRequest request = requestBuilder.build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofFile(target)); + + if (response.statusCode() == 200) { + directSuccessCount++; + return; + } + } catch (Exception e) { + if (isConnectionError(e)) { + directFailCount++; + if (directAttempt < directRetries) { + System.out.println(ZAnsi.yellow("[NET] Direct download retry " + directAttempt + "/" + directRetries + " for " + url.substring(0, Math.min(60, url.length())) + "...")); + try { Thread.sleep(1000 * directAttempt); } catch (InterruptedException ie) { break; } + continue; + } + markServiceAsBlocked(url); + } else { + throw e; } } - - HttpRequest request = requestBuilder.build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofFile(target)); - - if (response.statusCode() == 200) { - directSuccessCount++; - return; - } - } catch (Exception e) { - if (isConnectionError(e)) { - directFailCount++; - markServiceAsBlocked(url); - } + break; } } - String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8); - String proxyUrl = BASE_URL + "/proxy/download?url=" + encodedUrl; + int maxRetries = 3; + for (int attempt = 1; attempt <= maxRetries; attempt++) { + try { + String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8); + String proxyUrl = BASE_URL + "/proxy/download?url=" + encodedUrl; - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(proxyUrl)) - .timeout(Duration.ofMinutes(5)) - .header("User-Agent", "ZernMC-Launcher/1.0") - .GET() - .build(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(proxyUrl)) + .timeout(Duration.ofMinutes(5)) + .header("User-Agent", "ZernMC-Launcher/1.0") + .GET() + .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofFile(target)); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofFile(target)); - if (response.statusCode() != 200) { - throw new IOException("Proxy download failed: HTTP " + response.statusCode()); + if (response.statusCode() != 200) { + throw new IOException("Proxy download failed: HTTP " + response.statusCode()); + } + + proxySuccessCount++; + return; + + } catch (Exception e) { + if (attempt == maxRetries || !isConnectionError(e)) { + throw new IOException("Proxy download failed: " + e.getMessage(), e); + } + try { Thread.sleep(1000 * attempt); } catch (InterruptedException ie) { break; } + } } - - proxySuccessCount++; } public static String get(String endpoint) throws IOException, InterruptedException { diff --git a/launcher/pom.xml b/launcher/pom.xml index 62d14a4..2ce239c 100644 --- a/launcher/pom.xml +++ b/launcher/pom.xml @@ -1,179 +1,179 @@ - - - - 4.0.0 - me.sashegdev - ZernMCLauncher - ${revision} - pom - - ZernMC Launcher Parent - ZernMC Launcher - Multi-module project - - - bootstrap - launcher - - - - 1.0.14 - 1 - 21 - 21 - UTF-8 - ZernMC - 2026 - ZernMC Launcher - Multi-module project - - - - - - org.apache.httpcomponents - httpclient - 4.5.14 - - - com.fasterxml.jackson.core - jackson-databind - 2.15.2 - - - com.google.code.gson - gson - 2.10.1 - - - org.json - json - 20231013 - - - org.fusesource.jansi - jansi - 2.4.1 - - - org.jline - jline - 3.24.1 - - - me.tongfei - progressbar - 0.9.5 - - - commons-io - commons-io - 2.15.1 - - - - org.openjfx - javafx-controls - 23 - win - - - org.openjfx - javafx-web - 23 - win - - - org.openjfx - javafx-graphics - 23 - win - - - org.openjfx - javafx-base - 23 - win - - - org.openjfx - javafx-media - 23 - win - - - - - - - - org.codehaus.mojo - flatten-maven-plugin - 1.6.0 - - true - resolveCiFriendliesOnly - - - - flatten - process-resources - flatten - - - flatten-clean - clean - clean - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.2.3 - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.5.0 - - - package - - shade - - - ../../server/builds/ZernMCLauncher.jar - - - ${mainClass} - - ${project.version}.${hotfix} - ZernMC Launcher - SashegDev - Samopisnui Minecraft-launcher. by SashegDev - https://github.com/SashegDev/launcher - - - - - - - - - - - - global - - true - - - ZernMC Launcher - http://87.120.187.36:1582 - - - + + + + 4.0.0 + me.sashegdev + ZernMCLauncher + ${revision} + pom + + ZernMC Launcher Parent + ZernMC Launcher - Multi-module project + + + bootstrap + launcher + + + + 1.0.14 + 2 + 21 + 21 + UTF-8 + ZernMC + 2026 + ZernMC Launcher - Multi-module project + + + + + + org.apache.httpcomponents + httpclient + 4.5.14 + + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + + + com.google.code.gson + gson + 2.10.1 + + + org.json + json + 20231013 + + + org.fusesource.jansi + jansi + 2.4.1 + + + org.jline + jline + 3.24.1 + + + me.tongfei + progressbar + 0.9.5 + + + commons-io + commons-io + 2.15.1 + + + + org.openjfx + javafx-controls + 23 + win + + + org.openjfx + javafx-web + 23 + win + + + org.openjfx + javafx-graphics + 23 + win + + + org.openjfx + javafx-base + 23 + win + + + org.openjfx + javafx-media + 23 + win + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + 1.6.0 + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + flatten + + + flatten-clean + clean + clean + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.3 + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + + shade + + + ../../server/builds/ZernMCLauncher.jar + + + ${mainClass} + + ${project.version}.${hotfix} + ZernMC Launcher + SashegDev + Samopisnui Minecraft-launcher. by SashegDev + https://github.com/SashegDev/launcher + + + + + + + + + + + + global + + true + + + ZernMC Launcher + http://87.120.187.36:1582 + + + \ No newline at end of file