Skip to content

Commit 34fa253

Browse files
committed
Add async() execution for Void and String
1 parent 84f9159 commit 34fa253

File tree

7 files changed

+131
-7
lines changed

7 files changed

+131
-7
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.avaje.http.client;
2+
3+
import java.net.http.HttpResponse;
4+
import java.util.concurrent.CompletableFuture;
5+
6+
class DHttpAsync implements HttpAsyncResponse {
7+
8+
private final DHttpClientRequest request;
9+
10+
DHttpAsync(DHttpClientRequest request) {
11+
this.request = request;
12+
}
13+
14+
@Override
15+
public CompletableFuture<HttpResponse<Void>> asDiscarding() {
16+
return request
17+
.performSendAsync(false, HttpResponse.BodyHandlers.discarding())
18+
.thenApply(request::afterAsync);
19+
}
20+
21+
@Override
22+
public CompletableFuture<HttpResponse<String>> asString() {
23+
return request
24+
.performSendAsync(true, HttpResponse.BodyHandlers.ofString())
25+
.thenApply(request::afterAsync);
26+
}
27+
28+
}

client/src/main/java/io/avaje/http/client/DHttpClientContext.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.time.Duration;
1010
import java.util.List;
1111
import java.util.Map;
12+
import java.util.concurrent.CompletableFuture;
1213
import java.util.concurrent.atomic.AtomicReference;
1314

1415
class DHttpClientContext implements HttpClientContext {
@@ -29,6 +30,7 @@ class DHttpClientContext implements HttpClientContext {
2930
private final boolean withAuthToken;
3031
private final AuthTokenProvider authTokenProvider;
3132
private final AtomicReference<AuthToken> tokenRef = new AtomicReference<>();
33+
private int loggingMaxBody = 1_000;
3234

3335
DHttpClientContext(HttpClient httpClient, String baseUrl, Duration requestTimeout, BodyAdapter bodyAdapter, RetryHandler retryHandler, RequestListener requestListener, AuthTokenProvider authTokenProvider, RequestIntercept intercept) {
3436
this.httpClient = httpClient;
@@ -155,6 +157,10 @@ <T> HttpResponse<T> send(HttpRequest.Builder requestBuilder, HttpResponse.BodyHa
155157
}
156158
}
157159

160+
<T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest.Builder requestBuilder, HttpResponse.BodyHandler<T> bodyHandler) {
161+
return httpClient.sendAsync(requestBuilder.build(), bodyHandler);
162+
}
163+
158164
BodyContent write(Object bean, String contentType) {
159165
return bodyAdapter.beanWriter(bean.getClass()).write(bean, contentType);
160166
}
@@ -198,4 +204,7 @@ private String authToken() {
198204
return authToken.token();
199205
}
200206

207+
String maxResponseBody(String body) {
208+
return body.length() > loggingMaxBody ? body.substring(0, loggingMaxBody) + " <truncated> ..." : body;
209+
}
201210
}

client/src/main/java/io/avaje/http/client/DHttpClientRequest.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.nio.file.Path;
1414
import java.time.*;
1515
import java.util.*;
16+
import java.util.concurrent.CompletableFuture;
1617
import java.util.function.Supplier;
1718
import java.util.stream.Stream;
1819

