Skip to content

Commit

Permalink
Merge pull request #7 from TheRealRyGuy/master
Browse files Browse the repository at this point in the history
Migrate to CompletableFuture / HttpClient usage, misc changes
  • Loading branch information
JulianVennen committed Jun 15, 2023
2 parents 898e889 + 0bb6057 commit 56e6744
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 169 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
java: [8, 11, 17, 19]
java: [11, 17, 19]
steps:
- uses: actions/checkout@v2
- name: Set up Java
Expand All @@ -16,7 +16,5 @@ jobs:
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: init submodule
run: git submodule init && git submodule update
- name: Build with Gradle
run: ./gradlew build --no-daemon
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
- Update dependencies
- mark the client property of UploadLogResponse as transient
- This should fix an issue with deserialization on Java 17+
- Utilize `CompletableFuture` to wrap around API calls
- API calls are now async
- Moved visibility of all fields to `private`, some are also `final`
- Utilizes Java 11s `HttpClient`
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ repositories {
}

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.3'

implementation 'com.google.code.gson:gson:2.10.1'
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/gs/mclo/api/APIException.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import java.io.IOException;

public class APIException extends IOException {
protected JsonResponse response;
protected final JsonResponse response;

public APIException(JsonResponse response) {
super("The API returned an error");
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/gs/mclo/api/Instance.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package gs.mclo.api;

public class Instance {
protected String apiBaseUrl;
protected String viewLogUrl;
private String apiBaseUrl;
private String viewLogUrl;

/**
* Create a new Instance with the default API base URL and view log URL
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/gs/mclo/api/Log.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class Log {
/**
* log content
*/
protected String content;
private String content;

/**
* pattern for IPv4 addresses
Expand Down Expand Up @@ -117,7 +117,7 @@ private void filter() {
*/
private void filterIPv4() {
Matcher matcher = IPV4_PATTERN.matcher(this.content);
StringBuffer sb = new StringBuffer();
StringBuilder sb = new StringBuilder();
while (matcher.find()) {
if (isWhitelistedIPv4(matcher.group())) {
continue;
Expand Down Expand Up @@ -147,7 +147,7 @@ private boolean isWhitelistedIPv4(String s) {
*/
private void filterIPv6() {
Matcher matcher = IPV6_PATTERN.matcher(this.content);
StringBuffer sb = new StringBuffer();
StringBuilder sb = new StringBuilder();
while (matcher.find()) {
if (isWhitelistedIPv6(matcher.group())) {
continue;
Expand Down
154 changes: 48 additions & 106 deletions src/main/java/gs/mclo/api/MclogsClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,31 @@

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;

public class MclogsClient {
protected String projectName = null;
protected String projectVersion = null;

protected String minecraftVersion = null;
private final Gson gson = new Gson();
private final HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NORMAL)
.build();

protected String customUserAgent = null;
private String projectName = null;
private String projectVersion = null;

protected Instance instance = new Instance();
private String minecraftVersion = null;

protected Gson gson = new Gson();
private String customUserAgent = null;

private Instance instance = new Instance();

/**
* Create a new Mclogs instance with a custom user agent
Expand Down Expand Up @@ -154,40 +160,15 @@ public MclogsClient setInstance(Instance instance) {
* @param log the log to upload
* @return the response
*/
public UploadLogResponse uploadLog(Log log) throws IOException {
//connect to api
URL url = new URL(instance.getLogUploadUrl());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
try {
connection.setRequestMethod("POST");
connection.setDoOutput(true);

//convert log to application/x-www-form-urlencoded
String content = "content=" + URLEncoder.encode(log.getContent(), StandardCharsets.UTF_8.toString());
byte[] out = content.getBytes(StandardCharsets.UTF_8);
int length = out.length;

//send log to api
connection.setFixedLengthStreamingMode(length);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
connection.setRequestProperty("Accepts", "application/json");
connection.setRequestProperty("User-Agent", this.getUserAgent());
connection.connect();
try (OutputStream os = connection.getOutputStream()) {
os.write(out);
}

//handle response
UploadLogResponse response = gson.fromJson(
Util.inputStreamToString(connection.getInputStream()),
UploadLogResponse.class
);
response.setClient(this).throwIfError();
connection.disconnect();
return response;
} finally {
connection.disconnect();
}
public CompletableFuture<UploadLogResponse> uploadLog(Log log) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(instance.getLogUploadUrl()))
.header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
.header("Accepts", "application/json")
.header("User-Agent", this.getUserAgent())
.POST(HttpRequest.BodyPublishers.ofString("content=" + URLEncoder.encode(log.getContent(), StandardCharsets.UTF_8)))
.build();
return httpClient.sendAsync(request, Util.parseResponse(UploadLogResponse.class, gson)).thenApply(HttpResponse::body);
}

/**
Expand All @@ -196,7 +177,7 @@ public UploadLogResponse uploadLog(Log log) throws IOException {
* @param log the log to upload
* @return the response
*/
public UploadLogResponse uploadLog(String log) throws IOException {
public CompletableFuture<UploadLogResponse> uploadLog(String log) {
return this.uploadLog(new Log(log));
}

Expand All @@ -206,55 +187,42 @@ public UploadLogResponse uploadLog(String log) throws IOException {
* @param log the log to upload
* @return the response
*/
public UploadLogResponse uploadLog(Path log) throws IOException {
public CompletableFuture<UploadLogResponse> uploadLog(Path log) throws IOException {
return this.uploadLog(new Log(log));
}

/**
* Fetch the raw contents of a log from mclo.gs
*
* @param logId the id of the log
* @return the raw contents of the log
* @throws IOException if an error occurs while fetching the log
*/
public String getRawLogContent(String logId) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(instance.getRawLogUrl(logId)).openConnection();
try {
connection.setRequestMethod("GET");
connection.setRequestProperty("User-Agent", this.getUserAgent());
connection.connect();
String response = Util.inputStreamToString(connection.getInputStream());
connection.disconnect();
return response;
}
finally {
connection.disconnect();
}
public CompletableFuture<String> getRawLogContent(String logId) throws IOException {
HttpRequest request = HttpRequest
.newBuilder()
.uri(URI.create(instance.getRawLogUrl(logId)))
.header("User-Agent", this.getUserAgent())
.GET()
.build();
return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body);
}

/**
* Fetch the insights for a log from mclo.gs
*
* @param logId the id of the log
* @return the insights for the log
* @throws IOException if an error occurs while fetching the insights
*/
public InsightsResponse getInsights(String logId) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(instance.getLogInsightsUrl(logId)).openConnection();
try {
connection.setRequestMethod("GET");
connection.setRequestProperty("User-Agent", this.getUserAgent());
connection.setRequestProperty("Accepts", "application/json");
connection.connect();
InsightsResponse response = gson.fromJson(
Util.inputStreamToString(connection.getInputStream()),
InsightsResponse.class
);
response.throwIfError();
connection.disconnect();
return response;
}
finally {
connection.disconnect();
}
public CompletableFuture<InsightsResponse> getInsights(String logId) {
HttpRequest request = HttpRequest
.newBuilder()
.uri(URI.create(instance.getLogInsightsUrl(logId)))
.header("User-Agent", this.getUserAgent())
.header("Accepts", "application/json")
.GET()
.build();
return httpClient.sendAsync(request, Util.parseResponse(InsightsResponse.class, gson)).thenApply(HttpResponse::body);
}

/**
Expand All @@ -263,20 +231,7 @@ public InsightsResponse getInsights(String logId) throws IOException {
* @return log file names
*/
public String[] listLogsInDirectory(String directory){
File logsDirectory = new File(directory, "logs");

if (!logsDirectory.exists()) {
return new String[0];
}

String[] files = logsDirectory.list();
if (files == null)
files = new String[0];

return Arrays.stream(files)
.filter(file -> file.matches(Log.ALLOWED_FILE_NAME_PATTERN.pattern()))
.sorted()
.toArray(String[]::new);
return Util.listFilesInDirectory(new File(directory, "logs"));
}

/**
Expand All @@ -285,19 +240,6 @@ public String[] listLogsInDirectory(String directory){
* @return log file names
*/
public String[] listCrashReportsInDirectory(String directory){
File crashReportDirectory = new File(directory, "crash-reports");

if (!crashReportDirectory.exists()) {
return new String[0];
}

String[] files = crashReportDirectory.list();
if (files == null)
files = new String[0];

return Arrays.stream(files)
.filter(file -> file.matches(Log.ALLOWED_FILE_NAME_PATTERN.pattern()))
.sorted()
.toArray(String[]::new);
return Util.listFilesInDirectory(new File(directory, "crash-reports"));
}
}
28 changes: 24 additions & 4 deletions src/main/java/gs/mclo/api/Util.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
package gs.mclo.api;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import com.google.gson.Gson;

import java.io.*;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;

public class Util {

public static String[] listFilesInDirectory(File file) {
if(!file.exists())
return new String[0];
String[] files = file.list();
if (files == null)
files = new String[0];

return Arrays.stream(files)
.filter(f -> f.matches(Log.ALLOWED_FILE_NAME_PATTERN.pattern()))
.sorted()
.toArray(String[]::new);
}

public static <T> HttpResponse.BodyHandler<T> parseResponse(Class<T> clazz, Gson gson) {
return responseInfo -> HttpResponse.BodySubscribers.mapping(HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8),
body -> gson.fromJson(body, clazz));
}

/**
* parse an input stream to a string
* @param is input stream
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/gs/mclo/api/response/InsightsResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,27 @@ public class InsightsResponse extends JsonResponse {
/**
* ID of detected log (name/type) e.g. "vanilla/server"
*/
protected String id = null;
private String id = null;

/**
* Software name, e.g. "Vanilla"
*/
protected String name = null;
private String name = null;

/**
* Software type, e.g. "server"
*/
protected String type = null;
private String type = null;

/**
* Combined title, e.g. "Vanilla 1.12.2 Server Log"
*/
protected String title = null;
private String title = null;

/**
* Information obtained from the analysis of the log
*/
protected Analysis analysis = null;
private Analysis analysis = null;

/**
* Get the ID of detected log (name/type) e.g. "vanilla/server"
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/gs/mclo/api/response/JsonResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import gs.mclo.api.APIException;

public class JsonResponse {
protected boolean success = true;
protected String error = null;
private boolean success = true;
private String error = null;

/**
* was the upload successful?
Expand Down
Loading

0 comments on commit 56e6744

Please sign in to comment.