8000 feat: create transfer util (#21478) (#21505) · vaadin/flow@349fc57 · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Commit 349fc57

Browse files
vaadin-botcaalador
andauthored
feat: create transfer util (#21478) (#21505)
Create a TransferUtil for handling data transfer for upload and download. For upload handle xhr vs multipart part vs multipart iterator. This makes the UploadHanlder interface easier to handle. Fixes #21486 Co-authored-by: caalador <mikael.grankvist@vaadin.com>
1 parent 2b3eefa commit 349fc57

10 files changed

+249
-191
lines changed

flow-server/src/main/java/com/vaadin/flow/server/streams/AbstractFileUploadHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public void handleUploadRequest(UploadEvent event) throws IOException {
6262
try (InputStream inputStream = event.getInputStream();
6363
FileOutputStream outputStream = new FileOutputStream(
6464
file)) {
65-
TransferProgressListener.transfer(inputStream, outputStream,
65+
TransferUtil.transfer(inputStream, outputStream,
6666
getTransferContext(event), getListeners());
6767
}
6868
} catch (IOException e) {

flow-server/src/main/java/com/vaadin/flow/server/streams/ClassDownloadHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public void handleDownloadRequest(DownloadEvent downloadEvent)
105105
if (!isInline()) {
106106
downloadEvent.setFileName(resourceName);
107107
}
108-
TransferProgressListener.transfer(inputStream, outputStream,
108+
TransferUtil.transfer(inputStream, outputStream,
109109
getTransferContext(downloadEvent), getListeners());
110110
} catch (IOException ioe) {
111111
// Set status before output is closed (see #8740)

flow-server/src/main/java/com/vaadin/flow/server/streams/FileDownloadHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public void handleDownloadRequest(DownloadEvent downloadEvent)
8383
downloadEvent
8484
.setContentType(getContentType(resourceName, response));
8585
downloadEvent.setContentLength(file.length());
86-
TransferProgressListener.transfer(inputStream, outputStream,
86+
TransferUtil.transfer(inputStream, outputStream,
8787
getTransferContext(downloadEvent), getListeners());
8888
} catch (IOException ioe) {
8989
// Set status before output is closed (see #8740)

flow-server/src/main/java/com/vaadin/flow/server/streams/InMemoryUploadHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff li 1E80 ne change
@@ -45,7 +45,7 @@ public void handleUploadRequest(UploadEvent event) throws IOException {
4545
try {
4646
try (InputStream inputStream = event.getInputStream();
4747
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) {
48-
TransferProgressListener.transfer(inputStream, outputStream,
48+
TransferUtil.transfer(inputStream, outputStream,
4949
getTransferContext(event), getListeners());
5050
data = outputStream.toByteArray();
5151
}

flow-server/src/main/java/com/vaadin/flow/server/streams/InputStreamDownloadHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public void handleDownloadRequest(DownloadEvent downloadEvent)
6464

6565
try (OutputStream outputStream = downloadEvent.getOutputStream();
6666
InputStream inputStream = download.getInputStream()) {
67-
TransferProgressListener.transfer(inputStream, outputStream,
67+
TransferUtil.transfer(inputStream, outputStream,
6868
getTransferContext(downloadEvent), getListeners());
6969
} catch (IOException ioe) {
7070
// Set status before output is closed (see #8740)

flow-server/src/main/java/com/vaadin/flow/server/streams/ServletResourceDownloadHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void handleDownloadRequest(DownloadEvent downloadEvent)
8888
if (!isInline()) {
8989
downloadEvent.setFileName(resourceName);
9090
}
91-
TransferProgressListener.transfer(inputStream, outputStream,
91+
TransferUtil.transfer(inputStream, outputStream,
9292
getTransferContext(downloadEvent), getListeners());
9393
} catch (IOException ioe) {
9494
// Set status before output is closed (see #8740)

flow-server/src/main/java/com/vaadin/flow/server/streams/TransferProgressListener.java

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,6 @@ public interface TransferProgressListener extends Serializable {
5050
*/
5151
long DEFAULT_PROGRESS_REPORT_INTERVAL_IN_BYTES = 65536;
5252

53-
/**
54-
* Default buffer size for reading data from the input stream.
55-
* <p>
56-
* Follows the default buffer size of the Java
57-
* {@link InputStream#transferTo(OutputStream)}.
58-
*/
59-
int DEFAULT_BUFFER_SIZE = 16384;
60-
6153
/**
6254
* Called when the data transfer is started.
6355
* <p>
@@ -156,64 +148,4 @@ default void onComplete(TransferContext context, long transferredBytes) {
156148
default long progressReportInterval() {
157149
return DEFAULT_PROGRESS_REPORT_INTERVAL_IN_BYTES;
158150
}
159-
160-
/**
161-
* Transfers data from the given input stream to the output stream while
162-
* notifying the progress to the given listeners.
163-
*
164-
* @param inputStream
165-
* the input stream to read from
166-
* @param outputStream
167-
* the output stream to write to
168-
* @param transferContext
169-
* the transfer request containing metadata about the transfer
170-
* @param listeners
171-
* collection of listeners to notify about progress
172-
* @return the number of bytes transferred
173-
* @throws IOException
174-
* if an I/O error occurs during the transfer
175-
*/
176-
static long transfer(InputStream inputStream, OutputStream outputStream,
177-
TransferContext transferContext,
178-
Collection<TransferProgressListener> listeners) throws IOException {
179-
Objects.requireNonNull(inputStream, "InputStream cannot be null");
180-
Objects.requireNonNull(outputStream, "OutputStream cannot be null");
181-
Objects.requireNonNull(transferContext,
182-
"TransferRequest cannot be null");
183-
Objects.requireNonNull(listeners,
184-
"TransferProgressListener cannot be null");
185-
listeners.forEach(listener -> listener.onStart(transferContext));
186-
long transferred = 0;
187-
Map<TransferProgressListener, Long> lastNotified = new HashMap<>(
188-
listeners.size());
189-
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
190-
int read;
191-
while ((read = inputStream.read(buffer, 0, DEFAULT_BUFFER_SIZE)) >= 0) {
192-
outputStream.write(buffer, 0, read);
193-
if (transferred < Long.MAX_VALUE) {
194-
try {
195-
transferred = Math.addExact(transferred, read);
196-
} catch (ArithmeticException ignore) {
197-
transferred = Long.MAX_VALUE;
198-
}
199-
for (TransferProgressListener listener : listeners) {
200-
Long lastNotifiedLong = lastNotified.getOrDefault(listener,
201-
0L);
202-
long progressReportInterval = listener
203-
.progressReportInterval();
204-
if (progressReportInterval > -1 && transferred
205-
- lastNotifiedLong >= progressReportInterval) {
206-
long finalTransferred = transferred;
207-
listener.onProgress(transferContext, finalTransferred,
208-
transferContext.contentLength());
209-
lastNotified.put(listener, transferred);
210-
}
211-
}
212-
}
213-
}
214-
long finalTransferred = transferred;
215-
listeners.forEach(listener -> listener.onComplete(transferContext,
216-
finalTransferred));
217-
return transferred;
218-
}
219151
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/*
2+
* Copyright 2000-2025 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package com.vaadin.flow.server.streams;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.OutputStream;
22+
import java.io.UncheckedIOException;
23+
import java.nio.charset.StandardCharsets;
24+
import java.util.Collection;
25+
import java.util.Collections;
26+
import java.util.HashMap;
27+
import java.util.Map;
28+
import java.util.Objects;
29+
30+
import jakarta.servlet.ServletException;
31+
import jakarta.servlet.http.HttpServletRequest;
32+
import jakarta.servlet.http.Part;
33+
import org.apache.commons.fileupload2.core.FileItemInput;
34+
import org.apache.commons.fileupload2.core.FileItemInputIterator;
35+
import org.apache.commons.fileupload2.core.FileUploadByteCountLimitException;
36+
import org.apache.commons.fileupload2.core.FileUploadException;
37+
import org.apache.commons.fileupload2.core.FileUploadFileCountLimitException;
38+
import org.apache.commons.fileupload2.core.FileUploadSizeException;
39+
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
40+
import org.slf4j.LoggerFactory;
41+
42+
import com.vaadin.flow.dom.Element;
43+
import com.vaadin.flow.server.VaadinRequest;
44+
import com.vaadin.flow.server.VaadinResponse;
45+
import com.vaadin.flow.server.VaadinSession;
46+
47+
/**
48+
* Utility class with methods for handling transfer of upload and download
49+
* requests.
50+
*/
51+
public final class TransferUtil {
52+
53+
/**
54+
* Default buffer size for reading data from the input stream.
55+
* <p>
56+
* Follows the default buffer size of the Java
57+
* {@link InputStream#transferTo(OutputStream)}.
58+
*/
59+
public static int DEFAULT_BUFFER_SIZE = 16384;
60+
61+
/**
62+
* Transfers data from the given input stream to the output stream while
63+
* notifying the progress to the given listeners.
64+
*
65+
* @param inputStream
66+
* the input stream to read from
67+
* @param outputStream
68+
* the output stream to write to
69+
* @param transferContext
70+
* the transfer request containing metadata about the transfer
71+
* @param listeners
72+
* collection of listeners to notify about progress
73+
* @return the number of bytes transferred
74+
* @throws IOException
75+
* if an I/O error occurs during the transfer
76+
*/
77+
public static long transfer(InputStream inputStream,
78+
OutputStream outputStream, TransferContext transferContext,
79+
Collection<TransferProgressListener> listeners) throws IOException {
80+
Objects.requireNonNull(inputStream, "InputStream cannot be null");
81+
Objects.requireNonNull(outputStream, "OutputStream cannot be null");
82+
Objects.requireNonNull(transferContext,
83+
"TransferRequest cannot be null");
84+
Objects.requireNonNull(listeners,
85+
"TransferProgressListener cannot be null");
86+
listeners.forEach(listener -> listener.onStart(transferContext));
87+
long transferred = 0;
88+
Map<TransferProgressListener, Long> lastNotified = new HashMap<>(
89+
listeners.size());
90+
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
91+
int read;
92+
while ((read = inputStream.read(buffer, 0, DEFAULT_BUFFER_SIZE)) >= 0) {
93+
outputStream.write(buffer, 0, read);
94+
if (transferred < Long.MAX_VALUE) {
95+
try {
96+
transferred = Math.addExact(transferred, read);
97+
} catch (ArithmeticException ignore) {
< 10000 /code>98+
transferred = Long.MAX_VALUE;
99+
}
100+
for (TransferProgressListener listener : listeners) {
101+
Long lastNotifiedLong = lastNotified.getOrDefault(listener,
102+
0L);
103+
long progressReportInterval = listener
104+
.progressReportInterval();
105+
if (progressReportInterval > -1 && transferred
106+
- lastNotifiedLong >= progressReportInterval) {
107+
long finalTransferred = transferred;
108+
listener.onProgress(transferContext, finalTransferred,
109+
transferContext.contentLength());
110+
lastNotified.put(listener, transferred);
111+
}
112+
}
113+
}
114+
}
115+
long finalTransferred = transferred;
116+
listeners.forEach(listener -> listener.onComplete(transferContext,
117+
finalTransferred));
118+
return transferred;
119+
}
120+
121+
/**
122+
* Handle upload request and call
123+
* {@link UploadHandler#handleUploadRequest(UploadEvent)} correctly for xhr
124+
* and multipart uploads.
125+
* <p>
126+
* For internal use only. May be renamed or removed in a future release.
127+
*
128+
* @param handler
129+
* UploadHandler that should be called for this upload
130+
* @param request
131+
* The VaadinRequest for this upload
132+
* @param response
133+
* The VaadinResponse for this upload
134+
* @param session
135+
* Current VaadinSession
136+
* @param owner
137+
* The element that owns the request handler
138+
*/
139+
static void handleUpload(UploadHandler handler, VaadinRequest request,
140+
VaadinResponse response, VaadinSession session, Element owner) {
141+
boolean isMultipartUpload = request instanceof HttpServletRequest
142+
&& JakartaServletFileUpload
143+
.isMultipartContent((HttpServletRequest) request);
144+
try {
145+
String fileName;
146+
if (isMultipartUpload) {
147+
Collection<Part> parts = Collections.EMPTY_LIST;
148+
try {
149+
parts = ((HttpServletRequest) request).getParts();
150+
} catch (IOException ioe) {
151+
throw new UncheckedIOException(ioe);
152+
} catch (ServletException ioe) {
153+
LoggerFactory.getLogger(UploadHandler.class).trace(
154+
"Pretending the request did not contain any parts because of exception",
155+
ioe);
156+
}
157+
if (!parts.isEmpty()) {
158+
for (Part part : parts) {
159+
UploadEvent event = new UploadEvent(request, response,
160+
session, part.getSubmittedFileName(),
161+
part.getSize(), part.getContentType(), owner,
162+
null, part);
163+
handler.handleUploadRequest(event);
164+
}
165+
handler.responseHandled(true, response);
166+
} else {
167+
long contentLength = request.getContentLengthLong();
168+
// Parse the request
169+
FileItemInputIterator iter;
170+
try {
171+
JakartaServletFileUpload upload = new JakartaServletFileUpload();
172+
upload.setSizeMax(handler.getRequestSizeMax());
173+
upload.setFileSizeMax(handler.getFileSizeMax());
174+
upload.setFileCountMax(handler.getFileCountMax());
175+
if (request.getCharacterEncoding() == null) {
176+
// Request body's file upload headers are expected
177+
// to be
178+
// encoded in
179+
// UTF-8 if not explicitly set otherwise in the
180+
// request.
181+
upload.setHeaderCharset(StandardCharsets.UTF_8);
182+
}
183+
iter = upload
184+
.getItemIterator((HttpServletRequest) request);
185+
while (iter.hasNext()) {
186+
FileItemInput item = iter.next();
187+
188+
UploadEvent event = new UploadEvent(request,
189+
response, session, item.getName(),
190+
contentLength, item.getContentType(), owner,
191+
item, null);
192+
handler.handleUploadRequest(event);
193+
}
194+
handler.responseHandled(true, response);
195+
} catch (FileUploadException e) {
196+
String limitInfoStr = "{} limit exceeded. To increase the limit "
197+
+ "extend StreamRequestHandler, override {} method for "
198+
+ "UploadHandler and provide a higher limit.";
199+
if (e instanceof FileUploadByteCountLimitException) {
200+
LoggerFactory.getLogger(UploadHandler.class).warn(
201+
limitInfoStr, "Request size",
202+
"getRequestSizeMax");
203+
} else if (e instanceof FileUploadSizeException) {
204+
LoggerFactory.getLogger(UploadHandler.class).warn(
205+
limitInfoStr, "File size",
206+
"getFileSizeMax");
207+
} else if (e instanceof FileUploadFileCountLimitException) {
208+
LoggerFactory.getLogger(UploadHandler.class).warn(
209+
limitInfoStr, "File count",
210+
"getFileCountMax");
211+
}
212+
LoggerFactory.getLogger(UploadHandler.class)
213+
.warn("File upload failed.", e);
214+
handler.responseHandled(false, response);
215+
} catch (IOException ioe) {
216+
LoggerFactory.getLogger(UploadHandler.class)
217+
.warn("IO Exception during file upload", ioe);
218+
handler.responseHandled(false, response);
219+
}
220+
}
221+
} else {
222+
// These are unknown in filexhr ATM
223+
fileName = "unknown";
224+
String contentType = "unknown";
225+
226+
UploadEvent event = new UploadEvent(request, response, session,
227+
fileName, request.getContentLengthLong(), contentType,
228+
owner, null, null);
229+
230+
handler.handleUploadRequest(event);
231+
handler.responseHandled(true, response);
232+
}
233+
} catch (Exception e) {
234+
LoggerFactory.getLogger(UploadHandler.class)
235+
.error("Exception during upload", e);
236+
handler.responseHandled(false, response);
237+
}
238+
}
239+
}

0 commit comments

Comments
 (0)
0