Http Client Comparison 2021-09

Feature comparison

CERN

Present

HTTPS

Keep-Alive

Connection Pooling

Caching

HTTP2

WebSocket

SSE

Reactive

License

Java HttpURLConnection 11

cmp

DONE

alert m (1)

alert wc

alert wc

alert m (7)

choice-no

choice-no (2)

choice-no

choice-no

GPLv2 Classpath Exception

Java HttpClient 11

cmp

DONE

DONE

DONE

DONE

choice-no

DONE

DONE

choice-no

DONE, Flow

GPLv2 Classpath Exception

OkHttp 3.6.0

used

puzzle partially

DONE

DONE

DONE

DONE

DONE

DONE

DONE

choice-no

Apache v2

OkHttp 4.9.2

cmp (3)

choice-no

DONE

DONE

DONE

DONE

DONE

DONE

DONE

choice-no

Apache v2

Apache httpcomponents-client 4.5.13

used

DONE

DONE

DONE

DONE

DONE

choice-no

choice-no

choice-no (4)

choice-no (4)

Apache v2

Apache httpcomponents-client 5.1

cmp (6)

choice-no

DONE

DONE

DONE

DONE

DONE

choice-no

choice-no (4)

DONE, Reactive Streams

Apache v2

Jetty Client 11.0.6

nu

choice-no

DONE

DONE

DONE

choice-no

DONE

DONE

choice-no

choice-no

Apache v2

Spring reactive Webclient

nu

puzzle partially

DONE

alert ne

alert ne

choice-no no
alert ne alert ne alert ne

DONE, Reactor

Apache v2

\– Reactor Netty 0.9.12

nu

puzzle partially

DONE

DONE

DONE

choice-no

DONE

DONE

DONE

DONE, Reactor

Apache v2

\– Jetty Reactive 3.0.4

nu

choice-no

DONE

DONE

DONE

choice-no

DONE

question

question

DONE, Reactive Streams

EPL 2.0

Legend

  • cmp: compatible
  • used: same version used at CERN, related to CERN merge 2021-08
  • ic: incompatible
  • nu: not used
  • m: manually, supported but special steps or additional implementation required for it to work
  • wc: if used with care
  • ne: not exposed, must be configured using the underlying implementation

Client Notes

Java HttpUrlConnection 11

Java HttpClient 11

OkHttp

From Square, Square is an American financial services and digital payments company based in San Francisco, California. The company was founded in 2009 by Jack Dorsey and Jim McKelvey. Jack Patrick Dorsey, is an American billionaire technology entrepreneur and philanthropist who is the co-founder and CEO of Twitter.
  • Defaults seem mostly ready to use for applications
  • Follows redirects by default, disable if not needed since it might be a security issue (5)

OkHttp 4 compatiblity

  • Version 4 is OkHttp rewritten in Kotlin
  • (3) https://square.github.io/okhttp/upgrading_to_okhttp_4/

    "Binary compatibility is the ability to compile a program against OkHttp 3.x, and then to run it against OkHttp 4.x. We’re using the excellent japicmp library via its Gradle plugin to enforce binary compatibility.

    Java source compatibility is the ability to upgrade Java uses of OkHttp 3.x to 4.x without changing .java files."
  • Javadoc does not seem to be available for 4.?.? builds, only a more kotlin centered documentation format. https://github.com/square/okhttp/issues/6450 (Dec 2020!)
  • Other libs like apache chose to use a new namespace for the new version, OkHttp chose to keep it and ensure binary compatibility. But this also means that you have to trust that.

Apache HTTP

Jetty HttpClient

  • Follows redirects by default, disable if not needed since it might be a security issue (5)

Spring Webflux Client

Reactor Netty

Jetty Reactive

Is just a small wrapper around Jetty HttpClient. Alternatively used as implementation under the hood of spring webflux client.

Reactive

There are two main APIs. Java's java.util.concurrent.Flow and Reactive Streams ( http://www.reactive-streams.org/ ), Project Reactor and RxJava are implementations of the Reactive Streams Interface.

Not looked at

Configuring Timeouts

Java HttpURLConnection 11

// Globally
System.setProperty("sun.net.client.defaultReadTimeout", "10000"); // ms
System.setProperty("sun.net.client.defaultConnectTimeout", "3000"); // ms

URL url = new URL("https://www.gsi.de/start/aktuelles");
// URL url = new URL("http://localhost:4321"); // read timeout: while (true) ; do nc -l -p 4321 ; sleep 0.2 ; done
// URL url = new URL("http://10.255.255.1:4321"); // non routable, connection timeout https://stackoverflow.com/a/904609/405793
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// Locally
connection.setConnectTimeout(2000); // ms
connection.setReadTimeout(2000); // ms

try (InputStream is = connection.getInputStream()) {
    // url.openStream() would be shorter but does not provide content encoding info
    String body = IOUtils.toString(is, connection.getContentEncoding());
    System.out.println(body.substring(0, 512));
}

Java HttpClient 11

HttpClient client = HttpClient.newBuilder() //
        .connectTimeout(Duration.ofSeconds(2)) //
        .build();

