Skip to content

Commit f82a50d

Browse files
committed
#17 - Add stream() ... for streaming beans from x-json-stream response
1 parent 36593fe commit f82a50d

File tree

10 files changed

+94
-1
lines changed

10 files changed

+94
-1
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ public interface BodyReader<T> {
1010
*/
1111
T read(BodyContent content);
1212

13+
/**
14+
* Read the String content returning it as a java type.
15+
*/
16+
T readBody(String content);
1317
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ BodyContent write(Object bean, String contentType) {
159159
return bodyAdapter.beanWriter(bean.getClass()).write(bean, contentType);
160160
}
161161

162+
<T> BodyReader<T> beanReader(Class<T> cls) {
163+
return bodyAdapter.beanReader(cls);
164+
}
165+
162166
<T> T readBean(Class<T> cls, BodyContent content) {
163167
return bodyAdapter.beanReader(cls).read(content);
164168
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,17 @@ public <T> List<T> list(Class<T> cls) {
348348
return context.readList(cls, encodedResponseBody);
349349
}
350350

351+
@Override
352+
public <T> Stream<T> stream(Class<T> cls) {
353+
final HttpResponse<Stream<String>> res = withResponseHandler(HttpResponse.BodyHandlers.ofLines());
354+
this.httpResponse = res;
355+
if (res.statusCode() >= 300) {
356+
throw new HttpException(res, context);
357+
}
358+
final BodyReader<T> bodyReader = context.beanReader(cls);
359+
return res.body().map(bodyReader::readBody);
360+
}
361+
351362
@Override
352363
public <T> HttpResponse<T> withResponseHandler(HttpResponse.BodyHandler<T> responseHandler) {
353364
context.beforeRequest(this);

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ public interface HttpClientResponse {
4242
*/
4343
<T> List<T> list(Class<T> type);
4444

45+
/**
46+
* Return the response as a stream of beans.
47+
* <p>
48+
* Typically the response is expected to be {@literal applciation/x-json-stream}
49+
* newline delimited json payload.
50+
*
51+
* @param type The type of the bean to convert the response content into.
52+
* @param <T> The type that the content is converted to.
53+
* @return The stream of beans from the response
54+
* @throws HttpException when the response has error status codes
55+
*/
56+
<T> Stream<T> stream(Class<T> type);
57+
4558
/**
4659
* Return the response with check for 200 range status code.
4760
* <p>

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ private static class JReader<T> implements BodyReader<T> {
8383
this.reader = reader;
8484
}
8585

86+
@Override
87+
public T readBody(String content) {
88+
try {
89+
return reader.readValue(content);
90+
} catch (IOException e) {
91+
throw new RuntimeException(e);
92+
}
93+
}
94+
8695
@Override
8796
public T read(BodyContent s) {
8897
try {

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

Lines changed: 23 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.stream.Collectors;
11+
import java.util.stream.Stream;
1012

1113
import static org.assertj.core.api.Assertions.assertThat;
1214
import static org.junit.jupiter.api.Assertions.*;
@@ -15,6 +17,22 @@ class HelloControllerTest extends BaseWebTest {
1517

1618
final HttpClientContext clientContext = client();
1719

20+
@Test
21+
void get_stream() {
22+
23+
final Stream<SimpleData> stream = clientContext.request()
24+
.path("hello").path("stream")
25+
.GET()
26+
.stream(SimpleData.class);
27+
28+
final List<SimpleData> data = stream.collect(Collectors.toList());
29+
30+
assertThat(data).hasSize(4);
31+
final SimpleData first = data.get(0);
32+
assertThat(first.id).isEqualTo(1);
33+
assertThat(first.name).isEqualTo("one");
34+
}
35+
1836
@Test
1937
void get_helloMessage() {
2038

@@ -233,4 +251,9 @@ void get_withMatrixParam() {
233251
assertEquals(200, httpRes.statusCode());
234252
assertEquals("yr:2011 au:rob co:nz other:foo extra:banana", httpRes.body());
235253
}
254+
255+
public static class SimpleData {
256+
public long id;
257+
public String name;
258+
}
236259
}

client/src/test/java/org/example/webserver/HelloController$Route.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public void registerRoutes() {
3636
ctx.contentType("text/plain").result(controller.retry());
3737
});
3838

39+
ApiBuilder.get("/hello/stream", ctx -> {
40+
ctx.status(200);
41+
controller.stream(ctx);
42+
});
43+
3944
ApiBuilder.get("/hello/:id/:date", ctx -> {
4045
ctx.status(200);
4146
int id = asInt(ctx.pathParam("id"));

client/src/test/java/org/example/webserver/HelloController.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ String retry() {
4747
}
4848
}
4949

50+
@Get("stream")
51+
void stream(Context context) {
52+
// simulate x-json-stream response
53+
context.header("content-type", "application/x-json-stream");
54+
String content =
55+
"{\"id\":1, \"name\":\"one\"}\n" +
56+
"{\"id\":2, \"name\":\"two\"}\n" +
57+
"{\"id\":3, \"name\":\"three\"}\n" +
58+
"{\"id\":4, \"name\":\"four\"}\n";
59+
context.result(content);
60+
}
61+
5062
/**
5163
* Return the Hello DTO.
5264
*

gson-adapter/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<dependency>
2828
<groupId>io.avaje</groupId>
2929
<artifactId>avaje-http-client</artifactId>
30-
<version>1.3</version>
30+
<version>1.6-SNAPSHOT</version>
3131
<scope>provided</scope>
3232
</dependency>
3333

gson-adapter/src/main/java/io/avaje/http/client/gson/GsonBodyAdapter.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ private static class Reader<T> implements BodyReader<T> {
100100
this.adapter = adapter;
101101
}
102102

103+
/**
104+
* Read the content returning it as a java type.
105+
*/
106+
@Override
107+
public T readBody(String content) {
108+
try {
109+
return adapter.fromJson(content);
110+
} catch (IOException e) {
111+
throw new RuntimeException(e);
112+
}
113+
}
114+
103115
@Override
104116
public T read(BodyContent body) {
105117
try {

0 commit comments

Comments
 (0)