Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HttpClient Read Timeout #296

Merged
merged 8 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
Loading