-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
Jetty version(s)
- 12.0.27
- 12.1.1
Jetty Environment
- ee10
Java version/vendor (use: java -version)
Not relevant, but reproduced on:
openjdk version "21.0.4" 2024-07-16 LTS
OpenJDK Runtime Environment Zulu21.36+17-CA (build 21.0.4+7-LTS)
OpenJDK 64-Bit Server VM Zulu21.36+17-CA (build 21.0.4+7-LTS, mixed mode, sharing)
OS type/version
Not relevant, but reproduced on
macOS 15.6.1
Description
HttpInput#read can rethrow the same exception if the current chunk is in a failed state. This can happen, for example, when the client hung up, and there was an early EOF.
In itself this seems fine, but when the HttpInput is wrapped to deal with, for example, multipart uploads it is easy to run into a situation where the close on the wrapper needs to read the underlying input stream to skip to the next part of the multipart upload, leading to self-suppression issues with the try-with-resources construct.
Seems related to #12029 and #11736
How to reproduce?
When you have a servlet with this sort of doPost implementation, you can run into the issue by starting an upload to the POST endpoint, and interrupting the upload before it finishes
This example servlet uses apache commons file-upload. This is important because it is this library that wraps the HttpInput of jetty with an implementation fit for multipart processing.
public class UploadServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
// Hello
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h1>Hello World</h1>");
out.println("</body></html>");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
// use this with, for example, curl:
// curl -v -F upload=@large_file.bin http://localhost:8080/upload
try {
if (JakartaServletFileUpload.isMultipartContent(request)) {
JakartaServletFileUpload<?, ?> upload = new JakartaServletFileUpload<>();
for (FileItemInputIterator it = upload.getItemIterator(request); it.hasNext(); ) {
var item = it.next();
if (!item.isFormField()) {
byte[] buffer = new byte[1024];
try (var is = item.getInputStream()) {
long total = 0;
int read;
while ((read = is.read(buffer)) != -1) {
total += read;
}
response.setHeader("X-Content-Length-Echo", Long.toString(total));
System.out.println("total read: " + total);
}
}
}
response.sendRedirect("/upload");
}
} catch (IOException e) {
System.err.println("Regular IO Exception: " + e.getMessage());
throw e;
} catch (RuntimeException e) {
// Because of the try-with-resources usage of the input stream,
// an IllegalArgumentException is thrown regarding self-suppressing
// exceptions.
System.err.println("Unexpected error occurred. " + e.getMessage());
e.printStackTrace();
}
}
}You can find a ready-to-run maven project here: https://github.com/datadobi/jetty-self-suppression-issue
- import the project in an IDE supporting maven
- start the
Mainclass in that project - use cURL to upload a big file : curl -v -F upload=@large_file.bin http://localhost:8080/upload
- interrupt the cURL upload (using CTRL-C, for example) before the upload completes
This will result in the following output on the console of the Main application:
Unexpected error occurred. Self-suppression not permitted
java.lang.IllegalArgumentException: Self-suppression not permitted
at java.base/java.lang.Throwable.addSuppressed(Throwable.java:1096)
at com.datadobi.bugreports.uploaddemo.UploadServlet.doPost(UploadServlet.java:36)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:653)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:723)
at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:751)
at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1622)
at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1555)
at org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:823)
at org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:440)
at org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:470)
at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1071)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:151)
at org.eclipse.jetty.server.Server.handle(Server.java:182)
at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:677)
at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:416)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:981)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1211)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1166)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: org.eclipse.jetty.server.internal.HttpConnection$HttpEofException: Early EOF
at org.eclipse.jetty.server.internal.HttpConnection$RequestHandler.earlyEOF(HttpConnection.java:1080)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:1753)
at org.eclipse.jetty.server.internal.HttpConnection.parseAndFillForContent(HttpConnection.java:539)
at org.eclipse.jetty.server.internal.HttpConnection$HttpStreamOverHTTP1.read(HttpConnection.java:1371)
at org.eclipse.jetty.server.HttpStream$Wrapper.read(HttpStream.java:167)
at org.eclipse.jetty.server.internal.HttpChannelState$ChannelRequest.read(HttpChannelState.java:948)
at org.eclipse.jetty.server.Request$Wrapper.read(Request.java:919)
at org.eclipse.jetty.ee10.servlet.AsyncContentProducer.readChunk(AsyncContentProducer.java:324)
at org.eclipse.jetty.ee10.servlet.AsyncContentProducer.produceChunk(AsyncContentProducer.java:304)
at org.eclipse.jetty.ee10.servlet.AsyncContentProducer.nextChunk(AsyncContentProducer.java:208)
at org.eclipse.jetty.ee10.servlet.BlockingContentProducer.nextChunk(BlockingContentProducer.java:100)
at org.eclipse.jetty.ee10.servlet.HttpInput.read(HttpInput.java:259)
at org.eclipse.jetty.ee10.servlet.HttpInput.read(HttpInput.java:240)
at org.apache.commons.fileupload2.core.MultipartInput$ItemInputStream.makeAvailable(MultipartInput.java:356)
at org.apache.commons.fileupload2.core.MultipartInput$ItemInputStream.read(MultipartInput.java:415)
at java.base/java.io.InputStream.read(InputStream.java:220)
at com.datadobi.bugreports.uploaddemo.UploadServlet.doPost(UploadServlet.java:39)
... 20 more