I have a large map that I want to return to the frontend. Originally I was converting the map to a jackson json node and returning the map back to the user with the return ok() method that play provides.
Original code:
public Result returnResponse() {
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> returnMap = populateMapWithData();
JsonNode response = mapper.valueToTree(returnMap);
return ok(response);
}
Since the map can be really large, I am running into memory issues.
On looking at the play framework documentation, there are two ways to return large data to the frontend. If the size is known I can stream the data back to the user. If the size is not known, I can provide the data in chunks.
Play Framework documentation: https://www.playframework.com/documentation/2.8.x/JavaStream
For streaming:
public Result index() {
java.io.File file = new java.io.File("/tmp/fileToServe.pdf");
java.nio.file.Path path = file.toPath();
Source<ByteString, ?> source = FileIO.fromPath(path);
Optional<Long> contentLength = null;
try {
contentLength = Optional.of(Files.size(path));
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
return new Result(
new ResponseHeader(200, Collections.emptyMap()),
new HttpEntity.Streamed(source, contentLength, Optional.of("text/plain")));
}
For chunking:
public Result index() {
// Prepare a chunked text stream
Source<ByteString, ?> source =
Source.<ByteString>actorRef(256, OverflowStrategy.dropNew())
.mapMaterializedValue(
sourceActor -> {
sourceActor.tell(ByteString.fromString("kiki"), null);
sourceActor.tell(ByteString.fromString("foo"), null);
sourceActor.tell(ByteString.fromString("bar"), null);
sourceActor.tell(new Status.Success(NotUsed.getInstance()), null);
return NotUsed.getInstance();
});
// Serves this stream with 200 OK
return ok().chunked(source);
}
My questions are:
By my opinion you must implement stream code design f.e.:
populateMapWithData()
must return stream of data, one returned element - one json node (or array of nodes, i think werry little nodes can made you response very slow)populateMapWithData()
must get data from database by piece (when stream will try get next part of data), for save you memoryChunked responses
because you don't know real size of content, more details in PlayFramwork Doc same link that in you questionI lets tried make different design sample:
Results.ok().chunked( //chunked result
Source.fromPublisher(s -> // publisher read all data by chunks
mapper.valueToTree(populateEntryWithData(s))));
Object populateEntryWithData(Subscriber<? super T> s) {
//todo implement page reading for data
//todo or you can try use reactive driver for database
}
ATTENTION
In play framework for working with json usually will be better use play.libs.Json
or inject ObjectMapper
or inject ObjectMapperBuilder
.
If you need add more configuration for object mapper you can call mapper.copy()
, else you can corrupt another internal mapping function in service