Serving files and requests
Serve a multi-file frontend and answer fetch, form and htmx requests from Java with the Router, with no socket.
loadResources shows a multi-file page and lets Java answer its requests: fetch, forms, htmx hx-get and hx-post, and JSON APIs. Files are served inside the web view, with no socket. The origin is https://jolt.localhost on Windows and jolt://app on Linux, so use relative URLs.
A request handler
window.loadResources(AppResources.classpath("ui"), request -> switch (request.path()) {
case "/notes" -> AppResponse.html(renderNotes());
case "/notes/add" -> {
notes.add(request.formValue("text"));
yield AppResponse.html(renderNotes());
}
default -> null; // null: let the files answer
});The Router
For more than a few routes use Router, which is a RequestHandler:
Router router = Router.create()
.get("/notes/{id}", request -> AppResponse.html(pages.note(request.param("id"))))
.post("/notes", request -> { notes.add(request.formValue("text")); return AppResponse.redirect("/notes"); })
.group("/admin", admin -> admin
.before(request -> loggedIn(request) ? null : AppResponse.redirect("/login"))
.get("/users", request -> AppResponse.html(pages.users())))
.onError((request, error) -> AppResponse.html(pages.error(error)).withStatus(500));
window.loadResources(AppResources.classpath("ui"), router);- Patterns:
{id}captures one segment,{*rest}the rest. A plain segment beats{id}, which beats{*rest}. - A request no route matches falls through to your files. A wrong method on a known path gets 405 with
Allow. - Handlers run on worker threads. An exception becomes a bare 500 and the details go to the log, never to the page.
- Escape anything user-supplied before putting it in HTML, and keep
GEThandlers free of side effects.
Uploads
multipart/form-data works with plain forms, fetch and htmx. Read it with request.parts() or request.part(name):
AppRequest.Part file = request.part("document");
if (file != null && !file.filename().isEmpty()) {
Files.write(uploads.resolve(UUID.randomUUID() + ".bin"), file.content()); // choose the stored name yourself
}Treat uploaded files as untrusted: filename() and contentType() come from the user's machine. Bodies are held in memory, 8 MB by default (413 above that). Raise the limit with ResourceOptions.defaults().withMaxRequestBytes(...), up to 1 GB.
Loopback fallback
The in-process transport needs WebKitGTK 2.40 or newer on Linux. Older systems fall back automatically to a guarded server on 127.0.0.1 with a per-run CSRF token and Origin, Host and Sec-Fetch-Site checks. You can also ask for it, for example to open the app in a normal browser: window.loadResources(resources, handler, 48123).