- Async HTTP requests
- HTTP / HTTPS
- Connection Pooling
- SNI
Requests can be creating using the AsyncHttpRequest class and builder
AsyncHttpRequest request = AsyncHttpRequest.builder() .withMethod(AsyncHttpRequest.HttpMethod.POST) .withURI(URI.create("https://api.somewhere.com")) .withHeader("Authorization", "Basic YWRtaW46YWRtaW4xMjM=") .build(); or using the shorter methods
AsyncHttpRequest request = AsyncHttpRequest.get(URI.create("https://api.somewhere.com")).build(); Listeners can be used to implement logging, set default headers on requests, handle ThreadLocal variables and more.
Some common listeners are in the package uk.co.gcwilliams.async.http.listeners
// logging LoggingListener.INSTANCE // set default headers new DefaultHeadersListener("Authorization", "Basic YWRtaW46YWRtaW4xMjM="); The HTTP client can be created using the builder on the NettyAsyncHttpClient class. Various configuration options are available on the builder.
AsyncHttpClient http = NettyAsyncHttpClient.builder().build(); Once you have built the HTTP client you can prepare request to be sent.
Task<AsyncHttpResponse> response = http.prepare(request); And to actually send the request, you need to fork the task and provide 2 consumers, one for the response and one for any exception that might occur.
response.fork( response -> { // handle response }, exception -> { // handle exception }); The abstraction on which asynchronous computations can be built upon.
Task<String> action = Task.of("Some Value"); Task<String> action = Task.of(new IllegalStateException("Some Value")); Task<String> action = Task.of((resolve, reject) -> { // some operation // THEN resolve.accept("Some Value"); // OR reject.accept(new IllegalStateException("Some Value")); }); Mapping over the value of a task, just like java.util.stream.Stream#map
Task<String> action = Task.of("1"); Task<Integer> mappped = action.map(value -> Integer.parse(value)); Mapping over the value of a task and returning another task, just like java.util.stream.Stream#flatMap
Task<String> action = Task.of("1"); Task<Integer> flatMappped = action.flatMap(value -> Task.of(Integer.parse(value))); Converting a List<Task<...>> to Task<List<...>>
Task<String> givenName = Task.of("Homer"); Task<String> surname = Task.of("Simpson"); List<Task<String>> names = List.of(givenName, surname); Task<List<String>> action = Tasks.traverse(names); // or in parallel Task<List<String>> action = Tasks.traverseP(names); Multiple tasks with different types
Task<String> one = Task.of("Homer"); Task<Integer> two = Task.of(1); Task<String> three = Tasks.apply(one, two, (homer, one) -> Task.of(String.format("%s is number %s", homer, one))); // homer is number 1 Useful in Unit Tests
// arrange Task<String> action = Task.of("Homer"); // act String homer = Tasks.get(action, Duration.ofMinutes(1)); // assert assertThat(homer, equalTo("Homer")); Tasks are simple and work great for the HTTP client, however, you might want to use CompletionStage<T> or CompletableFuture<T> in the rest of your code base. You can use the CompletionStages utility class to convert a task to a CompletionStage<T>.
Task<String> task = Task.of("Homer"); CompletionStage<String> completionStage = CompletionStages.toCompletionStage(task); String homer = completionStage.toCompletableFuture().get(30, TimeUnit.SECONDS)