-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
Describe the bug
When doing streaming file uploads, unless the uploaded file is very very small (rendering the whole point of streaming the upload moot), Quarkus REST Client hangs (at line 5 of private <T> T unwrap(CompletableFuture<T> c), the line return c.get(), in org.jboss.resteasy.reactive.client.impl.InvocationBuilderImpl) when providing an InputStream created from an incoming multipart request in an multipart message to the downstream API.
Expected behavior
Request is streamed to downstream API.
Actual behavior
Request hangs.
How to Reproduce?
Example code that consistently demonstrates the issue:
streamed-uploads.zip
streamed-uploads-resteasy.zip
Example Quarkus REST server to represent our downstream API that will eventually receive the uploaded file.
downstream-api.zip
The above two projects do the same thing, they accept an incoming multipart request, create a FileData containing an InputStream representing the as of yet not fully received HttpServletRequest (via quarkus-undertow and commons-fileupload2-jakarta-servlet6), they then build a UploadRequest containing a piece of form data, and the previously mentioned InputStream. One uses Quarkus REST and REST Client, the other uses Quarkus RESTeasy and RESTeasy Client. The latter works (sort of) as expected, but the former does not.
I have attached some sample data here, one is a 1x1 bmp file that's only 58 bytes, the other is a 5000x5000 jpg at about 98kb.
sample-data.zip
The code can be tested using e.g.
curl http://localhost:8080/upload -F "contentType=image/bmp" -F "fileName=image.bmp" -F "file=@1x1.bmp"
curl http://localhost:8080/upload -F "contentType=image/jpeg" -F "fileName=image.jpg" -F "file=@5000x5000.jpg"Suggested test:
- Start the
downstream-api - Start the
streamed-uploads-resteasyapplication (this is the Quarkus RESTeasy + RESTeasy Client version) - Run both
curlcommands above, they should execute fine. - Kill the
streamed-uploads-resteasyapplication and start thestreamed-uploadsapplication (this is the Quarkus REST + REST Client version) - Run both
curlcommands above, the first will execute as it should, but the second will not. - Comment line 31 and uncomment lines 32-33 in UploadController of the
streamed-uploadsproject. This just reads the same 5000x5000 jpg image from disk and uses that as theInputStreamto thedownstream-api. - Try calling the endpoint again, it should now run fine, even though it's sending the same 98kb jpg image as an
InputStreamto thedownstream-apias it was in the secondcurlcommand above, which hangs.
Output of uname -a or ver
Darwin bullen.local 24.5.0 Darwin Kernel Version 24.5.0: Tue Apr 22 19:54:25 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6020 arm64 arm Darwin
Output of java -version
openjdk version "21.0.5" 2024-10-15 LTS
OpenJDK Runtime Environment Corretto-21.0.5.11.1 (build 21.0.5+11-LTS)
OpenJDK 64-Bit Server VM Corretto-21.0.5.11.1 (build 21.0.5+11-LTS, mixed mode, sharing)
Quarkus version or git rev
3.22.2
Build tool (ie. output of mvnw --version or gradlew --version)
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: /opt/homebrew/Cellar/maven/3.9.9/libexec
Java version: 21.0.5, vendor: Amazon.com Inc., runtime: /Users/rekoil/.sdkman/candidates/java/21.0.5-amzn
Default locale: en_GB, platform encoding: UTF-8
OS name: "mac os x", version: "15.5", arch: "aarch64", family: "mac"
Additional information
If you have python and python-flask installed here's a simpler downstream API that just prints all the received headers and file length to the console:
downstream-api-python.zip
When I did this, I noticed that both versions are sending a Content-Length header (which they shouldn't), whereas when performing the test with the FileInputStream then it is correctly sending a Transfer-Encoding: chunked header (because REST/RESTeasy Client can't know the full Content-Length value until it's too late), so I guess that it is actually resolving the InputStream to bytes before starting the request?
The goal here is that it should not be doing that, but should be setting Transfer-Encoding: chunked and not necessarily resolving the InputStream until potentially long after the downstream API has been called.
This is where my understanding of the inner workings of Quarkus limit me from figuring out how to proceed. Hopefully someone here can help me figure this out!