diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 4edd932d..f3957e26 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -21,10 +21,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: '17' + java-version: '21' distribution: 'temurin' cache: maven - name: Build with Maven diff --git a/.java-version b/.java-version index 98d9bcb7..f2a4d8fb 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -17 +jdk-21.0.1+12 diff --git a/pom.xml b/pom.xml index 89036b74..15676ba7 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.github.libgraviton worker-base jar - 3.12.0-SNAPSHOT + 4.0.0-SNAPSHOT Graviton Worker Base Library https://github.com/libgraviton/graviton-worker-base-java 2015 @@ -131,18 +131,6 @@ guava 32.1.3-jre - - com.squareup.okhttp3 - okhttp - 4.11.0 - - - org.jetbrains - annotations - - - - commons-io commons-io @@ -306,7 +294,7 @@ com.github.libgraviton graviton-worker-test-base - 3.7.0 + 4.0.0 test @@ -316,9 +304,9 @@ - com.github.tomakehurst - wiremock-jre8 - 2.35.1 + org.wiremock + wiremock + 3.3.1 test @@ -362,10 +350,6 @@ com.google.guava guava - - com.squareup.okhttp3 - okhttp - commons-io commons-io @@ -424,6 +408,12 @@ classgraph + + com.github.mizosoft.methanol + methanol + 1.7.0 + + io.micrometer @@ -459,8 +449,8 @@ test - com.github.tomakehurst - wiremock-jre8 + org.wiremock + wiremock test @@ -511,8 +501,8 @@ maven-compiler-plugin 3.10.1 - 17 - 17 + 21 + 21 diff --git a/src/main/java/com/github/libgraviton/workerbase/di/WorkerBaseProvider.java b/src/main/java/com/github/libgraviton/workerbase/di/WorkerBaseProvider.java index 1de783d2..bb683542 100644 --- a/src/main/java/com/github/libgraviton/workerbase/di/WorkerBaseProvider.java +++ b/src/main/java/com/github/libgraviton/workerbase/di/WorkerBaseProvider.java @@ -11,24 +11,23 @@ import com.github.libgraviton.workerbase.gdk.api.endpoint.GeneratedEndpointManager; import com.github.libgraviton.workerbase.gdk.api.endpoint.exception.UnableToLoadEndpointAssociationsException; import com.github.libgraviton.workerbase.gdk.api.gateway.GravitonGateway; -import com.github.libgraviton.workerbase.gdk.api.gateway.OkHttpGateway; -import com.github.libgraviton.workerbase.gdk.api.gateway.okhttp.OkHttpGatewayFactory; +import com.github.libgraviton.workerbase.gdk.api.gateway.MethanolGateway; +import com.github.libgraviton.workerbase.gdk.api.gateway.http.MethanolGatewayFactory; import com.github.libgraviton.workerbase.gdk.requestexecutor.auth.Authenticator; import com.github.libgraviton.workerbase.gdk.requestexecutor.auth.GravitonGatewayAuthenticator; import com.github.libgraviton.workerbase.gdk.serialization.mapper.RqlObjectMapper; import com.github.libgraviton.workerbase.helper.EventStatusHandler; import com.github.libgraviton.workerbase.helper.WorkerProperties; +import com.github.mizosoft.methanol.Methanol; import io.activej.inject.Key; import io.activej.inject.annotation.Provides; import io.activej.inject.annotation.Transient; import io.activej.inject.binding.Binding; import io.activej.inject.module.AbstractModule; -import okhttp3.OkHttpClient; -import okhttp3.Protocol; import java.io.IOException; +import java.net.http.HttpClient; import java.text.SimpleDateFormat; -import java.util.Collections; import java.util.Properties; import java.util.TimeZone; import java.util.concurrent.*; @@ -42,7 +41,7 @@ protected void configure() { */ generate(GravitonGateway.class, (bindings, scope, key) -> { if (key.getType().equals(GravitonGateway.class)) { - return Binding.to(Key.of(OkHttpGateway.class)); + return Binding.to(Key.of(MethanolGateway.class)); } return null; }); @@ -77,25 +76,16 @@ public static GravitonApi gravitonApi(EndpointManager endpointManager, ObjectMap } @Provides - public static OkHttpClient getOkHttpClient() throws Exception { + public static Methanol getMethanol() throws Exception { final boolean hasRetry = WorkerProperties.HTTP_CLIENT_DORETRY.get().equals("true"); - final boolean forceHttp11 = WorkerProperties.HTTP_CLIENT_FORCE_HTTP1_1.get().equals("true"); final boolean trustAll = WorkerProperties.HTTP_CLIENT_TLS_TRUST_ALL.get().equals("true"); - OkHttpClient client = OkHttpGatewayFactory.getInstance(hasRetry); - if (trustAll) { - client = OkHttpGatewayFactory.getAllTrustingInstance(hasRetry, client); - } - - if (forceHttp11) { - client = client - .newBuilder() - .retryOnConnectionFailure(true) - .protocols(Collections.singletonList(Protocol.HTTP_1_1)) - .build(); - } + return MethanolGatewayFactory.getInstance(hasRetry, trustAll); + } - return client; + @Provides + public static HttpClient getHttpClient() throws Exception { + return getMethanol(); } @Provides diff --git a/src/main/java/com/github/libgraviton/workerbase/gdk/api/NoopResponse.java b/src/main/java/com/github/libgraviton/workerbase/gdk/api/NoopResponse.java index e2c25174..5a100f0c 100644 --- a/src/main/java/com/github/libgraviton/workerbase/gdk/api/NoopResponse.java +++ b/src/main/java/com/github/libgraviton/workerbase/gdk/api/NoopResponse.java @@ -15,7 +15,6 @@ public NoopResponse(NoopRequest request) { this.request = request; this.code = -1; this.isSuccessful = true; - this.message = "This is not the response you are looking for"; this.body = null; this.headers = new HeaderBag.Builder().build(); } diff --git a/src/main/java/com/github/libgraviton/workerbase/gdk/api/Response.java b/src/main/java/com/github/libgraviton/workerbase/gdk/api/Response.java index b3c13762..147a57fe 100644 --- a/src/main/java/com/github/libgraviton/workerbase/gdk/api/Response.java +++ b/src/main/java/com/github/libgraviton/workerbase/gdk/api/Response.java @@ -26,8 +26,6 @@ public class Response { protected int code; - protected String message; - protected HeaderBag headers; protected byte[] body; @@ -43,7 +41,6 @@ protected Response(com.github.libgraviton.workerbase.gdk.api.Response.Builder bu request = builder.request; code = builder.code; isSuccessful = builder.isSuccessful; - message = builder.message; body = builder.body; headers = builder.headerBuilder.build(); } @@ -151,16 +148,10 @@ public int getCode() { return code; } - public String getMessage() { - return message; - } - public static class Builder { protected int code; - protected String message; - protected final Request request; protected byte[] body; @@ -184,11 +175,6 @@ public Builder successful(boolean isSuccessful) { return this; } - public Builder message(String message) { - this.message = message; - return this; - } - public Builder body(byte[] body) { this.body = body; return this; diff --git a/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/MethanolGateway.java b/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/MethanolGateway.java new file mode 100644 index 00000000..fd711d8a --- /dev/null +++ b/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/MethanolGateway.java @@ -0,0 +1,124 @@ +package com.github.libgraviton.workerbase.gdk.api.gateway; + +import com.github.libgraviton.workerbase.annotation.GravitonWorkerDiScan; +import com.github.libgraviton.workerbase.gdk.api.Request; +import com.github.libgraviton.workerbase.gdk.api.Response; +import com.github.libgraviton.workerbase.gdk.api.header.Header; +import com.github.libgraviton.workerbase.gdk.api.header.HeaderBag; +import com.github.libgraviton.workerbase.gdk.api.multipart.FilePart; +import com.github.libgraviton.workerbase.gdk.api.multipart.Part; +import com.github.libgraviton.workerbase.gdk.exception.CommunicationException; +import com.github.mizosoft.methanol.Methanol; +import com.github.mizosoft.methanol.MultipartBodyPublisher; +import com.github.mizosoft.methanol.MutableRequest; +import io.activej.inject.annotation.Inject; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Map; + +@GravitonWorkerDiScan +public class MethanolGateway implements GravitonGateway { + + @Inject + Methanol httpClient; + + @Inject + public MethanolGateway() { + + } + + public HttpClient getHttpClient() { + return httpClient; + } + + public Response execute(Request request) throws CommunicationException { + try { + HttpRequest req = generateRequest(request); + + HttpResponse response = httpClient.send(req, HttpResponse.BodyHandlers.ofByteArray()); + return generateResponse(request, response); + } catch (FileNotFoundException | URISyntaxException e) { + throw new CommunicationException("Unable to create request", e); + } catch (IOException | InterruptedException e) { + throw new CommunicationException("Error sending request", e); + } + } + + private HttpRequest generateRequest(Request request) throws FileNotFoundException, URISyntaxException { + HttpRequest.BodyPublisher body; + + if (request.isMultipartRequest()) { + body = generateMultipartRequestBody(request); + } else { + if (null == request.getBodyBytes()) { + body = HttpRequest.BodyPublishers.noBody(); + } else { + body = HttpRequest.BodyPublishers.ofByteArray(request.getBodyBytes()); + } + } + + HttpRequest.Builder builder = MutableRequest.newBuilder() + .uri(request.getUrl().toURI()) + .method(request.getMethod().asString(), body); + + // set headers + for (Map.Entry header : request.getHeaders().all().entrySet()) { + for (String value : header.getValue()) { + builder.header(header.getKey(), value); + } + } + + return builder.build(); + } + + private MultipartBodyPublisher generateMultipartRequestBody(Request request) throws FileNotFoundException { + MultipartBodyPublisher.Builder multipartBuilder = MultipartBodyPublisher.newBuilder(); + + int partNumber = 1; + for (Part part : request.getParts()) { + if (part.getFormName() != null) { + multipartBuilder.formPart(part.getFormName(), HttpRequest.BodyPublishers.ofByteArray(part.getBody())); + } else { + String partName = String.format("part-%s", partNumber); + multipartBuilder.formPart(partName, HttpRequest.BodyPublishers.ofByteArray(part.getBody())); + } + partNumber++; + } + + for (FilePart part : request.getFileParts()) { + if (part.getFormName() != null) { + multipartBuilder.filePart(part.getFormName(), part.getBody().toPath()); + } else { + String partName = String.format("part-%s", partNumber); + multipartBuilder.filePart(partName, part.getBody().toPath(), com.github.mizosoft.methanol.MediaType.parse(part.getContentType())); + } + partNumber++; + } + + return multipartBuilder.build(); + } + + private Response generateResponse(Request req, HttpResponse resp) { + Response.Builder responseBuilder = new Response.Builder(req); + + return responseBuilder + .code(resp.statusCode()) + .headers(createResponseHeaders(resp.headers())) + .successful(resp.statusCode() < 300) + .body(resp.body()) + .build(); + } + + private HeaderBag.Builder createResponseHeaders(HttpHeaders headers) { + HeaderBag.Builder builder = new HeaderBag.Builder(); + headers.map().forEach(builder::set); + return builder; + } + +} diff --git a/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/OkHttpGateway.java b/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/OkHttpGateway.java deleted file mode 100644 index 41248cf3..00000000 --- a/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/OkHttpGateway.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.github.libgraviton.workerbase.gdk.api.gateway; - -import com.github.libgraviton.workerbase.annotation.GravitonWorkerDiScan; -import com.github.libgraviton.workerbase.gdk.api.Request; -import com.github.libgraviton.workerbase.gdk.api.Response; -import com.github.libgraviton.workerbase.gdk.api.header.Header; -import com.github.libgraviton.workerbase.gdk.api.header.HeaderBag; -import com.github.libgraviton.workerbase.gdk.api.multipart.FilePart; -import com.github.libgraviton.workerbase.gdk.api.multipart.Part; -import com.github.libgraviton.workerbase.gdk.exception.CommunicationException; -import com.github.libgraviton.workerbase.gdk.exception.UnsuccessfulRequestException; -import io.activej.inject.annotation.Inject; -import okhttp3.*; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -@GravitonWorkerDiScan -public class OkHttpGateway implements GravitonGateway { - - @Inject - OkHttpClient okHttp; - - @Inject - public OkHttpGateway() { - - } - - public OkHttpClient getOkHttp() { - return okHttp; - } - - public Response execute(Request request) throws CommunicationException { - okhttp3.Request okHttpRequest = generateRequest(request); - - try (okhttp3.Response okHttpResponse = okHttp.newCall(okHttpRequest).execute()) { - byte[] body = okHttpResponse.body().bytes(); - return generateResponse(request, okHttpResponse, body); - } catch (IOException e) { - throw new UnsuccessfulRequestException( - String.format("'%s' to '%s' failed.", request.getMethod(), request.getUrl()), - e - ); - } - } - - private okhttp3.Request generateRequest(Request request) { - RequestBody okHttpBody; - if (request.isMultipartRequest()) { - okHttpBody = generateMultipartRequestBody(request); - } else { - okHttpBody = generateDefaultRequestBody(request); - } - - okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder(); - return requestBuilder - .method(request.getMethod().asString(), okHttpBody) - .url(request.getUrl()) - .headers(createRequestHeaders(request.getHeaders())) - .build(); - } - - private RequestBody generateMultipartRequestBody(Request request) { - MultipartBody.Builder builder = new MultipartBody.Builder(); - // string based parts - for (Part part : request.getParts()) { - MultipartBody.Part bodyPart; - RequestBody requestBody = RequestBody.create(part.getBody()); - if (part.getFormName() != null) { - bodyPart = MultipartBody.Part.createFormData(part.getFormName(), null, requestBody); - } else { - bodyPart = MultipartBody.Part.create(null, requestBody); - } - - builder.addPart(bodyPart); - } - // file parts - for (FilePart part : request.getFileParts()) { - MultipartBody.Part bodyPart; - RequestBody requestBody = RequestBody.create(part.getBody(), MediaType.parse(part.getContentType())); - if (part.getFormName() != null) { - bodyPart = MultipartBody.Part.createFormData(part.getFormName(), null, requestBody); - } else { - bodyPart = MultipartBody.Part.create(null, requestBody); - } - - builder.addPart(bodyPart); - } - - return builder.build(); - } - - private RequestBody generateDefaultRequestBody(Request request) { - if (null == request.getBodyBytes()) { - return null; - } - - return RequestBody.create( - request.getBodyBytes(), - MediaType.parse( - String.format( - "%s; charset=utf-8", - request.getHeaders().get("Content-Type").toString() - ) - ) - ); - } - - private Response generateResponse(Request request, okhttp3.Response okHttpResponse, byte[] body) { - Response.Builder responseBuilder = new Response.Builder(request); - return responseBuilder - .code(okHttpResponse.code()) - .headers(createResponseHeaders(okHttpResponse.headers())) - .message(okHttpResponse.message()) - .successful(okHttpResponse.isSuccessful()) - .body(body) - .build(); - } - - private Headers createRequestHeaders(HeaderBag headerBag) { - Headers.Builder builder = new Headers.Builder(); - for (Map.Entry header : headerBag.all().entrySet()) { - for (String value : header.getValue()) { - builder.add(header.getKey(), value); - } - } - return builder.build(); - } - - private HeaderBag.Builder createResponseHeaders(Headers okhttpHeaders) { - HeaderBag.Builder builder = new HeaderBag.Builder(); - for (Map.Entry> header : okhttpHeaders.toMultimap().entrySet()) { - builder.set(header.getKey(), header.getValue()); - } - return builder; - } - -} diff --git a/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/http/MethanolGatewayFactory.java b/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/http/MethanolGatewayFactory.java new file mode 100644 index 00000000..3a9e647e --- /dev/null +++ b/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/http/MethanolGatewayFactory.java @@ -0,0 +1,76 @@ +package com.github.libgraviton.workerbase.gdk.api.gateway.http; + +import com.github.libgraviton.workerbase.gdk.util.http.interceptor.RetryInterceptor; +import com.github.mizosoft.methanol.Methanol; + +import javax.net.ssl.*; +import java.net.Socket; +import java.net.http.HttpClient; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +public class MethanolGatewayFactory { + + private static final TrustManager MOCK_TRUST_MANAGER = new X509ExtendedTrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException { + + } + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException { + + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { + // empty method + } + }; + + /** + * gets the normal instance without retry interceptor + * + * @return instance + */ + public static Methanol getInstance(boolean hasRetry, boolean trustAll) throws NoSuchAlgorithmException, KeyManagementException { + Methanol.Builder builder = Methanol.newBuilder(); + if (trustAll) { + SSLContext sslContext = SSLContext.getInstance("SSL"); // OR TLS + sslContext.init(null, new TrustManager[]{MOCK_TRUST_MANAGER}, new SecureRandom()); + + builder.sslContext(sslContext); + } + + if (hasRetry) { + builder.interceptor(new RetryInterceptor()); + } + + builder.followRedirects(HttpClient.Redirect.ALWAYS); + + return builder.build(); + } +} diff --git a/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/okhttp/OkHttpGatewayFactory.java b/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/okhttp/OkHttpGatewayFactory.java deleted file mode 100644 index 4fe03930..00000000 --- a/src/main/java/com/github/libgraviton/workerbase/gdk/api/gateway/okhttp/OkHttpGatewayFactory.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.github.libgraviton.workerbase.gdk.api.gateway.okhttp; - -import com.github.libgraviton.workerbase.gdk.util.okhttp.interceptor.RetryInterceptor; -import com.github.libgraviton.workerbase.helper.WorkerUtil; -import okhttp3.OkHttpClient; -import okhttp3.OkHttpClient.Builder; - -import javax.net.ssl.X509TrustManager; -import java.util.concurrent.TimeUnit; - -public class OkHttpGatewayFactory { - - /** - * gets the normal instance without retry interceptor - * - * @return instance - */ - public static OkHttpClient getInstance(boolean hasRetry) { - return getBaseBuilder(hasRetry).build(); - } - - /** - * returns a client that trusts all certs - * - * @param hasRetry if should have retry or not - * @return - * @throws Exception - */ - public static OkHttpClient getAllTrustingInstance(boolean hasRetry, OkHttpClient baseClient) throws Exception { - Builder baseBuilder; - if (baseClient != null) { - baseBuilder = baseClient.newBuilder(); - } else { - baseBuilder = getBaseBuilder(hasRetry); - } - - baseBuilder - .sslSocketFactory(WorkerUtil.getAllTrustingSocketFactory(), (X509TrustManager) WorkerUtil.getAllTrustingTrustManagers()[0]) - .hostnameVerifier((hostname, session) -> true); - - return baseBuilder.build(); - } - - private static Builder getBaseBuilder(boolean hasRetry) { - Builder builder = new Builder() - .connectTimeout(60, TimeUnit.SECONDS) - .writeTimeout(60, TimeUnit.SECONDS) - .readTimeout(60, TimeUnit.SECONDS); - - if (hasRetry) { - builder.addInterceptor(new RetryInterceptor()); - } - - return builder; - } -} diff --git a/src/main/java/com/github/libgraviton/workerbase/gdk/auth/BasicAuth.java b/src/main/java/com/github/libgraviton/workerbase/gdk/auth/BasicAuth.java index 975bd342..e9d97b3d 100644 --- a/src/main/java/com/github/libgraviton/workerbase/gdk/auth/BasicAuth.java +++ b/src/main/java/com/github/libgraviton/workerbase/gdk/auth/BasicAuth.java @@ -1,13 +1,12 @@ package com.github.libgraviton.workerbase.gdk.auth; import com.github.libgraviton.workerbase.gdk.api.header.HeaderBag; -import com.github.libgraviton.workerbase.gdk.auth.HeaderAuth; -import okio.ByteString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Base64; public class BasicAuth implements HeaderAuth { @@ -35,7 +34,8 @@ public String basic() { public String basic(Charset charset) { String usernameAndPassword = username + ":" + password; byte[] bytes = usernameAndPassword.getBytes(charset); - String encoded = ByteString.of(bytes).base64(); - return "Basic ".concat(encoded); + + String encodedString = Base64.getEncoder().encodeToString(bytes); + return "Basic ".concat(encodedString); } } diff --git a/src/main/java/com/github/libgraviton/workerbase/gdk/exception/UnsuccessfulResponseException.java b/src/main/java/com/github/libgraviton/workerbase/gdk/exception/UnsuccessfulResponseException.java index 03afe024..6673a9f0 100644 --- a/src/main/java/com/github/libgraviton/workerbase/gdk/exception/UnsuccessfulResponseException.java +++ b/src/main/java/com/github/libgraviton/workerbase/gdk/exception/UnsuccessfulResponseException.java @@ -1,7 +1,6 @@ package com.github.libgraviton.workerbase.gdk.exception; import com.github.libgraviton.workerbase.gdk.api.Response; -import com.github.libgraviton.workerbase.gdk.exception.CommunicationException; /** * Whenever a received response code is not within 200 - 299. @@ -21,11 +20,10 @@ public Response getResponse() { private static String generateMessage(Response response) { return String.format( - "Failed '%s' to '%s'. Response was '%d' - '%s' with body '%s'.", + "Failed '%s' to '%s'. Response was '%d' - body '%s'.", response.getRequest().getMethod(), response.getRequest().getUrl(), response.getCode(), - response.getMessage(), response.getBody() ); } diff --git a/src/main/java/com/github/libgraviton/workerbase/gdk/util/okhttp/interceptor/RetryInterceptor.java b/src/main/java/com/github/libgraviton/workerbase/gdk/util/http/interceptor/RetryInterceptor.java similarity index 59% rename from src/main/java/com/github/libgraviton/workerbase/gdk/util/okhttp/interceptor/RetryInterceptor.java rename to src/main/java/com/github/libgraviton/workerbase/gdk/util/http/interceptor/RetryInterceptor.java index 8a3dd7e5..363d22e5 100644 --- a/src/main/java/com/github/libgraviton/workerbase/gdk/util/okhttp/interceptor/RetryInterceptor.java +++ b/src/main/java/com/github/libgraviton/workerbase/gdk/util/http/interceptor/RetryInterceptor.java @@ -1,19 +1,19 @@ -package com.github.libgraviton.workerbase.gdk.util.okhttp.interceptor; +package com.github.libgraviton.workerbase.gdk.util.http.interceptor; import com.github.libgraviton.workerbase.util.RetryRegistry; +import com.github.mizosoft.methanol.Methanol; import io.github.resilience4j.core.functions.CheckedSupplier; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.time.Duration; import java.util.List; +import java.util.concurrent.CompletableFuture; -public class RetryInterceptor implements Interceptor { +public class RetryInterceptor implements Methanol.Interceptor { private static final Logger LOG = LoggerFactory.getLogger(RetryInterceptor.class); private final int retryCount; @@ -22,7 +22,7 @@ public class RetryInterceptor implements Interceptor { public RetryInterceptor() { this( 6 * 10, // 10 minutes - 10 + 5 ); } @@ -31,15 +31,13 @@ public RetryInterceptor(int retryCount, int waitInBetween) { this.waitInBetween = waitInBetween; } - @Override public @NotNull Response intercept(@NotNull Chain chain) throws IOException { + @Override + public HttpResponse intercept(HttpRequest request, Chain chain) throws IOException { + CheckedSupplier> responseSupplier = () -> { + HttpResponse response = chain.forward(request); - final Request request = chain.request(); - CheckedSupplier responseSupplier = () -> { - Response response = chain.proceed(request); - - if (List.of(500, 502, 503).contains(response.code())) { - response.close(); - throw new IOException("Got response status " + response.code()); + if (List.of(500, 502, 503).contains(response.statusCode())) { + throw new IOException("Got response status " + response.statusCode()); } return response; @@ -61,4 +59,9 @@ public RetryInterceptor(int retryCount, int waitInBetween) { } } + @Override + public CompletableFuture> interceptAsync(HttpRequest request, Chain chain) { + LOG.warn("Called async for retryInterceptor; is not supported."); + return chain.forwardAsync(request); + } } diff --git a/src/main/java/com/github/libgraviton/workerbase/helper/WorkerScope.java b/src/main/java/com/github/libgraviton/workerbase/helper/WorkerScope.java index bc3998d3..939d9b04 100644 --- a/src/main/java/com/github/libgraviton/workerbase/helper/WorkerScope.java +++ b/src/main/java/com/github/libgraviton/workerbase/helper/WorkerScope.java @@ -7,9 +7,9 @@ import com.github.libgraviton.workerbase.gdk.GravitonApi; import com.github.libgraviton.workerbase.gdk.GravitonFileEndpoint; import io.activej.inject.annotation.Inject; -import okhttp3.HttpUrl; import org.apache.commons.collections4.map.HashedMap; +import java.net.URI; import java.util.Map; import java.util.Properties; @@ -65,17 +65,24 @@ public Map getScopeCacheMap() { * @return corrected url */ public String convertToGravitonUrl(String url) { - HttpUrl baseUrl = HttpUrl.parse(WorkerProperties.GRAVITON_BASE_URL.get()); + try { + URI baseUri = new URI(WorkerProperties.GRAVITON_BASE_URL.get()); + URI srcUri = new URI(url); - // convert - HttpUrl targetUrl = HttpUrl - .parse(url) - .newBuilder() - .host(baseUrl.host()) - .port(baseUrl.port()) - .scheme(baseUrl.scheme()) - .build(); + URI newUri = new URI( + baseUri.getScheme(), + baseUri.getUserInfo(), + baseUri.getHost(), + baseUri.getPort(), + srcUri.getPath(), + srcUri.getQuery(), + srcUri.getFragment() + ); - return targetUrl.toString(); + return newUri.toURL().toString(); + } catch (Throwable t) { + } + + return url; } } diff --git a/src/main/java/com/github/libgraviton/workerbase/util/DownloadClient.java b/src/main/java/com/github/libgraviton/workerbase/util/DownloadClient.java index 64987b2a..449afec0 100644 --- a/src/main/java/com/github/libgraviton/workerbase/util/DownloadClient.java +++ b/src/main/java/com/github/libgraviton/workerbase/util/DownloadClient.java @@ -1,16 +1,22 @@ package com.github.libgraviton.workerbase.util; -import java.io.File; import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Map; import com.github.libgraviton.workerbase.helper.DependencyInjection; -import okhttp3.*; -import okio.BufferedSink; -import okio.Okio; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class DownloadClient { + private static final Logger LOG = LoggerFactory.getLogger(DownloadClient.class); + /** * Downloads a file to disk * @@ -19,15 +25,20 @@ public class DownloadClient { * @throws Exception */ public static void downloadFile(String url, final String targetPath, Map requestHeaders) { - try (ResponseBody body = getResponseBody(url, requestHeaders)) { - File file = new File(targetPath); - BufferedSink sink = Okio.buffer(Okio.sink(file)); - - sink.writeAll(body.source()); - sink.flush(); - sink.close(); - } catch (Exception e) { - throw new RuntimeException("Error downloading URL '" + url + "'", e); + try { + HttpClient httpClient = DependencyInjection.getInstance(HttpClient.class); + + HttpRequest.Builder reqBuilder = HttpRequest.newBuilder(new URI(url)); + requestHeaders.forEach(reqBuilder::header); + + httpClient.send( + reqBuilder.build(), + HttpResponse.BodyHandlers.ofFile(Path.of(targetPath)) + ); + + LOG.info("Downloaded '{}' to path '{}'", url, targetPath); + } catch (Throwable t) { + LOG.error("Error downloading url '{}'", url, t); } } @@ -37,22 +48,23 @@ public static void downloadFile(String url, final String targetPath) { @Deprecated(since = "Use writeFileContentToDisk(), File and streams to deal with files, not byte arrays!") public static byte[] downloadFileBytes(String url) throws IOException { - try (ResponseBody body = getResponseBody(url, Map.of())) { - return body.bytes(); - } - } - - private static ResponseBody getResponseBody(String url, Map requestHeaders) throws IOException { - Request request = new Request.Builder().url(url).headers(Headers.of(requestHeaders)).build(); + java.io.File finalTemp = java.io.File.createTempFile("grv-file", ".tmp"); - OkHttpClient client = DependencyInjection.getInstance(OkHttpClient.class); - Response response = client.newCall(request).execute(); + // download + downloadFile(url, finalTemp.getAbsolutePath(), Map.of()); - if (response.code() < 200 || response.code() > 300) { - throw new RuntimeException( - "Download of url '" + url + "' returned an unexpected status code of " + response.code()); + if (!finalTemp.exists()) { + LOG.error("URL '{}' could not be downloaded", url); + throw new IOException("Could not download URL"); } - return response.body(); + byte[] contents = Files.readAllBytes(finalTemp.toPath()); + + // delete file + finalTemp.delete(); + + // read into array + return contents; } + } diff --git a/src/test/java/com/github/libgraviton/workerbase/gdk/api/ResponseTest.java b/src/test/java/com/github/libgraviton/workerbase/gdk/api/ResponseTest.java index 29a92b4d..9feef362 100644 --- a/src/test/java/com/github/libgraviton/workerbase/gdk/api/ResponseTest.java +++ b/src/test/java/com/github/libgraviton/workerbase/gdk/api/ResponseTest.java @@ -25,7 +25,6 @@ public void setup() throws Exception { response = new Response.Builder(request) .body("{\"code\":0}".getBytes()) .successful(true) - .message("a message") .code(200) .build(); response.setObjectMapper(new ObjectMapper()); @@ -59,7 +58,6 @@ public void testSuccessfulDeserializeBody() throws DeserializationException { response.getBodyItem(SerializationTestClass.class); Assertions.assertTrue(response.isSuccessful()); Assertions.assertEquals(200, response.getCode()); - Assertions.assertEquals("a message", response.getMessage()); Assertions.assertEquals("{\"code\":0}", response.getBody()); Assertions.assertEquals(request, response.getRequest()); Assertions.assertEquals(0, response.getHeaders().all().size()); @@ -76,7 +74,6 @@ public void testDeserializeBodyAsList() throws Exception { response = new Response.Builder(request) .body(new ObjectMapper().writeValueAsString(testClasses).getBytes()) .successful(true) - .message("a message") .code(200) .build(); response.setObjectMapper(new ObjectMapper()); diff --git a/src/test/java/com/github/libgraviton/workerbase/gdk/okhttp/RetryInterceptorTest.java b/src/test/java/com/github/libgraviton/workerbase/gdk/okhttp/RetryInterceptorTest.java index 3867cb8d..eb5efd8b 100644 --- a/src/test/java/com/github/libgraviton/workerbase/gdk/okhttp/RetryInterceptorTest.java +++ b/src/test/java/com/github/libgraviton/workerbase/gdk/okhttp/RetryInterceptorTest.java @@ -1,17 +1,20 @@ package com.github.libgraviton.workerbase.gdk.okhttp; -import com.github.libgraviton.workerbase.gdk.util.okhttp.interceptor.RetryInterceptor; +import com.github.libgraviton.workerbase.gdk.util.http.interceptor.RetryInterceptor; import com.github.libgraviton.workertestbase.WorkerTestExtension; +import com.github.mizosoft.methanol.Methanol; +import com.github.mizosoft.methanol.MutableRequest; import com.github.tomakehurst.wiremock.stubbing.Scenario; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import java.io.IOException; import java.net.ConnectException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import static com.github.tomakehurst.wiremock.client.WireMock.*; @@ -21,22 +24,21 @@ public class RetryInterceptorTest { public static WorkerTestExtension workerTestExtension = (new WorkerTestExtension()) .setStartWiremock(true); - private OkHttpClient client; + private HttpClient client; @Test public void testWrongHostHandling() { Assertions.assertThrows(IOException.class, () -> { RetryInterceptor retryInterceptor = new RetryInterceptor(5, 1); - client = new OkHttpClient.Builder() - .addInterceptor(retryInterceptor) + client = Methanol.newBuilder() + .interceptor(retryInterceptor) .build(); - Request request = new Request.Builder() - .url("http://myservice/my-precious-url/4") - .build(); + HttpRequest request = MutableRequest.newBuilder(new URI("http://myservice/my-precious-url/4")) + .build(); - client.newCall(request).execute(); + client.send(request, HttpResponse.BodyHandlers.discarding()); }); } @@ -45,16 +47,15 @@ public void testWrongPortHandling() throws Exception { Assertions.assertThrows(IOException.class, () -> { RetryInterceptor retryInterceptor = new RetryInterceptor(5, 1); - client = new OkHttpClient.Builder() - .addInterceptor(retryInterceptor) - .build(); + client = Methanol.newBuilder() + .interceptor(retryInterceptor) + .build(); - Request request = new Request.Builder() - .url("http://localhost:9988") - .build(); + HttpRequest request = MutableRequest.newBuilder(new URI("http://localhost:9988")) + .build(); try { - client.newCall(request).execute(); + client.send(request, HttpResponse.BodyHandlers.discarding()); } catch (ConnectException e) { throw e; } @@ -91,18 +92,17 @@ public void testWrongHttpStatusHandling() throws Exception { RetryInterceptor retryInterceptor = new RetryInterceptor(5, 1); - client = new OkHttpClient.Builder() - .addInterceptor(retryInterceptor) - .build(); + client = Methanol.newBuilder() + .interceptor(retryInterceptor) + .build(); - Request request = new Request.Builder() - .url(workerTestExtension.getWiremockUrl() + "/service") - .build(); + HttpRequest request = MutableRequest.newBuilder(new URI(workerTestExtension.getWiremockUrl() + "/service")) + .build(); - Response response = client.newCall(request).execute(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - Assertions.assertEquals(200, response.code()); - Assertions.assertEquals("YEEEEES!", response.body().string()); + Assertions.assertEquals(200, response.statusCode()); + Assertions.assertEquals("YEEEEES!", response.body()); } } diff --git a/src/test/java/com/github/libgraviton/workerbase/util/DownloadClientTest.java b/src/test/java/com/github/libgraviton/workerbase/util/DownloadClientTest.java new file mode 100644 index 00000000..6734534e --- /dev/null +++ b/src/test/java/com/github/libgraviton/workerbase/util/DownloadClientTest.java @@ -0,0 +1,50 @@ +package com.github.libgraviton.workerbase.util; + +import com.github.libgraviton.workertestbase.WorkerTestExtension; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.io.File; +import java.util.Map; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; + +public class DownloadClientTest { + + @RegisterExtension + public static WorkerTestExtension workerTestExtension = (new WorkerTestExtension()) + .setStartWiremock(true); + + @Test + public void testBasicDownload() { + String target = "/tmp/PDF-TEST-FILE.pdf"; + String url = workerTestExtension.getWiremockUrl() + "/test.pdf"; + + StubMapping redir = workerTestExtension.getWireMockServer() + .stubFor( + get(urlMatching("/test.pdf")) + .withHeader("FRANZ", equalTo("TEST")) + .willReturn( + aResponse().withStatus(301).withHeader("location", "/file/test.pdf") + ) + ); + + StubMapping filestub = workerTestExtension.getWireMockServer() + .stubFor( + get(urlMatching("/file/test.pdf")) + // the transient headers + .willReturn( + aResponse().withBodyFile("test.pdf").withStatus(200) + ) + ); + + DownloadClient.downloadFile(url, target, Map.of("FRANZ", "TEST")); + + File file = new File(target); + Assertions.assertEquals(7021, file.length()); + file.delete(); + } + +} diff --git a/src/test/resources/files/test.pdf b/src/test/resources/__files/test.pdf similarity index 100% rename from src/test/resources/files/test.pdf rename to src/test/resources/__files/test.pdf