Skip to content

Element-scoped request handlers #21164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
mshabarov opened this issue Mar 20, 2025 · 2 comments · May be fixed by #21228
Open

Element-scoped request handlers #21164

mshabarov opened this issue Mar 20, 2025 · 2 comments · May be fixed by #21228
Assignees

Comments

@mshabarov
Copy link
Contributor

mshabarov commented Mar 20, 2025

Describe your motivation

A low-level API for handling any kind of request from an element without any assumptions about what the request does and, whenever the owner element is still attached, the checks for disabled and inert elements pass, Vaadin Flow invokes this handler automatically.

Vaadin Flow uses it as a building block for higher-level API - DownloadHandler and UploadHandler, but also can be used for a completely custom upload or download mechanism (e.g. for handling a multipart file upload in a special way) relying on basic request, response, session and owner element objects passed to a handler.

Abstraction levels like StreamReceiver, StreamResource and StreamVariable are not used in this new API.

Describe the solution you'd like

The idea is to reuse the mechanism from AbstractStreamResource for generating a URL that is set as the client-side value of an element attribute and keep that URL active while that element is attached. Implementation re-uses also how a resource is registered in StreamResourceRegistry and how it being transferred to client.

In contrast to the two existing AbstractStreamResource cases, StreamReceiver for handling uploads and StreamResource for handling downloads, this new mechanism would be designed for any kind of request handling without any assumptions about what the request does.

Usage could look like this:

Downloads

// Dynamic File Download
Anchor downloadLink = new Anchor();
downloadLink.setText("Download File");

// 1. Factory method for shorter handler variant
// 2. Can also be used with arbitrary element, e.g. imageElement.setAttribute("src", elementRequestHandler);
downloadLink.setHref((request, response, session, element) -> {
            if (request.getHeader("foo") == null) {
                response.setStatus(400);
            }

            String content = "Hello, this is a dynamically generated file.";
            byte[] fileBytes = content.getBytes(StandardCharsets.UTF_8);

            response.setStatus(200);
            response.setContentType("text/plain");
            response.setHeader("Content-Disposition", "attachment; filename=\"generated-file.txt\"");
            
            try (OutputStream out = response.getOutputStream()) {
                out.write(fileBytes);
            }
}));

Uploads

// hypothetical <input type="file"> custom element
// that has a method or CTOR that takes ElementRequestHandler
Input upload = new Input();
upload.setType("file");
upload.setHandler((request, response, session, element) -> {
            String method = request.getMethod();
            // Request validation ...
            
            try (InputStream inputStream = request.getInputStream()) {
                String fileName = getFileName(request);
                
                // Save the file to the file system
                Files.copy(inputStream, getDestinationFile(request), StandardCopyOption.REPLACE_EXISTING);

                // Respond with success message
                response.setStatus(200);
                response.getWriter().write("File uploaded successfully: " + fileName);
            } catch (IOException e) {
                response.setStatus(500); // Internal Server Error
                response.getWriter().write("File upload failed: " + e.getMessage());
            }
}));

ElementRequestHandler is a functional interface and could look like:

@FunctionalInterface
public interface ElementRequestHandler {
  void handleRequest(VaadinRequest request, VaadinReponse response, VaadinSession session, Element owner);

  default String getUrlPostfix() {
    return null;
  }

  default boolean allowInert() {
    return false;
  }

  default DisabledUpdateMode getDisabledUpdateMode() {
    return DisabledUpdateMode.ONLY_WHEN_ENABLED;
  }
}
  1. UrlPostfix - The optional URL postfix allows appending an application-controlled string, e.g. the logical name of the target file, to the end of the otherwise random-looking download URL. If defined, requests that would otherwise be routable are still rejected if the postfix is missing or invalid. Postfix changes the last segment in the resource url:
    • with no postfix - http://localhost:8080/VAADIN/dynamic/resource/2/10e46acd-0ec1-4b95-b9ea-9be151139a96/image.png
    • with empty ("") postfix - http://localhost:8080/VAADIN/dynamic/resource/2/10e46acd-0ec1-4b95-b9ea-9be151139a96/
  2. AllowInert - invoke request handler even though the owner element is inert. An example could be an image carousel on the front page which is being updated in a background with the new images, and the opened modal dialog shouldn't prevent this carousel to work / give 404 response for image requests because of inert curtain.
  3. DisabledUpdateMode - controls whether request handler is invoked when the owner element is disabled.
@Legioth
Copy link
Member

Legioth commented Mar 21, 2025

I see ElementRequestHandler as a low-level mechanism that wouldn't need any helpers like ElementRequestHandler.of.

This was referenced Mar 21, 2025
@mshabarov
Copy link
Contributor Author

I see ElementRequestHandler as a low-level mechanism that wouldn't need any helpers like ElementRequestHandler.of

Changed.

@mshabarov mshabarov changed the title [Draft] Element-scoped request handlers Element-scoped request handlers Mar 24, 2025
@mshabarov mshabarov moved this from 🪵Product backlog to 🟢Ready to Go in Vaadin Flow ongoing work (Vaadin 10+) Mar 26, 2025
@tepi tepi self-assigned this Mar 28, 2025
@tepi tepi moved this from 🟢Ready to Go to ⚒️ In progress in Vaadin Flow ongoing work (Vaadin 10+) Mar 28, 2025
@tepi tepi linked a pull request Apr 4, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: ⚒️ In progress
Development

Successfully merging a pull request may close this issue.

3 participants