HttpRequest request = HttpRequest.newBuilder() //
        .uri(URI.create("https://www.gsi.de/start/aktuelles")) //
        .timeout(Duration.ofSeconds(2)) //
        .GET() //
        .build();

HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println(response.body().substring(0, 512));
  • Timeout defaults unlimited
  • Properties are not used!
  • On client reuse: "Once built, an HttpClient is immutable, and can be used to send multiple requests."

OkHttp 3.6.0 / 4.9.2

OkHttpClient client = new OkHttpClient.Builder() //
        .connectTimeout(2, TimeUnit.SECONDS) //
        .readTimeout(2, TimeUnit.SECONDS) //
        .writeTimeout(2, TimeUnit.SECONDS) //
        // .callTimeout(4, TimeUnit.SECONDS) // Not in 3.6.0
        .build();

Request request = new Request.Builder() //
        .url("https://www.gsi.de/start/aktuelles") //
        .build();

try (Response response = client.newCall(request).execute()) {
    System.out.println(response.body().string().substring(0, 512));
}
  • The default value for connect, read, write is 10 seconds
  • The default value for call (time from start to finish) is 0 (unlimited)
  • "OkHttp performs best when you create a single OkHttpClient instance and reuse it for all of your HTTP calls"
  • Connection pool is unlimited therefore no Connection Request Timeout exists like in apache httpcomponents (see below)

Apache httpcomponents-client 4.5.13

// String url = "https://www.gsi.de/start/aktuelles";
// String url = "http://localhost:4321"; // read timeout: while (true) ; do nc -l -p 4321 ; sleep 0.2 ; done
String url = "http://10.255.255.1:4321"; // non routable, connection timeout https://stackoverflow.com/a/904609/405793

RequestConfig requestConfig = RequestConfig.custom() //
        .setSocketTimeout(2000) //
        .setConnectTimeout(2000) //
        .setConnectionRequestTimeout(2000) //
        .build();

CloseableHttpClient httpClient = HttpClients.custom() //
        .setDefaultRequestConfig(requestConfig) //
        .build();

HttpGet httpGet = new HttpGet(url);
// httpGet.setConfig(requestConfig); // alternative to setting it centrally or to overwrite it
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
    System.out.println(EntityUtils.toString(response.getEntity()).substring(0, 512));
} finally {
    response.close();
    httpClient.close(); // on app/service shutdown
}
  • getConnectionRequestTimeout - "Returns the timeout in milliseconds used when requesting a connection from the connection manager." (Potentially limited per target host on the client side)
  • Timeouts: "Default: -1. A timeout value of zero is interpreted as an infinite timeout. A negative value is interpreted as undefined (system default if applicable)."
    • So the default is quite vague, the following seems reasonable The default read timeout is infinity on all platforms. "The default connect timeout is of the order of a minute, but it varies by platform." https://stackoverflow.com/q/60998421/405793
    • No timeout on my machine (debian linux) after 25 minutes
    • sun.net.client Properties are not used!
    • Should be set to a known value

Apache httpcomponents-client 5.1

String url = "https://www.gsi.de/start/aktuelles";
// String url = "http://localhost:4321"; // read timeout: while (true) ; do nc -l -p 4321 ; sleep 0.2 ; done
// String url = "http://10.255.255.1:4321"; // non routable, connection timeout https://stackoverflow.com/a/904609/405793

RequestConfig requestConfig = RequestConfig.custom() //
        .setResponseTimeout(Timeout.ofSeconds(2)) //
        .setConnectTimeout(Timeout.ofSeconds(2)) //
        .setConnectionRequestTimeout(Timeout.ofSeconds(2)) //
        .build();

CloseableHttpClient httpClient = HttpClients.custom() //
        .setDefaultRequestConfig(requestConfig) //
        .build();

HttpGet httpGet = new HttpGet(url);
// httpGet.setConfig(requestConfig); // alternative to setting it centrally or to overwrite it
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
    System.out.println(EntityUtils.toString(response.getEntity()).substring(0, 512));
} finally {
    response.close();
    httpClient.close(); // on app/service shutdown
}
  • Connect timeout (changed since v4.5) "A timeout value of zero is interpreted as an infinite timeout. Default: 3 minutes"
  • Connection Request (connection pool) timeout (changed since v4.5) "A timeout value of zero is interpreted as an infinite timeout. Default: 3 minutes"
  • Response timeout, formerly socket timeout (changed since v4.5) "Default: null"
    • Response Timeout seems to be the same as socket timeout before endpoint.setSocketTimeout(responseTimeout); in org.apache.hc.client5.http.impl.classic.InternalExecRuntime.execute(String, ClassicHttpRequest, HttpClientContext)

Apache httpcomponents-client 5.1 fluent

String url = "https://www.gsi.de/start/aktuelles";
// String url = "http://localhost:4321"; // read timeout: while (true) ; do nc -l -p 4321 ; sleep 0.2 ; done
// String url = "http://10.255.255.1:4321"; // non routable, connection timeout https://stackoverflow.com/a/904609/405793

