Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import org.projectnessie.api.ConfigApi;
import org.projectnessie.api.ContentsApi;
import org.projectnessie.api.TreeApi;
import org.projectnessie.client.http.HttpClientException;
import org.projectnessie.client.http.HttpClientReadTimeoutException;

public interface NessieClient extends AutoCloseable {

Expand Down Expand Up @@ -64,6 +66,8 @@ class Builder {
private String username;
private String password;
private boolean tracing;
private int readTimeoutMillis = Integer.parseInt(System.getProperty("sun.net.client.defaultReadTimeout", "25000"));
private int connectionTimeoutMillis = Integer.parseInt(System.getProperty("sun.net.client.defaultConnectionTimeout", "5000"));

/**
* Same semantics as {@link #fromConfig(Function)}, uses the system properties.
Expand Down Expand Up @@ -167,12 +171,32 @@ public Builder withTracing(boolean tracing) {
return this;
}

/**
* Set the read timeout in milliseconds for this client. Timeout will throw {@link HttpClientReadTimeoutException}.
* @param readTimeoutMillis number of seconds to wait for a response from server.
* @return {@code this}
*/
public Builder withReadTimeout(int readTimeoutMillis) {
this.readTimeoutMillis = readTimeoutMillis;
return this;
}

/**
* Set the connection timeout in milliseconds for this client. Timeout will throw {@link HttpClientException}.
* @param connectionTimeoutMillis number of seconds to wait to connect to the server.
* @return {@code this}
*/
public Builder withConnectionTimeout(int connectionTimeoutMillis) {
this.connectionTimeoutMillis = connectionTimeoutMillis;
return this;
}

/**
* Build a new {@link NessieClient}.
* @return new {@link NessieClient}
*/
public NessieClient build() {
return new NessieHttpClient(authType, uri, username, password, tracing);
return new NessieHttpClient(authType, uri, username, password, tracing, readTimeoutMillis, connectionTimeoutMillis);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,17 @@ class NessieHttpClient implements NessieClient {
/**
* Create new HTTP {@link NessieClient}. All REST api endpoints are mapped here. This client should support
* any jaxrs implementation
*
* @param authType authentication type (AWS, NONE, BASIC)
* @param uri URL for the nessie client (eg http://localhost:19120/api/v1)
* @param username username (only for BASIC auth)
* @param password password (only for BASIC auth)
* @param readTimeout time in milliseconds to wait for a response from the server
* @param connectionTimeoutMillis time in milliseconds to wait to connect to the server
*/
NessieHttpClient(AuthType authType, URI uri, String username, String password, boolean enableTracing) {
HttpClient client = HttpClient.builder().setBaseUri(uri).setObjectMapper(mapper).build();
NessieHttpClient(AuthType authType, URI uri, String username, String password, boolean enableTracing, int readTimeout,
int connectionTimeoutMillis) {
HttpClient client = HttpClient.builder().setBaseUri(uri).setObjectMapper(mapper).setReadTimeoutMillis(readTimeout)
.setConnectionTimeoutMillis(connectionTimeoutMillis).build();
if (enableTracing) {
addTracing(client);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public class HttpClient {
private final String accept;
private final ObjectMapper mapper;
private final SSLContext sslContext;
private final int readTimeoutMillis;
private final int connectionTimeoutMillis;
private final List<RequestFilter> requestFilters = new ArrayList<>();
private final List<ResponseFilter> responseFilters = new ArrayList<>();

Expand All @@ -55,15 +57,19 @@ public enum Method {

/**
* Construct an HTTP client with a universal Accept header.
*
* @param baseUri uri base eg https://example.com
* @param accept Accept header eg "application/json"
* @param readTimeoutMillis timeout to wait for response from server, in milliseconds
* @param connectionTimeoutMillis timeout to wait to connecto to server, in milliseconds
*/
private HttpClient(URI baseUri, String accept, ObjectMapper mapper, SSLContext sslContext) {
private HttpClient(URI baseUri, String accept, ObjectMapper mapper, SSLContext sslContext, int readTimeoutMillis,
int connectionTimeoutMillis) {
this.baseUri = Objects.requireNonNull(baseUri);
this.accept = HttpUtils.checkNonNullTrim(accept);
this.mapper = mapper;
this.sslContext = sslContext;
this.readTimeoutMillis = readTimeoutMillis;
this.connectionTimeoutMillis = connectionTimeoutMillis;
if (!"http".equals(baseUri.getScheme()) && !"https".equals(baseUri.getScheme())) {
throw new IllegalArgumentException(String.format("Cannot start http client. %s must be a valid http or https address", baseUri));
}
Expand All @@ -84,7 +90,8 @@ public void register(ResponseFilter filter) {
}

public HttpRequest newRequest() {
return new HttpRequest(baseUri, accept, mapper, requestFilters, responseFilters, sslContext);
return new HttpRequest(baseUri, accept, mapper, requestFilters, responseFilters, sslContext, readTimeoutMillis,
connectionTimeoutMillis);
}

public static HttpClientBuilder builder() {
Expand All @@ -96,6 +103,9 @@ public static class HttpClientBuilder {
private String accept = "application/json";
private ObjectMapper mapper;
private SSLContext sslContext;
private int readTimeoutMillis = Integer.parseInt(System.getProperty("sun.net.client.defaultReadTimeout", "25000"));
private int connectionTimeoutMillis =
Integer.parseInt(System.getProperty("sun.net.client.defaultConnectionTimeout", "5000"));

private HttpClientBuilder() {
}
Expand Down Expand Up @@ -136,6 +146,24 @@ public HttpClientBuilder setSslContext(SSLContext sslContext) {
return this;
}

public int getReadTimeoutMillis() {
return readTimeoutMillis;
}

public HttpClientBuilder setReadTimeoutMillis(int readTimeoutMillis) {
this.readTimeoutMillis = readTimeoutMillis;
return this;
}

public int getConnectionTimeoutMillis() {
return connectionTimeoutMillis;
}

public HttpClientBuilder setConnectionTimeoutMillis(int connectionTimeoutMillis) {
this.connectionTimeoutMillis = connectionTimeoutMillis;
return this;
}

/**
* Construct an HttpClient from builder settings.
*/
Expand All @@ -150,7 +178,7 @@ public HttpClient build() {
throw new HttpClientException("Cannot construct Http Client. Default SSL config is invalid.", e);
}
}
return new HttpClient(baseUri, accept, mapper, sslContext);
return new HttpClient(baseUri, accept, mapper, sslContext, readTimeoutMillis, connectionTimeoutMillis);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 Dremio
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.projectnessie.client.http;

/**
* Specialization of HttpClientException in the case of a timeout while waiting to read the response.
*/
public class HttpClientReadTimeoutException extends HttpClientException {
public HttpClientReadTimeoutException() {
super();
}

public HttpClientReadTimeoutException(String message) {
super(message);
}

public HttpClientReadTimeoutException(String message, Throwable cause) {
super(message, cause);
}

public HttpClientReadTimeoutException(Throwable cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.AbstractMap.SimpleImmutableEntry;
Expand All @@ -45,15 +46,19 @@ public class HttpRequest {

private final UriBuilder uriBuilder;
private final ObjectMapper mapper;
private final int readTimeoutMillis;
private final int connectionTimeoutMillis;
private final Map<String, Set<String>> headers = new HashMap<>();
private final List<RequestFilter> requestFilters;
private final List<ResponseFilter> responseFilters;
private SSLContext sslContext;

HttpRequest(URI baseUri, String accept, ObjectMapper mapper, List<RequestFilter> requestFilters,
List<ResponseFilter> responseFilters, SSLContext context) {
List<ResponseFilter> responseFilters, SSLContext context, int readTimeoutMillis, int connectionTimeoutMillis) {
this.uriBuilder = new UriBuilder(baseUri);
this.mapper = mapper;
this.readTimeoutMillis = readTimeoutMillis;
this.connectionTimeoutMillis = connectionTimeoutMillis;
putHeader("Accept", accept, headers);
this.requestFilters = requestFilters;
this.responseFilters = responseFilters;
Expand Down Expand Up @@ -86,6 +91,8 @@ private HttpResponse executeRequest(Method method, Object body) throws HttpClien
try {
URI uri = uriBuilder.build();
HttpURLConnection con = (HttpURLConnection) uri.toURL().openConnection();
con.setReadTimeout(readTimeoutMillis);
con.setConnectTimeout(connectionTimeoutMillis);
if (con instanceof HttpsURLConnection) {
((HttpsURLConnection) con).setSSLSocketFactory(sslContext.getSocketFactory());
}
Expand Down Expand Up @@ -137,6 +144,9 @@ private HttpResponse executeRequest(Method method, Object body) throws HttpClien
throw new HttpClientException(String.format("Cannot serialize body of request. Unable to serialize %s", body.getClass()), e);
} catch (MalformedURLException e) {
throw new HttpClientException(String.format("Cannot perform request. Malformed Url for %s", uriBuilder.build()), e);
} catch (SocketTimeoutException e) {
throw new HttpClientReadTimeoutException(
String.format("Cannot finish request. Timeout while waiting for response with a timeout of %ds", readTimeoutMillis / 1000), e);
} catch (IOException e) {
throw new HttpClientException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ public class TestHttpClient {
private static final Instant NOW = Instant.now();

private static HttpRequest get(InetSocketAddress address) {
return HttpClient.builder().setBaseUri(URI.create("http://localhost:" + address.getPort())).setObjectMapper(MAPPER).build().newRequest();
return get(address, 15000, 15000);
}

private static HttpRequest get(InetSocketAddress address, int connectTimeout, int readTimeout) {
return HttpClient.builder().setBaseUri(URI.create("http://localhost:" + address.getPort())).setObjectMapper(MAPPER)
.setConnectionTimeoutMillis(connectTimeout).setReadTimeoutMillis(readTimeout).build().newRequest();
}

@Test
Expand All @@ -65,6 +70,17 @@ void testGet() throws Exception {
}
}

@Test
void testReadTimeout() {
HttpHandler handler = h -> {
};
Assertions.assertThrows(HttpClientReadTimeoutException.class, () -> {
try (TestServer server = new TestServer(handler)) {
get(server.getAddress(), 15000, 1).get().readEntity(ExampleBean.class);
}
});
}

@Test
void testPut() throws Exception {
ExampleBean inputBean = new ExampleBean("x", 1, NOW);
Expand Down