From b841828454168bbdbb211a9aa41d9ab244389c60 Mon Sep 17 00:00:00 2001 From: Daniel Walsh Date: Sun, 25 Feb 2024 18:24:21 +0000 Subject: [PATCH] Remove Unirest and replace with Java's native HttpClient (#4068) Co-authored-by: J3fftw <44972470+J3fftw1@users.noreply.github.com> --- pom.xml | 18 --- .../core/services/MetricsService.java | 121 ++++++++++++------ .../github/ContributionsConnector.java | 26 ++-- .../github/GitHubActivityConnector.java | 16 +-- .../core/services/github/GitHubConnector.java | 60 +++++---- .../github/GitHubIssuesConnector.java | 18 +-- 6 files changed, 147 insertions(+), 112 deletions(-) diff --git a/pom.xml b/pom.xml index 08c63b0dd5..4949b1f107 100644 --- a/pom.xml +++ b/pom.xml @@ -204,10 +204,6 @@ io.papermc.lib io.github.thebusybiscuit.slimefun4.libraries.paperlib - - kong.unirest - io.github.thebusybiscuit.slimefun4.libraries.unirest - org.apache.commons.lang io.github.thebusybiscuit.slimefun4.libraries.commons.lang @@ -357,20 +353,6 @@ 1.0.8 compile - - com.konghq - unirest-java - 3.14.5 - compile - - - - - com.google.code.gson - gson - - - diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/MetricsService.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/MetricsService.java index e186004c4e..67cd5a7acd 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/MetricsService.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/MetricsService.java @@ -4,11 +4,22 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.net.URI; import java.net.URL; import java.net.URLClassLoader; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandler; +import java.net.http.HttpResponse.BodySubscriber; +import java.nio.ByteBuffer; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.concurrent.atomic.AtomicInteger; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Flow.Subscription; import java.util.logging.Level; import javax.annotation.Nonnull; @@ -16,15 +27,13 @@ import org.bukkit.plugin.Plugin; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; + import io.github.bakedlibs.dough.common.CommonPatterns; import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; -import kong.unirest.GetRequest; -import kong.unirest.HttpResponse; -import kong.unirest.JsonNode; -import kong.unirest.Unirest; -import kong.unirest.UnirestException; - /** * This Class represents a Metrics Service that sends data to https://bstats.org/ * This data is used to analyse the usage of this {@link Plugin}. @@ -66,22 +75,12 @@ public class MetricsService { private final Slimefun plugin; private final File parentFolder; private final File metricsModuleFile; + private final HttpClient client = HttpClient.newHttpClient(); private URLClassLoader moduleClassLoader; private String metricVersion = null; private boolean hasDownloadedUpdate = false; - static { - // @formatter:off (We want this to stay this nicely aligned :D ) - Unirest.config() - .concurrency(2, 1) - .setDefaultHeader("User-Agent", "MetricsModule Auto-Updater") - .setDefaultHeader("Accept", "application/vnd.github.v3+json") - .enableCookieManagement(false) - .cookieSpec("ignoreCookies"); - // @formatter:on - } - /** * This constructs a new instance of our {@link MetricsService}. * @@ -199,20 +198,16 @@ public boolean checkForUpdate(@Nullable String currentVersion) { */ private int getLatestVersion() { try { - HttpResponse response = Unirest.get(RELEASES_URL).asJson(); + HttpResponse response = client.send(buildBaseRequest(URI.create(RELEASES_URL)), HttpResponse.BodyHandlers.ofString()); - if (!response.isSuccess()) { + if (response.statusCode() < 200 || response.statusCode() >= 300) { return -1; } - JsonNode node = response.getBody(); + JsonElement element = JsonParser.parseString(response.body()); - if (node == null) { - return -1; - } - - return node.getObject().getInt("tag_name"); - } catch (UnirestException e) { + return element.getAsJsonObject().get("tag_name").getAsInt(); + } catch (IOException | InterruptedException | JsonParseException e) { plugin.getLogger().log(Level.WARNING, "Failed to fetch latest builds for Metrics: {0}", e.getMessage()); return -1; } @@ -235,19 +230,13 @@ private boolean download(int version) { Files.delete(file.toPath()); } - AtomicInteger lastPercentPosted = new AtomicInteger(); - GetRequest request = Unirest.get(DOWNLOAD_URL + "/" + version + "/" + JAR_NAME + ".jar"); + HttpResponse response = client.send( + buildBaseRequest(URI.create(DOWNLOAD_URL + "/" + version + "/" + JAR_NAME + ".jar")), + downloadMonitor(HttpResponse.BodyHandlers.ofFile(file.toPath())) + ); - HttpResponse response = request.downloadMonitor((b, fileName, bytesWritten, totalBytes) -> { - int percent = (int) (20 * (Math.round((((double) bytesWritten / totalBytes) * 100) / 20))); - if (percent != 0 && percent != lastPercentPosted.get()) { - plugin.getLogger().info("# Downloading... " + percent + "% " + "(" + bytesWritten + "/" + totalBytes + " bytes)"); - lastPercentPosted.set(percent); - } - }).asFile(file.getPath()); - - if (response.isSuccess()) { + if (response.statusCode() >= 200 && response.statusCode() < 300) { plugin.getLogger().log(Level.INFO, "Successfully downloaded {0} build: #{1}", new Object[] { JAR_NAME, version }); // Replace the metric file with the new one @@ -258,7 +247,7 @@ private boolean download(int version) { hasDownloadedUpdate = true; return true; } - } catch (UnirestException e) { + } catch (InterruptedException | JsonParseException e) { plugin.getLogger().log(Level.WARNING, "Failed to fetch the latest jar file from the builds page. Perhaps GitHub is down? Response: {0}", e.getMessage()); } catch (IOException e) { plugin.getLogger().log(Level.WARNING, "Failed to replace the old metric file with the new one. Please do this manually! Error: {0}", e.getMessage()); @@ -287,4 +276,58 @@ public String getVersion() { public boolean hasAutoUpdates() { return Slimefun.instance().getConfig().getBoolean("metrics.auto-update"); } + + private HttpRequest buildBaseRequest(@Nonnull URI uri) { + return HttpRequest.newBuilder() + .uri(uri) + .timeout(Duration.ofSeconds(5)) + .header("User-Agent", "MetricsModule Auto-Updater") + .header("Accept", "application/vnd.github.v3+json") + .build(); + } + + private BodyHandler downloadMonitor(BodyHandler h) { + return info -> new BodySubscriber() { + + private BodySubscriber delegateSubscriber = h.apply(info); + private int lastPercentPosted = 0; + private long bytesWritten = 0; + + @Override + public void onSubscribe(Subscription subscription) { + delegateSubscriber.onSubscribe(subscription); + } + + @Override + public void onNext(List item) { + bytesWritten += item.stream().mapToLong(ByteBuffer::capacity).sum(); + long totalBytes = info.headers().firstValue("Content-Length").map(Long::parseLong).orElse(-1L); + + int percent = (int) (20 * (Math.round((((double) bytesWritten / totalBytes) * 100) / 20))); + + if (percent != 0 && percent != lastPercentPosted) { + plugin.getLogger().info("# Downloading... " + percent + "% " + "(" + bytesWritten + "/" + totalBytes + " bytes)"); + lastPercentPosted = percent; + } + + delegateSubscriber.onNext(item); + } + + @Override + public void onError(Throwable throwable) { + delegateSubscriber.onError(throwable); + + } + + @Override + public void onComplete() { + delegateSubscriber.onComplete(); + } + + @Override + public CompletionStage getBody() { + return delegateSubscriber.getBody(); + } + }; + } } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/ContributionsConnector.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/ContributionsConnector.java index 7dd4970552..4eb9d92637 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/ContributionsConnector.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/ContributionsConnector.java @@ -9,11 +9,11 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; -import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; -import kong.unirest.JsonNode; -import kong.unirest.json.JSONArray; -import kong.unirest.json.JSONObject; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; class ContributionsConnector extends GitHubConnector { @@ -90,11 +90,11 @@ public boolean hasFinished() { } @Override - public void onSuccess(@Nonnull JsonNode response) { + public void onSuccess(@Nonnull JsonElement response) { finished = true; - if (response.isArray()) { - computeContributors(response.getArray()); + if (response.isJsonArray()) { + computeContributors(response.getAsJsonArray()); } else { Slimefun.logger().log(Level.WARNING, "Received an unusual answer from GitHub, possibly a timeout? ({0})", response); } @@ -123,13 +123,13 @@ public Map getParameters() { return parameters; } - private void computeContributors(@Nonnull JSONArray array) { - for (int i = 0; i < array.length(); i++) { - JSONObject object = array.getJSONObject(i); + private void computeContributors(@Nonnull JsonArray array) { + for (JsonElement element : array) { + JsonObject object = element.getAsJsonObject(); - String name = object.getString("login"); - int commits = object.getInt("contributions"); - String profile = object.getString("html_url"); + String name = object.get("login").getAsString(); + int commits = object.get("contributions").getAsInt(); + String profile = object.get("html_url").getAsString(); if (!ignoredAccounts.contains(name)) { String username = aliases.getOrDefault(name, name); diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubActivityConnector.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubActivityConnector.java index b298195e7b..60697f1345 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubActivityConnector.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubActivityConnector.java @@ -7,10 +7,10 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; -import io.github.thebusybiscuit.slimefun4.utils.NumberUtils; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; -import kong.unirest.JsonNode; -import kong.unirest.json.JSONObject; +import io.github.thebusybiscuit.slimefun4.utils.NumberUtils; class GitHubActivityConnector extends GitHubConnector { @@ -23,11 +23,11 @@ class GitHubActivityConnector extends GitHubConnector { } @Override - public void onSuccess(@Nonnull JsonNode response) { - JSONObject object = response.getObject(); - int forks = object.getInt("forks"); - int stars = object.getInt("stargazers_count"); - LocalDateTime lastPush = NumberUtils.parseGitHubDate(object.getString("pushed_at")); + public void onSuccess(@Nonnull JsonElement response) { + JsonObject object = response.getAsJsonObject(); + int forks = object.get("forks").getAsInt(); + int stars = object.get("stargazers_count").getAsInt(); + LocalDateTime lastPush = NumberUtils.parseGitHubDate(object.get("pushed_at").getAsString()); callback.accept(forks, stars, lastPush); } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubConnector.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubConnector.java index 422ed31787..3d6a711b9e 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubConnector.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubConnector.java @@ -6,7 +6,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.logging.Level; @@ -14,13 +19,11 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; -import kong.unirest.HttpResponse; -import kong.unirest.JsonNode; -import kong.unirest.Unirest; -import kong.unirest.UnirestException; -import kong.unirest.json.JSONException; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; /** * The {@link GitHubConnector} is used to connect to the GitHub API service. @@ -34,6 +37,7 @@ abstract class GitHubConnector { private static final String API_URL = "https://api.github.com/"; private static final String USER_AGENT = "Slimefun4 (https://github.com/Slimefun)"; + private static final HttpClient client = HttpClient.newHttpClient(); protected final GitHubService github; private final String url; @@ -83,7 +87,7 @@ abstract class GitHubConnector { * @param response * The response */ - public abstract void onSuccess(@Nonnull JsonNode response); + public abstract void onSuccess(@Nonnull JsonElement response); /** * This method is called when the connection has failed. @@ -105,38 +109,44 @@ void download() { } try { - // @formatter:off - HttpResponse response = Unirest.get(url) - .queryString(getParameters()) - .header("User-Agent", USER_AGENT) - .asJson(); - // @formatter:on - - if (response.isSuccess()) { - onSuccess(response.getBody()); - writeCacheFile(response.getBody()); + String params = getParameters().entrySet().stream() + .map(p -> p.getKey() + "=" + p.getValue()) + .reduce((p1, p2) -> p1 + "&" + p2) + .map(s -> "?" + s) + .orElse(""); + URI uri = new URI(url + params); + + HttpResponse response = client.send( + HttpRequest.newBuilder(uri).header("User-Agent", USER_AGENT).build(), + HttpResponse.BodyHandlers.ofString() + ); + JsonElement element = JsonParser.parseString(response.body()); + + if (response.statusCode() >= 200 && response.statusCode() < 300) { + onSuccess(element); + writeCacheFile(element); } else { if (github.isLoggingEnabled()) { - Slimefun.logger().log(Level.WARNING, "Failed to fetch {0}: {1} - {2}", new Object[] { url, response.getStatus(), response.getBody() }); + Slimefun.logger().log(Level.WARNING, "Failed to fetch {0}: {1} - {2}", new Object[] { url, response.statusCode(), element }); } // It has the cached file, let's just read that then if (file.exists()) { - JsonNode cache = readCacheFile(); + JsonElement cache = readCacheFile(); if (cache != null) { onSuccess(cache); } } } - } catch (UnirestException e) { + } catch (IOException | InterruptedException | JsonParseException | URISyntaxException e) { if (github.isLoggingEnabled()) { Slimefun.logger().log(Level.WARNING, "Could not connect to GitHub in time.", e); } // It has the cached file, let's just read that then if (file.exists()) { - JsonNode cache = readCacheFile(); + JsonElement cache = readCacheFile(); if (cache != null) { onSuccess(cache); @@ -150,16 +160,16 @@ void download() { } @Nullable - private JsonNode readCacheFile() { + private JsonElement readCacheFile() { try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { - return new JsonNode(reader.readLine()); - } catch (IOException | JSONException e) { + return JsonParser.parseString(reader.readLine()); + } catch (IOException | JsonParseException e) { Slimefun.logger().log(Level.WARNING, "Failed to read Github cache file: {0} - {1}: {2}", new Object[] { file.getName(), e.getClass().getSimpleName(), e.getMessage() }); return null; } } - private void writeCacheFile(@Nonnull JsonNode node) { + private void writeCacheFile(@Nonnull JsonElement node) { try (FileOutputStream output = new FileOutputStream(file)) { output.write(node.toString().getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubIssuesConnector.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubIssuesConnector.java index 81cd1ac5d3..96a4237e1e 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubIssuesConnector.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/services/github/GitHubIssuesConnector.java @@ -7,11 +7,11 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; -import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; -import kong.unirest.JsonNode; -import kong.unirest.json.JSONArray; -import kong.unirest.json.JSONObject; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; class GitHubIssuesConnector extends GitHubConnector { @@ -24,15 +24,15 @@ class GitHubIssuesConnector extends GitHubConnector { } @Override - public void onSuccess(@Nonnull JsonNode response) { - if (response.isArray()) { - JSONArray array = response.getArray(); + public void onSuccess(@Nonnull JsonElement response) { + if (response.isJsonArray()) { + JsonArray array = response.getAsJsonArray(); int issues = 0; int pullRequests = 0; - for (int i = 0; i < array.length(); i++) { - JSONObject obj = array.getJSONObject(i); + for (JsonElement element : array) { + JsonObject obj = element.getAsJsonObject(); if (obj.has("pull_request")) { pullRequests++;