hilla
hilla copied to clipboard
Programmatic file download support for Hilla endpoints
Add support for returning a Spring Resource result from Hilla endpoints. The files should be downloaded using an appropriate HTTP MIME type.
Example endpoint:
@BrowserCallable
class DownloadEndpoint {
public Resource loadFile(String fileId) {
//...
}
}
TypeScript usage:
const file: File = await DownloadEndpoint.loadFile("myFile.txt");
const url = URL.createObjectUrl(file);
Note the point of this API would be to give TypeScript access to the actual bytes of the file. The bytes start being downloaded when the endpoint method is run.
In many cases, you instead want to create a link that the user can click to start a "regular" download. This requires a different mechanism since that link would trigger a GET request which also means that e.g. access control would have to handled without relying on custom request headers.
My take on this. Suppose that we have an endpoint method like the one in the issue description:
public Resource loadFile(String fileId) {...}
The generator should treat it differently:
function loadFile_1(fileId: string): string {
return `/connect/MyEndpoint/loadFile/${encodeURIComponent(JSON.stringify({ fileId }))}`;
}
(when downloading, it's unlikely to have big payloads). So, the returned link can be assigned to an anchor.
When the request arrives to the server, as it is separated from the others being a GET, we can apply the usual security checks for the method and then call it as usual, returning the downloadable data instead of a JSON.
A better encoding technique should be used to get nicer URLs, like URL-safe base64.
A small additional note is that we should maybe also include a CSRF token in that URL though you could on the other hand also argue that doing so would break caching. That's something we can optimize later by providing an option to disable CSRF checking for individual methods regardless of whether they return regular data or a Resource.
Another thing we might want to add later is an option for controlling TS generation so that you could also make it create the Promise<File> variant for the cases where that makes more sense.