/http-client-ext

Extensions to JDK's HttpClient

Primary LanguageKotlinApache License 2.0Apache-2.0

HttpClient Extensions

The project provides useful extensions to JDK's HttpClient.

Status

Type Status
Build Build
Artifact Maven Central
Javadoc javadoc
Code coverage codecov
LGTM Total alerts Language grade: Java

Requirements

  • Java 11+
  • Gradle for building the project.

Gradle

implementation 'io.github.nstdio:http-client-ext:2.3.2'

Maven

<dependency>
    <groupId>io.github.nstdio</groupId>
    <artifactId>http-client-ext</artifactId>
    <version>2.3.2</version>
</dependency>

Features

Caching

The ExtendedHttpClient implements client part of RFC7234

There are two types of cache:

// backed by volotile in-memory storage
Cache mem = Cache.newInMemoryCacheBuilder().build()

// and persistent storage
Cache disk = Cache.newDiskCacheBuilder().dir(Path.of("...")).build()

Here is the example of creating client with in memory cache:

HttpClient client = ExtendedHttpClient.newBuilder()
        .cache(Cache.newInMemoryCacheBuilder().build())
        .build();

URI uri = URI.create("https://api.github.com/users/defunkt");
HttpRequest request = HttpRequest.newBuilder(uri).build();

HttpResponse<String> networkResponse = client.send(request, ofString());
HttpResponse<String> cachedResponse = client.send(request, ofString());

and all available configurations for in memory cache:

Cache inMemory = Cache.newInMemoryCacheBuilder()
        .maxItems(4096) // number of responses can be cached
        .size(10 * 1000 * 1000) // maximum size of the entire cache in bytes, -1 for no constraint
        .requestFilter(request -> request.uri().getHost().equals("api.github.com")) // cache only requests that match given predicate
        .responseFilter(response -> response.statusCode() == 200) // cache only responses that match given predicate
        .build();

Above-mentioned configurations also applies to persistent cache with some additions

Path cacheDir = ...
Cache disk = Cache.newDiskCacheBuilder()
        .dir(cacheDir)
        .build();        

If request/response contains sensitive information one might want to store it encrypted:

Path cacheDir = ...
SecretKey secretKey = ...

Cache encrypted = Cache.newDiskCacheBuilder()
        .dir(cacheDir)
        .encrypted()
        .key(secretKey)
        .cipherAlgorithm("AES")
        .build();

will create persistent cache which encrypts data by user provided key.

Decompression

Here is an example of transparent encoding feature

HttpClient client = ExtendedHttpClient.newBuilder()
        .transparentEncoding(true)
        .build();

URI uri = URI.create("https://api.github.com/users/defunkt");
HttpRequest request = HttpRequest.newBuilder(uri).build();

// Client will add `Accept-Encoding: gzip, deflate` header to the request
// then decompress response body transparently of the user        
HttpResponse<String> response = client.send(request, ofString());

there is also dedicated BodyHandler for that, but in this case user should add Accept-Encoding header manually

HttpClient client = HttpClient.newClient();

URI uri = URI.create("https://api.github.com/users/defunkt");
HttpRequest request = HttpRequest.newBuilder(uri)
        .header("Accept-Encoding", "gzip, deflate")
        .build();

HttpResponse<String> response = client.send(request, BodyHandlers.ofDecompressing(ofString()));

Out of the box support for gzip and deflate is provided by JDK itself. For br (brotli) compression please add one of following dependencies to your project:

service loader will pick up correct dependency. If none of these preferred there is always an options to extend via SPI by providing CompressionFactory

JSON

Currently, two libraries are supported

just drop one of them as dependency and voilĂ 

// will create object using Jackson or Gson
User user = client.send(request, BodyHandlers.ofJson(User.class));

// or send JSON
Object user = null;
HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create("https://example.com/users"))
  .POST(BodyPublishers.ofJson(user))
  .build();

ExtendedHttpClient client = ExtendedHttpClient.newHttpClient();
HttpResponse<User> response = client.send(request, BodyHandlers.ofJson(User.class));

And if special configuration required

package com.example;

class JacksonMappingProvider implements JsonMappingProvider {
  @Override
  public JsonMapping get() {
    // configure jackson
    ObjectMapper mapper = ...

    return new JacksonJsonMapping(mapper);
  }
}

then standard SPI registration can be used or custom provider can be registered manually:

JacksonMappingProvider jackson = ...

JsonMappingProvider.addProvider(jackson);