diff --git a/java11/pom.xml b/java11/pom.xml new file mode 100644 index 0000000..dd811f6 --- /dev/null +++ b/java11/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + org.mineskin + client-parent + PARENT + + + java-client-java11 + ${revision} + + + + org.mineskin + java-client + ${revision} + + + + \ No newline at end of file diff --git a/java11/src/main/java/org/mineskin/Java11RequestHandler.java b/java11/src/main/java/org/mineskin/Java11RequestHandler.java new file mode 100644 index 0000000..0759932 --- /dev/null +++ b/java11/src/main/java/org/mineskin/Java11RequestHandler.java @@ -0,0 +1,168 @@ +package org.mineskin; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import org.mineskin.exception.MineSkinRequestException; +import org.mineskin.exception.MineskinException; +import org.mineskin.request.RequestHandler; +import org.mineskin.response.MineSkinResponse; +import org.mineskin.response.ResponseConstructor; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse.BodyHandlers; +import java.util.Map; +import java.util.logging.Level; +import java.util.stream.Collectors; + +public class Java11RequestHandler extends RequestHandler { + + private final Gson gson; + private final HttpClient httpClient; + + public Java11RequestHandler(String userAgent, String apiKey, int timeout, Gson gson) { + super(userAgent, apiKey, timeout, gson); + this.gson = gson; + + HttpClient.Builder clientBuilder = HttpClient.newBuilder() + .connectTimeout(java.time.Duration.ofMillis(timeout)); + + if (userAgent != null) { + clientBuilder.followRedirects(HttpClient.Redirect.NORMAL); + } + this.httpClient = clientBuilder.build(); + } + + private > R wrapResponse(HttpResponse response, Class clazz, ResponseConstructor constructor) throws IOException { + String rawBody = response.body(); + try { + JsonObject jsonBody = gson.fromJson(rawBody, JsonObject.class); + R wrapped = constructor.construct( + response.statusCode(), + lowercaseHeaders(response.headers().map()), + jsonBody, + gson, clazz + ); + if (!wrapped.isSuccess()) { + throw new MineSkinRequestException(wrapped.getError().orElse("Request Failed"), wrapped); + } + return wrapped; + } catch (JsonParseException e) { + MineSkinClientImpl.LOGGER.log(Level.WARNING, "Failed to parse response body: " + rawBody, e); + throw new MineskinException("Failed to parse response", e); + } + } + + private Map lowercaseHeaders(Map> headers) { + return headers.entrySet().stream() + .collect(Collectors.toMap( + entry -> entry.getKey().toLowerCase(), + entry -> String.join(", ", entry.getValue()) + )); + } + + public > R getJson(String url, Class clazz, ResponseConstructor constructor) throws IOException { + MineSkinClientImpl.LOGGER.fine("GET " + url); + + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .uri(URI.create(url)) + .GET() + .header("User-Agent", this.userAgent); + HttpRequest request; + if (apiKey != null) { + request = requestBuilder + .header("Authorization", "Bearer " + apiKey) + .header("Accept", "application/json").build(); + } else { + request = requestBuilder.build(); + } + HttpResponse response; + try { + response = this.httpClient.send(request, BodyHandlers.ofString()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return wrapResponse(response, clazz, constructor); + } + + public > R postJson(String url, JsonObject data, Class clazz, ResponseConstructor constructor) throws IOException { + MineSkinClientImpl.LOGGER.fine("POST " + url); + + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .uri(URI.create(url)) + .POST(BodyPublishers.ofString(gson.toJson(data))) + .header("Content-Type", "application/json") + .header("User-Agent", this.userAgent); + HttpRequest request; + if (apiKey != null) { + request = requestBuilder + .header("Authorization", "Bearer " + apiKey) + .header("Accept", "application/json").build(); + } else { + request = requestBuilder.build(); + } + + HttpResponse response; + try { + response = this.httpClient.send(request, BodyHandlers.ofString()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return wrapResponse(response, clazz, constructor); + } + + public > R postFormDataFile(String url, String key, String filename, InputStream in, Map data, Class clazz, ResponseConstructor constructor) throws IOException { + MineSkinClientImpl.LOGGER.fine("POST " + url); + + String boundary = "mineskin-" + System.currentTimeMillis(); + StringBuilder bodyBuilder = new StringBuilder(); + + // add form fields + for (Map.Entry entry : data.entrySet()) { + bodyBuilder.append("--").append(boundary).append("\r\n") + .append("Content-Disposition: form-data; name=\"").append(entry.getKey()).append("\"\r\n\r\n") + .append(entry.getValue()).append("\r\n"); + } + + // add file + byte[] fileContent = in.readAllBytes(); + bodyBuilder.append("--").append(boundary).append("\r\n") + .append("Content-Disposition: form-data; name=\"").append(key) + .append("\"; filename=\"").append(filename).append("\"\r\n") + .append("Content-Type: image/png\r\n\r\n"); + byte[] bodyStart = bodyBuilder.toString().getBytes(); + byte[] boundaryEnd = ("\r\n--" + boundary + "--\r\n").getBytes(); + byte[] bodyString = new byte[bodyStart.length + fileContent.length + boundaryEnd.length]; + System.arraycopy(bodyStart, 0, bodyString, 0, bodyStart.length); + System.arraycopy(fileContent, 0, bodyString, bodyStart.length, fileContent.length); + System.arraycopy(boundaryEnd, 0, bodyString, bodyStart.length + fileContent.length, boundaryEnd.length); + + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .uri(URI.create(url)) + .POST(HttpRequest.BodyPublishers.ofByteArray(bodyString)) + .header("Content-Type", "multipart/form-data; boundary=" + boundary) + .header("User-Agent", this.userAgent); + HttpRequest request; + if (apiKey != null) { + request = requestBuilder + .header("Authorization", "Bearer " + apiKey) + .header("Accept", "application/json").build(); + } else { + request = requestBuilder.build(); + } + + HttpResponse response; + try { + response = this.httpClient.send(request, BodyHandlers.ofString()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return wrapResponse(response, clazz, constructor); + } +} diff --git a/pom.xml b/pom.xml index 57dc24f..d2d6e08 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,7 @@ core apache jsoup + java11 tests diff --git a/tests/pom.xml b/tests/pom.xml index e68c7ec..8bfcfc1 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -28,6 +28,12 @@ java-client-apache ${revision} + + org.mineskin + java-client-java11 + 2.0.0-SNAPSHOT + test + junit diff --git a/tests/src/test/java/test/GenerateTest.java b/tests/src/test/java/test/GenerateTest.java index 814beb1..4752879 100644 --- a/tests/src/test/java/test/GenerateTest.java +++ b/tests/src/test/java/test/GenerateTest.java @@ -4,12 +4,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.mineskin.ApacheRequestHandler; -import org.mineskin.GenerateOptions; -import org.mineskin.ImageUtil; -import org.mineskin.JsoupRequestHandler; -import org.mineskin.MineSkinClient; -import org.mineskin.MineSkinClientImpl; +import org.mineskin.*; import org.mineskin.data.GeneratedSkin; import org.mineskin.data.Skin; import org.mineskin.data.Visibility; @@ -44,6 +39,12 @@ public class GenerateTest { .apiKey(System.getenv("MINESKIN_API_KEY")) .generateExecutor(EXECUTOR) .build(); + private static final MineSkinClient JAVA11 = MineSkinClient.builder() + .requestHandler(Java11RequestHandler::new) + .userAgent("MineSkinClient-Java11/Tests") + .apiKey(System.getenv("MINESKIN_API_KEY")) + .generateExecutor(EXECUTOR) + .build(); static { MineSkinClientImpl.LOGGER.setLevel(Level.ALL); @@ -60,7 +61,8 @@ public void before() throws InterruptedException { private static Stream clients() { return Stream.of( Arguments.of(APACHE), - Arguments.of(JSOUP) + Arguments.of(JSOUP), + Arguments.of(JAVA11) ); } diff --git a/tests/src/test/java/test/GetTest.java b/tests/src/test/java/test/GetTest.java index 0082e8e..6e5a562 100644 --- a/tests/src/test/java/test/GetTest.java +++ b/tests/src/test/java/test/GetTest.java @@ -3,10 +3,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.mineskin.ApacheRequestHandler; -import org.mineskin.JsoupRequestHandler; -import org.mineskin.MineSkinClient; -import org.mineskin.MineSkinClientImpl; +import org.mineskin.*; import org.mineskin.data.ExistingSkin; import org.mineskin.exception.MineSkinRequestException; import org.mineskin.response.GetSkinResponse; @@ -30,6 +27,11 @@ public class GetTest { .userAgent("MineSkinClient-Jsoup/Tests") .apiKey(System.getenv("MINESKIN_API_KEY")) .build(); + private static final MineSkinClient JAVA11 = MineSkinClient.builder() + .requestHandler(Java11RequestHandler::new) + .userAgent("MineSkinClient-Java11/Tests") + .apiKey(System.getenv("MINESKIN_API_KEY")) + .build(); static { MineSkinClientImpl.LOGGER.setLevel(Level.ALL); ConsoleHandler handler = new ConsoleHandler(); @@ -40,7 +42,8 @@ public class GetTest { private static Stream clients() { return Stream.of( Arguments.of(APACHE), - Arguments.of(JSOUP) + Arguments.of(JSOUP), + Arguments.of(JAVA11) ); }