String body = Request.get(url) //
        .responseTimeout(Timeout.ofSeconds(2)) //
        .connectTimeout(Timeout.ofSeconds(2)) //
        .execute() // Uses a global connection pool max 100 connections per route, total max of 200 connections, 3s connection request timeout
        // .execute(customizableHttpClient)
        .returnContent() //
        .asString();

System.out.println(body.substring(0, 512));
  • Small wrapper around classic API
  • Default executor (client) can't be customized except by properties (timeouts can not be set using properties)
  • Same comments as for non fluent

Reactor Netty

String url = "https://www.gsi.de/start/aktuelles";
// String url = "http://localhost:4321"; // read timeout: while (true) ; do nc -l -p 4321 ; sleep 0.2 ; done
// String url = "http://10.255.255.1:4321"; // non routable, connection timeout https://stackoverflow.com/a/904609/405793

HttpClient httpClient = HttpClient.create()
        .tcpConfiguration(client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000) //
                .doOnConnected(conn -> conn //
                        .addHandlerLast(new ReadTimeoutHandler(2, TimeUnit.SECONDS)) //
                        .addHandlerLast(new WriteTimeoutHandler(2, TimeUnit.SECONDS))));

System.out.println("Calling: " + url);

Disposable disposable = httpClient //
        .get() //
        .uri(url) //
        .responseContent() //
        .aggregate() //
        .asString() //
        .subscribe(content -> System.out.println(content.substring(0, 512)));

while (!disposable.isDisposed()) {
    System.out.print("."); Thread.sleep(250);
}
System.out.println("\ndone");

Jetty Reactive

String url = "https://www.gsi.de/start/aktuelles";
// String url = "http://localhost:4321"; // read timeout: while (true) ; do nc -l -p 4321 ; sleep 0.2 ; done
// String url = "http://10.255.255.1:4321"; // non routable, connection timeout https://stackoverflow.com/a/904609/405793

HttpClient httpClient = new HttpClient();
httpClient.setConnectTimeout(2000); // ms
httpClient.setIdleTimeout(2000); // ms
httpClient.setAddressResolutionTimeout(2000); // ms (included in connection timeout)
httpClient.start();
Request request = httpClient.newRequest(url);
ReactiveRequest reactiveRequest = ReactiveRequest.newBuilder(request).build(); // reactive wrapper 
Publisher<String> publisher = reactiveRequest.response(ReactiveResponse.Content.asString());

// Subscribe to the Publisher to send the request
publisher.subscribe(new Subscriber<String>() {
    public void onSubscribe(Subscription subscription) {
        subscription.request(1); // This is where the request is actually sent.
    }
    public void onNext(String response) {
        System.out.println(response.substring(0, 512));
    }
    public void onError(Throwable failure) { System.out.println("\nerror: " + failure); System.exit(1); }
    public void onComplete() { System.out.println("\ndone"); System.exit(0); }
});

System.out.println("Calling: " + url);

while (true) {
    System.out.print("."); Thread.sleep(250);
}

Spring RestTemplate

https://www.baeldung.com/rest-template#configure-timeout

Apache HttpComponents 4

String url = "https://www.gsi.de/start/aktuelles";
//String url = "http://localhost:4321"; // read timeout: while (true) ; do nc -l -p 4321 ; sleep 0.2 ; done
//String url = "http://10.255.255.1:4321"; // non routable, connection timeout https://stackoverflow.com/a/904609/405793

// Just like Httpcomponents4 config
RequestConfig config = RequestConfig.custom() //
        .setConnectTimeout(3000) // ms
        .setConnectionRequestTimeout(3000) // ms
        .setSocketTimeout(3000) // ms
        .build();
CloseableHttpClient client = HttpClientBuilder.create() //
        .setDefaultRequestConfig(config) //
        .build();
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);

RestTemplate restTemplate = new RestTemplate(httpRequestFactory);
restTemplate.setMessageConverters(List.of(new StringHttpMessageConverter())); // Default is json, this allows "String" response type
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, null, String.class);

String body = response.getBody();

System.out.println(body.substring(0, 512));

HttpUrlConnection

String url = "https://www.gsi.de/start/aktuelles";
// String url = "http://localhost:4321"; // read timeout: while (true) ; do nc -l -p 4321 ; sleep 0.2 ; done
// String url = "http://10.255.255.1:4321"; // non routable, connection timeout https://stackoverflow.com/a/904609/405793

SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory();
httpRequestFactory.setConnectTimeout(3000); // ms
httpRequestFactory.setReadTimeout(3000); // ms

RestTemplate restTemplate = new RestTemplate(httpRequestFactory);
restTemplate.setMessageConverters(List.of(new StringHttpMessageConverter())); // Default is json, this allows "String" response type
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, null, String.class);

String body = response.getBody();

System.out.println(body.substring(0, 512));

General

  • Defaults are like HttpUrlConnection, since SimpleClientHttpRequestFactory is used by default instead of apache!

Sources

ADP-165

Topic revision: r6 - 21 Oct 2021, BenjaminPeter
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback