From 958c66330acc05fa3ac45c1c22be6f2090a795ac Mon Sep 17 00:00:00 2001 From: Filip Date: Tue, 5 Dec 2023 21:55:07 +0100 Subject: [PATCH 1/7] Add setTimeout method to [Default]HttpClient --- .../jsonld/http/DefaultHttpClient.java | 13 +++++ .../apicatalog/jsonld/http/HttpClient.java | 8 +++ .../jsonld/loader/DefaultHttpLoader.java | 51 ++++++++++--------- .../jsonld/http/DefaultHttpClient.java | 23 ++++++--- 4 files changed, 65 insertions(+), 30 deletions(-) diff --git a/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java b/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java index 883f059d..0ddf971e 100644 --- a/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java +++ b/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java @@ -3,9 +3,11 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.time.Duration; import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.concurrent.TimeUnit; import com.apicatalog.jsonld.JsonLdError; import com.apicatalog.jsonld.JsonLdErrorCode; @@ -53,6 +55,17 @@ public static final HttpClient defaultInstance() { return INSTANCE; } + @Override + public void setTimeout(Duration timeout) { + okHttpClient = okHttpClient + .newBuilder() + .readTimeout(timeount != null + ? timeout.toMillis() + : 0, + TimeUnit.MILLISECONDS) + .build(); + } + static class HttpResponseImpl implements HttpResponse { private Response response; diff --git a/src/main/java/com/apicatalog/jsonld/http/HttpClient.java b/src/main/java/com/apicatalog/jsonld/http/HttpClient.java index 48590c95..9295bd82 100644 --- a/src/main/java/com/apicatalog/jsonld/http/HttpClient.java +++ b/src/main/java/com/apicatalog/jsonld/http/HttpClient.java @@ -1,11 +1,19 @@ package com.apicatalog.jsonld.http; import java.net.URI; +import java.time.Duration; import com.apicatalog.jsonld.JsonLdError; public interface HttpClient { HttpResponse send(URI targetUri, String requestProfile) throws JsonLdError; + + /** + * Set read timeout + * + * @param timeout to set or null for no timeout + */ + void setTimeout(Duration timeout); } diff --git a/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java b/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java index 0332938a..84ae224b 100644 --- a/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java +++ b/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.time.Duration; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -76,10 +77,9 @@ public Document loadDocument(final URI uri, final DocumentLoaderOptions options) // 3. if (response.statusCode() == 301 - || response.statusCode() == 302 - || response.statusCode() == 303 - || response.statusCode() == 307 - ) { + || response.statusCode() == 302 + || response.statusCode() == 303 + || response.statusCode() == 307) { final Optional location = response.location(); @@ -108,19 +108,16 @@ public Document loadDocument(final URI uri, final DocumentLoaderOptions options) // 4. if (contentType == null || (!MediaType.JSON.match(contentType) - && !contentType.subtype().toLowerCase().endsWith(PLUS_JSON)) - ) { + && !contentType.subtype().toLowerCase().endsWith(PLUS_JSON))) { final URI baseUri = targetUri; - Optional alternate = - linkValues.stream() - .flatMap(l -> Link.of(l, baseUri).stream()) - .filter(l -> l.relations().contains("alternate") - && l.type().isPresent() - && MediaType.JSON_LD.match(l.type().get()) - ) - .findFirst(); + Optional alternate = linkValues.stream() + .flatMap(l -> Link.of(l, baseUri).stream()) + .filter(l -> l.relations().contains("alternate") + && l.type().isPresent() + && MediaType.JSON_LD.match(l.type().get())) + .findFirst(); if (alternate.isPresent()) { @@ -133,16 +130,14 @@ public Document loadDocument(final URI uri, final DocumentLoaderOptions options) if (contentType != null && !MediaType.JSON_LD.match(contentType) && (MediaType.JSON.match(contentType) - || contentType.subtype().toLowerCase().endsWith(PLUS_JSON)) - ) { + || contentType.subtype().toLowerCase().endsWith(PLUS_JSON))) { final URI baseUri = targetUri; - final List contextUris = - linkValues.stream() - .flatMap(l -> Link.of(l, baseUri).stream()) - .filter(l -> l.relations().contains(ProfileConstants.CONTEXT)) - .collect(Collectors.toList()); + final List contextUris = linkValues.stream() + .flatMap(l -> Link.of(l, baseUri).stream()) + .filter(l -> l.relations().contains(ProfileConstants.CONTEXT)) + .collect(Collectors.toList()); if (contextUris.size() > 1) { throw new JsonLdError(JsonLdErrorCode.MULTIPLE_CONTEXT_LINK_HEADERS); @@ -214,9 +209,19 @@ private final Document resolve( * Set fallback content-type used when received content-type is not supported. * e.g. setFallbackContentType(MediaType.JSON_LD) * - * @param fallbackContentType a content type that overrides unsupported received content-type + * @param fallbackContentType a content type that overrides unsupported received + * content-type */ public void setFallbackContentType(MediaType fallbackContentType) { - this.resolver.setFallbackContentType(fallbackContentType); + resolver.setFallbackContentType(fallbackContentType); + } + + /** + * Set read timeout + * + * @param timeount to set or null for no timeout + */ + public void setTimeount(Duration timeount) { + httpClient.setTimeout(timeount); } } diff --git a/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java b/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java index 95db63f7..733bee25 100644 --- a/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java +++ b/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java @@ -21,6 +21,7 @@ import java.net.http.HttpClient.Redirect; import java.net.http.HttpRequest; import java.net.http.HttpResponse.BodyHandlers; +import java.time.Duration; import java.util.Collection; import java.util.Optional; @@ -34,19 +35,21 @@ public final class DefaultHttpClient implements HttpClient { private static final DefaultHttpClient INSTANCE = new DefaultHttpClient(CLIENT); private final java.net.http.HttpClient httpClient; + private Duration timeout; public DefaultHttpClient(final java.net.http.HttpClient httpClient) { this.httpClient = httpClient; + this.timeout = null; } public HttpResponse send(URI targetUri, String requestProfile) throws JsonLdError { - HttpRequest request = - HttpRequest.newBuilder() - .GET() - .uri(targetUri) - .header("Accept", requestProfile) - .build(); + HttpRequest request = HttpRequest.newBuilder() + .GET() + .uri(targetUri) + .header("Accept", requestProfile) + .timeout(timeout) + .build(); try { return new HttpResponseImpl(httpClient.send(request, BodyHandlers.ofInputStream())); @@ -66,6 +69,11 @@ public static final HttpClient defaultInstance() { return INSTANCE; } + @Override + public void setTimeout(Duration timeout) { + this.timeout = timeout; + } + public static class HttpResponseImpl implements HttpResponse { private final java.net.http.HttpResponse response; @@ -100,6 +108,7 @@ public Optional location() { } @Override - public void close() { /* unused */ } + public void close() { + /* unused */ } } } From b6391a5c001e1f3546a5e7fdd1f341dfb08d50d9 Mon Sep 17 00:00:00 2001 From: Filip Date: Tue, 5 Dec 2023 22:03:52 +0100 Subject: [PATCH 2/7] Fix timeout initialization --- .../java/com/apicatalog/jsonld/http/HttpClient.java | 6 +++++- .../apicatalog/jsonld/http/DefaultHttpClient.java | 12 +++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/apicatalog/jsonld/http/HttpClient.java b/src/main/java/com/apicatalog/jsonld/http/HttpClient.java index 9295bd82..e79ae00a 100644 --- a/src/main/java/com/apicatalog/jsonld/http/HttpClient.java +++ b/src/main/java/com/apicatalog/jsonld/http/HttpClient.java @@ -13,7 +13,11 @@ public interface HttpClient { * Set read timeout * * @param timeout to set or null for no timeout + * + * @since 1.4.0 */ - void setTimeout(Duration timeout); + default void setTimeout(Duration timeout) { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java b/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java index 733bee25..92743c67 100644 --- a/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java +++ b/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java @@ -44,15 +44,17 @@ public DefaultHttpClient(final java.net.http.HttpClient httpClient) { public HttpResponse send(URI targetUri, String requestProfile) throws JsonLdError { - HttpRequest request = HttpRequest.newBuilder() + HttpRequest.Builder request = HttpRequest.newBuilder() .GET() .uri(targetUri) - .header("Accept", requestProfile) - .timeout(timeout) - .build(); + .header("Accept", requestProfile); + + if (timeout != null && !timeout.isNegative() && !timeout.isZero()) { + request = request.timeout(timeout); + } try { - return new HttpResponseImpl(httpClient.send(request, BodyHandlers.ofInputStream())); + return new HttpResponseImpl(httpClient.send(request.build(), BodyHandlers.ofInputStream())); } catch (InterruptedException e) { From 7bcea2d1a08902b471038f6a142eeec4dceacc27 Mon Sep 17 00:00:00 2001 From: Filip Date: Tue, 5 Dec 2023 22:18:45 +0100 Subject: [PATCH 3/7] Add PROCESSING_TIMEOUT_EXCEEDED error code --- src/main/java/com/apicatalog/jsonld/JsonLdErrorCode.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/apicatalog/jsonld/JsonLdErrorCode.java b/src/main/java/com/apicatalog/jsonld/JsonLdErrorCode.java index 7b08518c..1d6ac42b 100644 --- a/src/main/java/com/apicatalog/jsonld/JsonLdErrorCode.java +++ b/src/main/java/com/apicatalog/jsonld/JsonLdErrorCode.java @@ -289,6 +289,9 @@ public enum JsonLdErrorCode { INVALID_ANNOTATION, // Custom + + PROCESSING_TIMEOUT_EXCEEDED, + UNSPECIFIED; private static final Map CODE_TO_MESSAGE; @@ -350,6 +353,7 @@ public enum JsonLdErrorCode { messages.put(INVALID_KEYWORD_EMBED_VALUE, "The value for @embed is not one recognized for the object embed flag"); messages.put(INVALID_EMBEDDED_NODE, "An invalid embedded node has been detected."); messages.put(INVALID_ANNOTATION, "An invalid annotation has been detected"); + messages.put(PROCESSING_TIMEOUT_EXCEEDED, "A processing has exceeded a defined timeount"); CODE_TO_MESSAGE = Collections.unmodifiableMap(messages); } From e3f80f22fb6145c5f104806f66b8cdffab9e4f39 Mon Sep 17 00:00:00 2001 From: Filip Date: Tue, 5 Dec 2023 22:32:51 +0100 Subject: [PATCH 4/7] Improve HTTP loader timeout API --- .../apicatalog/jsonld/http/DefaultHttpClient.java | 3 ++- .../java/com/apicatalog/jsonld/http/HttpClient.java | 6 ++++-- .../apicatalog/jsonld/loader/DefaultHttpLoader.java | 13 +++++++++++-- .../apicatalog/jsonld/http/DefaultHttpClient.java | 3 ++- .../com/apicatalog/jsonld/loader/HttpLoader.java | 2 +- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java b/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java index 0ddf971e..999c9305 100644 --- a/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java +++ b/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java @@ -56,7 +56,7 @@ public static final HttpClient defaultInstance() { } @Override - public void setTimeout(Duration timeout) { + public HttpClient timeout(Duration timeout) { okHttpClient = okHttpClient .newBuilder() .readTimeout(timeount != null @@ -64,6 +64,7 @@ public void setTimeout(Duration timeout) { : 0, TimeUnit.MILLISECONDS) .build(); + return this; } static class HttpResponseImpl implements HttpResponse { diff --git a/src/main/java/com/apicatalog/jsonld/http/HttpClient.java b/src/main/java/com/apicatalog/jsonld/http/HttpClient.java index e79ae00a..fa1788d6 100644 --- a/src/main/java/com/apicatalog/jsonld/http/HttpClient.java +++ b/src/main/java/com/apicatalog/jsonld/http/HttpClient.java @@ -10,13 +10,15 @@ public interface HttpClient { HttpResponse send(URI targetUri, String requestProfile) throws JsonLdError; /** - * Set read timeout + * Configure read timeout * * @param timeout to set or null for no timeout * + * @return {@link HttpClient} instance, + * * @since 1.4.0 */ - default void setTimeout(Duration timeout) { + default HttpClient timeout(Duration timeout) { throw new UnsupportedOperationException(); } diff --git a/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java b/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java index 84ae224b..649d464e 100644 --- a/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java +++ b/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java @@ -206,22 +206,31 @@ private final Document resolve( } /** + * @deprecated use {@code DefaultHttpLoader#fallbackContentType(MediaType)} + * * Set fallback content-type used when received content-type is not supported. * e.g. setFallbackContentType(MediaType.JSON_LD) * * @param fallbackContentType a content type that overrides unsupported received * content-type */ + @Deprecated public void setFallbackContentType(MediaType fallbackContentType) { resolver.setFallbackContentType(fallbackContentType); } + public DefaultHttpLoader fallbackContentType(MediaType fallbackContentType) { + resolver.setFallbackContentType(fallbackContentType); + return this; + } + /** * Set read timeout * * @param timeount to set or null for no timeout */ - public void setTimeount(Duration timeount) { - httpClient.setTimeout(timeount); + public DefaultHttpLoader timeount(Duration timeount) { + httpClient.timeout(timeount); + return this; } } diff --git a/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java b/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java index 92743c67..431d1aa6 100644 --- a/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java +++ b/src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java @@ -72,8 +72,9 @@ public static final HttpClient defaultInstance() { } @Override - public void setTimeout(Duration timeout) { + public HttpClient timeout(Duration timeout) { this.timeout = timeout; + return this; } public static class HttpResponseImpl implements HttpResponse { diff --git a/src/main/java11/com/apicatalog/jsonld/loader/HttpLoader.java b/src/main/java11/com/apicatalog/jsonld/loader/HttpLoader.java index 29a34c1b..881aa00a 100644 --- a/src/main/java11/com/apicatalog/jsonld/loader/HttpLoader.java +++ b/src/main/java11/com/apicatalog/jsonld/loader/HttpLoader.java @@ -36,7 +36,7 @@ public HttpLoader(HttpClient httpClient, int maxRedirections) { super(httpClient, maxRedirections); } - public static final DocumentLoader defaultInstance() { + public static final DefaultHttpLoader defaultInstance() { return INSTANCE; } } From a54431127f45225991fc6f0b8394187fa5ff9d1b Mon Sep 17 00:00:00 2001 From: Filip Date: Tue, 5 Dec 2023 22:34:29 +0100 Subject: [PATCH 5/7] Improve JavaDoc --- .../jsonld/loader/DefaultHttpLoader.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java b/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java index 649d464e..0d3a46fa 100644 --- a/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java +++ b/src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java @@ -207,9 +207,10 @@ private final Document resolve( /** * @deprecated use {@code DefaultHttpLoader#fallbackContentType(MediaType)} - * - * Set fallback content-type used when received content-type is not supported. - * e.g. setFallbackContentType(MediaType.JSON_LD) + * + * Set fallback content-type used when received content-type is not + * supported. e.g. + * setFallbackContentType(MediaType.JSON_LD) * * @param fallbackContentType a content type that overrides unsupported received * content-type @@ -219,6 +220,15 @@ public void setFallbackContentType(MediaType fallbackContentType) { resolver.setFallbackContentType(fallbackContentType); } + /** + * Set fallback content-type used when received content-type is not supported. + * e.g. setFallbackContentType(MediaType.JSON_LD) + * + * @param fallbackContentType a content type that overrides unsupported received + * content-type + * @return {@link DefaultHttpLoader} instance + * @since 1.4.0 + */ public DefaultHttpLoader fallbackContentType(MediaType fallbackContentType) { resolver.setFallbackContentType(fallbackContentType); return this; @@ -228,6 +238,8 @@ public DefaultHttpLoader fallbackContentType(MediaType fallbackContentType) { * Set read timeout * * @param timeount to set or null for no timeout + * @return {@link DefaultHttpLoader} instance + * @since 1.4.0 */ public DefaultHttpLoader timeount(Duration timeount) { httpClient.timeout(timeount); From dbb594e3fda6b887b80a95c13f2d5e7df5cd7abf Mon Sep 17 00:00:00 2001 From: Filip Date: Tue, 5 Dec 2023 22:42:03 +0100 Subject: [PATCH 6/7] Update README --- README.md | 11 +++++++++++ .../com/apicatalog/jsonld/loader/SchemeRouter.java | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f0a8156..77d21811 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,17 @@ JsonLd.compact(document, contextDocument).get(); ... ``` +#### HTTP Document Loader Timeout +Configure and set a custom HTTP document loader instance. + +```javascript +// since 1.4.0 - set read timeout +static DocumentLoader LOADER = HttpLoader.defaultInstance().timeount(Duration.ofSeconds(30)); +... +JsonLd.expand(...).loader(LOADER).get(); +``` + + ## Contributing All PR's welcome! diff --git a/src/main/java/com/apicatalog/jsonld/loader/SchemeRouter.java b/src/main/java/com/apicatalog/jsonld/loader/SchemeRouter.java index efea9e65..adc072d2 100644 --- a/src/main/java/com/apicatalog/jsonld/loader/SchemeRouter.java +++ b/src/main/java/com/apicatalog/jsonld/loader/SchemeRouter.java @@ -30,7 +30,7 @@ public final class SchemeRouter implements DocumentLoader { .set("http", HttpLoader.defaultInstance()) .set("https", HttpLoader.defaultInstance()) .set("file", new FileLoader()); - + private final Map loaders; public SchemeRouter() { From 1ba294d18669fb5b370c4c2c64f7b3db8d9c3b3d Mon Sep 17 00:00:00 2001 From: Filip Date: Tue, 5 Dec 2023 22:55:45 +0100 Subject: [PATCH 7/7] Fix typo --- .../android/com/apicatalog/jsonld/http/DefaultHttpClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java b/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java index 999c9305..b5ada605 100644 --- a/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java +++ b/src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java @@ -59,7 +59,7 @@ public static final HttpClient defaultInstance() { public HttpClient timeout(Duration timeout) { okHttpClient = okHttpClient .newBuilder() - .readTimeout(timeount != null + .readTimeout(timeout != null ? timeout.toMillis() : 0, TimeUnit.MILLISECONDS)