Skip to content

Commit

Permalink
Merge pull request #296 from filip26/feat/processing-limits
Browse files Browse the repository at this point in the history
HttpClient Read Timeout
  • Loading branch information
filip26 authored Dec 5, 2023
2 parents af7315a + 1ba294d commit bee404e
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 34 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
14 changes: 14 additions & 0 deletions src/main/android/com/apicatalog/jsonld/http/DefaultHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -53,6 +55,18 @@ public static final HttpClient defaultInstance() {
return INSTANCE;
}

@Override
public HttpClient timeout(Duration timeout) {
okHttpClient = okHttpClient
.newBuilder()
.readTimeout(timeout != null
? timeout.toMillis()
: 0,
TimeUnit.MILLISECONDS)
.build();
return this;
}

static class HttpResponseImpl implements HttpResponse {

private Response response;
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/apicatalog/jsonld/JsonLdErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,9 @@ public enum JsonLdErrorCode {
INVALID_ANNOTATION,

// Custom

PROCESSING_TIMEOUT_EXCEEDED,

UNSPECIFIED;

private static final Map<JsonLdErrorCode, String> CODE_TO_MESSAGE;
Expand Down Expand Up @@ -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);
}
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/apicatalog/jsonld/http/HttpClient.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
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;

/**
* Configure read timeout
*
* @param timeout to set or <code>null</code> for no timeout
*
* @return {@link HttpClient} instance,
*
* @since 1.4.0
*/
default HttpClient timeout(Duration timeout) {
throw new UnsupportedOperationException();
}

}
74 changes: 50 additions & 24 deletions src/main/java/com/apicatalog/jsonld/loader/DefaultHttpLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> location = response.location();

Expand Down Expand Up @@ -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<Link> 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<Link> 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()) {

Expand All @@ -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<Link> contextUris =
linkValues.stream()
.flatMap(l -> Link.of(l, baseUri).stream())
.filter(l -> l.relations().contains(ProfileConstants.CONTEXT))
.collect(Collectors.toList());
final List<Link> 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);
Expand Down Expand Up @@ -210,13 +205,44 @@ private final Document resolve(
}
}

/**
* @deprecated use {@code DefaultHttpLoader#fallbackContentType(MediaType)}
*
* Set fallback content-type used when received content-type is not
* supported. e.g.
* <code>setFallbackContentType(MediaType.JSON_LD)</code>
*
* @param fallbackContentType a content type that overrides unsupported received
* content-type
*/
@Deprecated
public void setFallbackContentType(MediaType fallbackContentType) {
resolver.setFallbackContentType(fallbackContentType);
}

/**
* Set fallback content-type used when received content-type is not supported.
* e.g. <code>setFallbackContentType(MediaType.JSON_LD)</code>
*
* @param fallbackContentType a content type that overrides unsupported received content-type
* @param fallbackContentType a content type that overrides unsupported received
* content-type
* @return {@link DefaultHttpLoader} instance
* @since 1.4.0
*/
public void setFallbackContentType(MediaType fallbackContentType) {
this.resolver.setFallbackContentType(fallbackContentType);
public DefaultHttpLoader fallbackContentType(MediaType fallbackContentType) {
resolver.setFallbackContentType(fallbackContentType);
return this;
}

/**
* Set read timeout
*
* @param timeount to set or <code>null</code> for no timeout
* @return {@link DefaultHttpLoader} instance
* @since 1.4.0
*/
public DefaultHttpLoader timeount(Duration timeount) {
httpClient.timeout(timeount);
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, DocumentLoader> loaders;

public SchemeRouter() {
Expand Down
28 changes: 20 additions & 8 deletions src/main/java11/com/apicatalog/jsonld/http/DefaultHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -34,22 +35,26 @@ 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.Builder request = HttpRequest.newBuilder()
.GET()
.uri(targetUri)
.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) {

Expand All @@ -66,6 +71,12 @@ public static final HttpClient defaultInstance() {
return INSTANCE;
}

@Override
public HttpClient timeout(Duration timeout) {
this.timeout = timeout;
return this;
}

public static class HttpResponseImpl implements HttpResponse {

private final java.net.http.HttpResponse<InputStream> response;
Expand Down Expand Up @@ -100,6 +111,7 @@ public Optional<String> location() {
}

@Override
public void close() { /* unused */ }
public void close() {
/* unused */ }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

0 comments on commit bee404e

Please sign in to comment.