Stream bytes between the page and Java - fakeivchenko/lwjwae GitHub Wiki
A binding answers with one value. For bytes, for large answers, and for answers that arrive over
time, a page makes a call with lwjwae.call, which resolves to a Response, as fetch does, and
Java answers it with handle. In this guide, a report streams into the page line by line, the page
sends 64 MB to Java, and a value goes both ways through the codec.

Answer calls in Java
public class Streams {
public static void main(String[] args) {
Application.launch(
WindowParameters.of("Streams", 640, 520),
window -> {
window.handle("report", Streams::report);
window.handle("checksum", Streams::checksum);
window.handle("summary", Streams::summary);
window.loadResource("streams/index.html");
});
}
/** Streams one line after another, until the report is done or the page cancels it. */
private static void report(RpcCall call) throws Exception {
int lines = Integer.parseInt(call.text());
try (RpcStream stream = call.stream("text/plain")) {
for (int line = 1; line <= lines; line++) {
String text = "Line " + line + " of " + lines + "\n";
if (!stream.write(text.getBytes(StandardCharsets.UTF_8))) {
return; // The page cancelled the call.
}
Thread.sleep(2);
}
}
}
/** Answers the bytes that the page sent with their SHA-256. */
private static void checksum(RpcCall call) throws Exception {
byte[] digest = MessageDigest.getInstance("SHA-256").digest(call.body());
call.reply(HexFormat.of().formatHex(digest));
}
/** Takes a task through the codec, and answers a value through it. */
private static void summary(RpcCall call) {
Task task = call.value(Task.class);
call.replyValue(new Task(task.title().toUpperCase(), task.done()));
}
}
An RpcCall has what the page sent, as body(), text(), or value(Class), and one way to
answer:
| Answer | Method |
|---|---|
| Text | reply(String) |
| Bytes of a type | reply(byte[], String contentType) |
| A file among the resources | replyResource(String) |
| A value through the codec | replyValue(Object) |
| A stream | stream(String contentType), then write(byte[]) for each part |
write blocks while the page is behind, and answers false once the page gave up on the call,
which is the signal to stop. A handler that throws answers with an error that rejects the promise
on the page.
Make calls from the page
let controller = null;
// A stream: the answer is a Response, and its body arrives while Java writes it.
document.querySelector("#report").addEventListener("click", async () => {
controller = new AbortController();
const lines = document.querySelector("#lines");
lines.textContent = "";
try {
const response = await lwjwae.call("report", "2000", { signal: controller.signal });
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
for (let chunk = await reader.read(); !chunk.done; chunk = await reader.read()) {
lines.textContent += chunk.value;
}
} catch (error) {
if (error.name !== "AbortError") throw error;
lines.textContent += "Cancelled\n";
}
});
document.querySelector("#cancel").addEventListener("click", () => controller?.abort());
// Bytes up: the body can be an ArrayBuffer, a typed array, a Blob, or a string.
document.querySelector("#upload").addEventListener("click", async () => {
const bytes = new Uint8Array(64 * 1024 * 1024).map((_, index) => index % 251);
const response = await lwjwae.call("checksum", bytes);
document.querySelector("#checksum").textContent = `SHA-256 ${await response.text()}`;
});
// A value both ways through the codec, without a Response.
document.querySelector("#summary").addEventListener("click", async () => {
const task = await lwjwae.invoke("summary", { title: "Write the guide", done: true });
document.querySelector("#value").textContent = JSON.stringify(task);
});
lwjwae.call(name, body, { signal }) resolves to a Response: text(), json(),
arrayBuffer(), blob(), or body to read it while it arrives. Aborting the signal cancels the
call on both sides. lwjwae.invoke(name, value) is the short form for a value through the codec,
which needs a codec as in Pass objects through the bridge.
Good to know
- Only the pages of the application, and the development server, may call. A page of another site that the window shows can't.
- A handler of the
Applicationrather than of aWindowanswers the calls of every window;call.window()tells which one called. - Calls are fast: 64 MB reach Java in a fraction of a second, as the screenshots show, so a page can hand whole files to Java rather than paths.