@@ -49,6 +50,7 @@ class DHttpClientRequest implements HttpClientRequest, HttpClientResponse {
4950
private boolean loggableResponseBody;
5051
private boolean skipAuthToken;
5152
private boolean suppressLogging;
53+
private long startAsyncNanos;
5254

5355
DHttpClientRequest(DHttpClientContext context, Duration requestTimeout) {
5456
this.context = context;
@@ -276,6 +278,11 @@ private void addHeaders() {
276278
}
277279
}
278280

281+
@Override
282+
public HttpAsyncResponse async() {
283+
return new DHttpAsync(this);
284+
}
285+
279286
@Override
280287
public HttpClientResponse HEAD() {
281288
httpRequest = newHead(url.build());
@@ -378,6 +385,21 @@ protected <T> HttpResponse<T> performSend(HttpResponse.BodyHandler<T> responseHa
378385
}
379386
}
380387

388+
protected <T> CompletableFuture<HttpResponse<T>> performSendAsync(boolean loggable, HttpResponse.BodyHandler<T> responseHandler) {
389+
loggableResponseBody = loggable;
390+
context.beforeRequest(this);
391+
addHeaders();
392+
startAsyncNanos = System.nanoTime();
393+
return context.sendAsync(httpRequest, responseHandler);
394+
}
395+
396+
public <E> HttpResponse<E> afterAsync(HttpResponse<E> response) {
397+
requestTimeNanos = System.nanoTime() - startAsyncNanos;
398+
httpResponse = response;
399+
context.afterResponse(this);
400+
return response;
401+
}
402+
381403
@Override
382404
public HttpResponse<byte[]> asByteArray() {
383405
return withResponseHandler(HttpResponse.BodyHandlers.ofByteArray());
@@ -511,13 +533,9 @@ public String responseBody() {
511533
return "<suppressed response body>";
512534
}
513535
if (encodedResponseBody != null) {
514-
return new String(encodedResponseBody.content(), StandardCharsets.UTF_8);
536+
return context.maxResponseBody(new String(encodedResponseBody.content(), StandardCharsets.UTF_8));
515537
} else if (httpResponse != null && loggableResponseBody) {
516-
String strBody = httpResponse.body().toString();
517-
if (strBody.length() > 1_000) {
518-
return strBody.substring(0, 1_000) + "...";
519-
}
520-
return strBody;
538+
return context.maxResponseBody(httpResponse.body().toString());
521539
}
522540
return null;
523541
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.avaje.http.client;
2+
3+
import java.net.http.HttpResponse;
4+
import java.util.concurrent.CompletableFuture;
5+
6+
/**
7+
* Async responses as CompletableFuture.
8+
*/
9+
public interface HttpAsyncResponse {
10+
11+
/**
12+
* Process discarding response body as {@literal HttpResponse<Void>}.
13+
*/
14+
CompletableFuture<HttpResponse<Void>> asDiscarding();
15+
16+
/**
17+
* Process as String response body {@literal HttpResponse<String>}.
18+
*/
19+
CompletableFuture<HttpResponse<String>> asString();
20+
21+
}

client/src/main/java/io/avaje/http/client/HttpClientResponse.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
*/
1313
public interface HttpClientResponse {
1414

15+
/**
16+
* Send the request async using CompletableFuture.
17+
*/
18+
HttpAsyncResponse async();
19+
1520
/**
1621
* Returning the response using the given response reader.
1722
*

client/src/test/java/io/avaje/http/client/HelloControllerTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import java.net.http.HttpResponse;
88
import java.util.List;
99
import java.util.Map;
10+
import java.util.concurrent.CompletableFuture;
11+
import java.util.concurrent.ExecutionException;
1012
import java.util.stream.Collectors;
1113
import java.util.stream.Stream;
1214

@@ -44,6 +46,31 @@ void get_helloMessage() {
4446
assertThat(hres.statusCode()).isEqualTo(200);
4547
}
4648

49+
@Test
50+
void async_get_asString() throws ExecutionException, InterruptedException {
51+
52+
final CompletableFuture<HttpResponse<String>> future = clientContext.request()
53+
.path("hello").path("message")
54+
.GET()
55+
.async().asString();
56+
57+
final HttpResponse<String> hres = future.get();
58+
assertThat(hres.body()).contains("hello world");
59+
assertThat(hres.statusCode()).isEqualTo(200);
60+
}
61+
62+
@Test
63+
void async_get_asDiscarding() throws ExecutionException, InterruptedException {
64+
65+
final CompletableFuture<HttpResponse<Void>> future = clientContext.request()
66+
.path("hello").path("message")
67+
.GET()
68+
.async().asDiscarding();
69+
70+
final HttpResponse<Void> hres = future.get();
71+
assertThat(hres.statusCode()).isEqualTo(200);
72+
}
73+
4774
@Test
4875
void get_helloMessage_via_url() {
4976

client/src/test/java/org/example/github/GithubTest.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.fasterxml.jackson.databind.ObjectMapper;
55
import io.avaje.http.client.HttpClientContext;
66
import io.avaje.http.client.JacksonBodyAdapter;
7+
import io.avaje.http.client.RequestLogger;
78
import org.junit.jupiter.api.Disabled;
89
import org.junit.jupiter.api.Test;
910

@@ -18,12 +19,27 @@ public class GithubTest {
1819

1920
@Test
2021
@Disabled
21-
void test() {
22+
void test() throws InterruptedException {
23+
2224
final HttpClientContext clientContext = HttpClientContext.newBuilder()
2325
.withBaseUrl("https://api.github.com")
2426
.withBodyAdapter(bodyAdapter)
27+
.withRequestListener(new RequestLogger())
2528
.build();
2629

30+
clientContext.request()
31+
.path("users").path("rbygrave").path("repos")
32+
.GET()
33+
.async()
34+
.asString()
35+
.thenAccept(res -> {
36+
37+
System.out.println("RES: "+res.statusCode());
38+
System.out.println("BODY: "+res.body());
39+
});
40+
41+
Thread.sleep(1_000);
42+
2743
// will not work under module classpath without registering the HttpApiProvider
2844
final Simple simple = clientContext.create(Simple.class);
2945

0 commit comments

Comments
 (0)