Skip to content

Commit bbc9b6a

Browse files
authored
Merge pull request #18 from avaje/feature/async
Add support for async method calls
2 parents ca89f8e + 3333bb2 commit bbc9b6a

File tree

9 files changed

+560
-26
lines changed

9 files changed

+560
-26
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.avaje.http.client;
2+
3+
import java.net.http.HttpResponse;
4+
import java.util.List;
5+
import java.util.concurrent.CompletableFuture;
6+
7+
class DHttpAsync implements HttpAsyncResponse {
8+
9+
private final DHttpClientRequest request;
10+
11+
DHttpAsync(DHttpClientRequest request) {
12+
this.request = request;
13+
}
14+
15+
@Override
16+
public <E> CompletableFuture<HttpResponse<E>> withHandler(HttpResponse.BodyHandler<E> handler) {
17+
return request
18+
.performSendAsync(false, handler)
19+
.thenApply(request::afterAsync);
20+
}
21+
22+
@Override
23+
public CompletableFuture<HttpResponse<Void>> asDiscarding() {
24+
return withHandler(HttpResponse.BodyHandlers.discarding());
25+
}
26+
27+
@Override
28+
public CompletableFuture<HttpResponse<String>> asString() {
29+
return request
30+
.performSendAsync(true, HttpResponse.BodyHandlers.ofString())
31+
.thenApply(request::afterAsync);
32+
}
33+
34+
@Override
35+
public <E> CompletableFuture<E> bean(Class<E> type) {
36+
return request
37+
.performSendAsync(true, HttpResponse.BodyHandlers.ofByteArray())
38+
.thenApply(httpResponse -> request.asyncBean(type, httpResponse));
39+
}
40+
41+
@Override
42+
public <E> CompletableFuture<List<E>> list(Class<E> type) {
43+
return request
44+
.performSendAsync(true, HttpResponse.BodyHandlers.ofByteArray())
45+
.thenApply(httpResponse -> request.asyncList(type, httpResponse));
46+
}
47+
}

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

Lines changed: 10 additions & 1 deletion
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;
@@ -98,7 +100,7 @@ public void checkResponse(HttpResponse<?> response) {
98100
}
99101
}
100102

101-
void check(HttpResponse<byte[]> response) {
103+
void checkMaybeThrow(HttpResponse<byte[]> response) {
102104
if (response.statusCode() >= 300) {
103105
throw new HttpException(this, response);
104106
}
@@ -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: 53 additions & 16 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());
@@ -320,7 +327,7 @@ public HttpClientResponse TRACE() {
320327
private void readResponseContent() {
321328
final HttpResponse<byte[]> response = asByteArray();
322329
this.httpResponse = response;
323-
context.check(response);
330+
context.checkMaybeThrow(response);
324331
encodedResponseBody = context.readContent(response);
325332
}
326333

@@ -350,7 +357,7 @@ public <T> List<T> list(Class<T> cls) {
350357

351358
@Override
352359
public <T> Stream<T> stream(Class<T> cls) {
353-
final HttpResponse<Stream<String>> res = withResponseHandler(HttpResponse.BodyHandlers.ofLines());
360+
final HttpResponse<Stream<String>> res = withHandler(HttpResponse.BodyHandlers.ofLines());
354361
this.httpResponse = res;
355362
if (res.statusCode() >= 300) {
356363
throw new HttpException(res, context);
@@ -360,7 +367,7 @@ public <T> Stream<T> stream(Class<T> cls) {
360367
}
361368

362369
@Override
363-
public <T> HttpResponse<T> withResponseHandler(HttpResponse.BodyHandler<T> responseHandler) {
370+
public <T> HttpResponse<T> withHandler(HttpResponse.BodyHandler<T> responseHandler) {
364371
context.beforeRequest(this);
365372
addHeaders();
366373
HttpResponse<T> response = performSend(responseHandler);
@@ -378,35 +385,68 @@ 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+
protected <E> E asyncBean(Class<E> type, HttpResponse<byte[]> response) {
397+
afterAsyncEncoded(response);
398+
return context.readBean(type, encodedResponseBody);
399+
}
400+
401+
protected <E> List<E> asyncList(Class<E> type, HttpResponse<byte[]> response) {
402+
afterAsyncEncoded(response);
403+
return context.readList(type, encodedResponseBody);
404+
}
405+
406+
private void afterAsyncEncoded(HttpResponse<byte[]> response) {
407+
requestTimeNanos = System.nanoTime() - startAsyncNanos;
408+
httpResponse = response;
409+
encodedResponseBody = context.readContent(response);
410+
context.afterResponse(this);
411+
context.checkMaybeThrow(response);
412+
}
413+
414+
protected <E> HttpResponse<E> afterAsync(HttpResponse<E> response) {
415+
requestTimeNanos = System.nanoTime() - startAsyncNanos;
416+
httpResponse = response;
417+
context.afterResponse(this);
418+
return response;
419+
}
420+
381421
@Override
382422
public HttpResponse<byte[]> asByteArray() {
383-
return withResponseHandler(HttpResponse.BodyHandlers.ofByteArray());
423+
return withHandler(HttpResponse.BodyHandlers.ofByteArray());
384424
}
385425

386426
@Override
387427
public HttpResponse<String> asString() {
388428
loggableResponseBody = true;
389-
return withResponseHandler(HttpResponse.BodyHandlers.ofString());
429+
return withHandler(HttpResponse.BodyHandlers.ofString());
390430
}
391431

392432
@Override
393433
public HttpResponse<Void> asDiscarding() {
394-
return withResponseHandler(discarding());
434+
return withHandler(discarding());
395435
}
396436

397437
@Override
398438
public HttpResponse<InputStream> asInputStream() {
399-
return withResponseHandler(HttpResponse.BodyHandlers.ofInputStream());
439+
return withHandler(HttpResponse.BodyHandlers.ofInputStream());
400440
}
401441

402442
@Override
403443
public HttpResponse<Path> asFile(Path file) {
404-
return withResponseHandler(HttpResponse.BodyHandlers.ofFile(file));
444+
return withHandler(HttpResponse.BodyHandlers.ofFile(file));
405445
}
406446

407447
@Override
408448
public HttpResponse<Stream<String>> asLines() {
409-
return withResponseHandler(HttpResponse.BodyHandlers.ofLines());
449+
return withHandler(HttpResponse.BodyHandlers.ofLines());
410450
}
411451

412452
private HttpRequest.Builder newReq(String url) {
@@ -511,13 +551,10 @@ public String responseBody() {
511551
return "<suppressed response body>";
512552
}
513553
if (encodedResponseBody != null) {
514-
return new String(encodedResponseBody.content(), StandardCharsets.UTF_8);
554+
return context.maxResponseBody(new String(encodedResponseBody.content(), StandardCharsets.UTF_8));
515555
} 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;
556+
final Object body = httpResponse.body();
557+
return (body == null) ? null : context.maxResponseBody(body.toString());
521558
}
522559
return null;
523560
}
@@ -527,7 +564,7 @@ static class HttpVoidResponse implements HttpResponse<Void> {
527564

528565
private final HttpResponse<?> orig;
529566

530-
@SuppressWarnings({"unchecked", "raw"})
567+
@SuppressWarnings({"raw"})
531568
HttpVoidResponse(HttpResponse<?> orig) {
532569
this.orig = orig;
533570
}

0 commit comments

Comments
 (0)