quarkus icon indicating copy to clipboard operation
quarkus copied to clipboard

MultipartForm is receiving null parameters at the server

Open DMaxter opened this issue 3 years ago • 9 comments

Describe the bug

When using resteasy-reactive in Kotlin (I didn't test with Java), when I have an endpoint like this:

import org.jboss.resteasy.reactive.MultipartForm


    @POST
    @Path("/image/{id}")
    @RolesAllowed(MANAGER_ROLE, ADMIN_ROLE)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Operation(summary = "Add images to a brand")
    @APIResponses(
        APIResponse(responseCode = "200", description = "Successful image registration"),
        APIResponse(responseCode = "401", description = "User is not logged in"),
        APIResponse(responseCode = "403", description = "User doesn't have authorization to add images to a brand")
    )
    fun addImage(@PathParam("id") brand: Long, @MultipartForm images: ImageUploadDto): Uni<Response> {
        return identity.deferredIdentity.onItem().transformToUni { id ->
            logger.info("User ${id.principal.name} is adding ${images} image to brand with id $brand")

            return@transformToUni brandService.addImages(brand, images)
        }
    }

With ImageUploadDto being:

import org.eclipse.microprofile.openapi.annotations.enums.SchemaType
import org.eclipse.microprofile.openapi.annotations.media.Schema
import org.jboss.resteasy.reactive.PartType
import org.jboss.resteasy.reactive.RestForm
import java.io.File
import javax.ws.rs.core.MediaType

class ImageUploadDto {
    @RestForm("image")
    @PartType(MediaType.APPLICATION_OCTET_STREAM)
    @Schema(type = SchemaType.STRING, format = "binary")
    var content: String? = null

    @RestForm
    var file: File? = null
}

In swagger, content has a button to allow me to upload a file and there is a file field which is the JSON mapping of the java.io.File class to JSON. I tried with org.jboss.resteasy.reactive.multipart.FileUpload but the behavior was the same as using File (except that FileUpload is an interface so the JSON mapping was empty).

Besides this, when data gets to the server, content is null as well as File (I defined the variables as nullable because I received a NullPointerException otherwise) as it is able to create an ImageUploadDto instance but the properties are null. I followed the example in the documentation (just adapted it to Kotlin) and it doesn't work

Expected behavior

Get the file contents

Actual behavior

Everything is null

How to Reproduce?

Use the example above and try to upload a file

Output of uname -a or ver

Linux zirconium 5.18.16-zen1-1-zen #1 ZEN SMP PREEMPT_DYNAMIC Wed, 03 Aug 2022 11:25:10 +0000 x86_64 GNU/Linux

Output of java -version

openjdk version "18.0.2" 2022-07-19
OpenJDK Runtime Environment (build 18.0.2+9)
OpenJDK 64-Bit Server VM (build 18.0.2+9, mixed mode)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.11.2.Final

Build tool (ie. output of mvnw --version or gradlew --version)

------------------------------------------------------------
Gradle 7.5.1
------------------------------------------------------------

Build time:   2022-08-06 13:53:45 UTC
Revision:     <unknown>

Kotlin:       1.6.21
Groovy:       3.0.10
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          18.0.2 (Oracle Corporation 18.0.2+9)
OS:           Linux 5.18.16-zen1-1-zen amd64

Additional information

No response

DMaxter avatar Aug 10 '22 00:08 DMaxter

/cc @evanchooly, @geoand

quarkus-bot[bot] avatar Aug 10 '22 00:08 quarkus-bot[bot]

Can you also attach what the HTTP request looks like (an easy way to do this is from the browser's developer tools)?

geoand avatar Aug 10 '22 06:08 geoand

The HTTP request looks like this:

POST /brand/image/1 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://localhost:8080/q/swagger-ui/
Content-Type: multipart/form-data; boundary=---------------------------104025827140925644591275308632
Content-Length: 720
Origin: http://localhost:8080
DNT: 1
Connection: keep-alive
Cookie: identity=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4MDgwIiwic3ViIjoiYWRtaW4iLCJyb2xlcyI6WyJBRE1JTiJdLCJpYXQiOjE2NjAxMTg0MjUsImV4cCI6MTY2MDEyOTIyNSwianRpIjoiOTZlMWQxZTgtNjU1NC00MGU2LWEzZDItZmM3OGVjNDAwMWRkIn0.U9yGfjNwlQwlCQp66DFFgefpA-XUua049ZfM14YTkStkb2sBe33mgrtJURH1a_8hGYdlphjSCTqw4DHBpmWDih_3nYQnv5WtwSh2uZN-phdoyiO1Cq4hD_X86cadKBAMneUapHdV5ZYBhMi4DhJJmNtwKHrhZkrMssr9DAxVON2-gSsBFD5ieyj0kDS30MNxJIpJ_vIGebA3HZam9SAdZO5RLV7zwi8NfcH-NTVFsjkgBnA2wNCxwzC8EqOqXE13EC69gi8teJf_ir2I4Qr5JdpwyrmyiDBxRflxiULmaFC8YMbj4Arcwgr6ZjIq8StsgR03PN4Y0kUGh9HHdlB0FeM8YYskMEuEkjYLEmxO69pbQFeDMAi19d4EyX5HxR7tMpSMSmec02q1hy8JN3lxEesL_CrpXmhyU1Cj0LaNyPavSh75-hobrXz5Sm_dt2vLuHE--OKwUJwOQtldFwhTZQmX9ad7GI_bESkdofEggIbV0WXP1reF3zWqFTEWkabcPmvjewKvCxkNLd8x-uKW60DF0lqk_suzeUZm8aFhhzsAadNakipLCqp20z_G_qjJ6CihKACMC1ENJ7IcNz_bPN8rAprKLLPBr0QptoH8wht232D5eRZscF8tflhLIZrFtMO4iTZNYMsJ0e1ChEwwEwJ97uSgxSFbkcwqIy_JcXf1suuW_X96yM6iH0nRLMuv6Sv87K2j9g5-W0vH3BBXddHcCUsn9VSl9rkxyQEyS5uDE1qjaumcTbznn1uHAm5sTSne_39APm2vCPPYdzmBda8mdnLvyWQrZAaUGYUcXaIo-WvMNwMlX1FIaXvYkgAXlbOTQtBqNv0INB0sg8zmjocEc2uM_UwUoagaIfgtXLX7DsokM9hlM3gs0Nma53PxXmaKKYtomj1pJQDvXtjBwjfQa3QnQGJ2j8wRjh6mWNSOiYsdPk5NywrYCSceWyOPNetQ01Uze8oUOH2xv1W1tYR9s9Fz7Aar3XRpjhE1DtyOhB85hn4zAQOdMerZH81r2L1WtsGVK5rS-PU-67yf69lHFyuWg6K4DimiJDQO4h5ckktvRURtd60zyfUQPzmFTkn1ay5Edt9tNPIMBBJ0HiMZQKRvunBXnIk1U2hLLqItJjNy9xz0tHNwABNbsBEG-0qIgjcjmVgSk3NVkuIBCSARjb-9cmTQaqJtjoX5b_gSyV28W1sxsz07dAzKAuFqU4rYY0dKGAglnZk3rRsHPvAqXwsxxNZ_xKKMIveTXh3qM6e0k1kWazkjxvJQGeHH0J9Ug4rYElB2p4EZcd-AQeI-HAYmdw3mfO_ADO3r-4FaVS9JVkeIeJa_yR_Gp1dhHO9dFSYsVGAnZ6LttSFoig
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Pragma: no-cache
Cache-Control: no-cache
-----------------------------104025827140925644591275308632
Content-Disposition: form-data; name="image"; filename="tmp"
Content-Type: application/octet-stream

123abcd

-----------------------------104025827140925644591275308632
Content-Disposition: form-data; name="file"

{
  "path": "string",
  "invalid": true,
  "name": "string",
  "parent": "string",
  "parentFile": "string",
  "absolute": true,
  "absolutePath": "string",
  "absoluteFile": "string",
  "canonicalPath": "string",
  "canonicalFile": "string",
  "directory": true,
  "file": true,
  "hidden": true,
  "totalSpace": 0,
  "freeSpace": 0,
  "usableSpace": 0
}
-----------------------------104025827140925644591275308632--

DMaxter avatar Aug 10 '22 08:08 DMaxter

Content-Disposition: form-data; name="file" contains no filename, so using FileUpload or File will not yield any results.

geoand avatar Aug 10 '22 08:08 geoand

The file parameter is the one on ImageUploadDto, it should not appear in swagger at all. It doesn't have a filename because it is showing as a parameter and it shouldn't

DMaxter avatar Aug 10 '22 10:08 DMaxter

So this sounds more like a SwaggerUI issue - which makes sense since I don't think we have any support for RESTEasy Reactive multipart handling in OpenAPI

geoand avatar Aug 10 '22 10:08 geoand

But sending through curl yields the same error, I tried sending a local file with curl and also got null in both parameters

DMaxter avatar Aug 10 '22 11:08 DMaxter

If you do not specify a value for @RestForm the name of the section is assumed to be the field name, so please make sure you are using the proper values.

geoand avatar Aug 10 '22 12:08 geoand

Yes, but that is generated in Swagger, the problem is that the file contents do not get to the backend

DMaxter avatar Aug 10 '22 13:08 DMaxter

Any news on this issue?

DMaxter avatar Aug 31 '22 17:08 DMaxter

Not at the moment.

We have a large refactoring of the multipart support we want to get in before making any other changes to it.

geoand avatar Aug 31 '22 17:08 geoand

Are there any alternatives I can use to upload images then?

DMaxter avatar Sep 05 '22 21:09 DMaxter

See my comment above.

geoand avatar Sep 06 '22 05:09 geoand

Is there an estimated date for when the refactoring will be done?

DMaxter avatar Nov 01 '22 16:11 DMaxter

The refactoring has been done and merged in main.

@Sgitario would you like to take another look at this issue?

geoand avatar Nov 01 '22 16:11 geoand

I guess you mean https://github.com/quarkusio/quarkus/pull/27526 which is already part of 2.14.0.CR1. @DMaxter can you try the Quarkus version 2.14.0.CR1 and see if it works now?

Sgitario avatar Nov 02 '22 06:11 Sgitario

The example in the updated docs is generating the following on Swagger: image

DMaxter avatar Nov 03 '22 18:11 DMaxter

With this example, OpenAPI is able to generate an upload button and the content of the file can be read inside my application

    @POST
    @Path("/upload")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    fun upload(@PartType(MediaType.APPLICATION_OCTET_STREAM) @Schema(type = SchemaType.STRING, format = "binary") @RestForm description: FileUpload,
               @RestForm("image") file: File?
    ): Uni<Response> {
        println(description.uploadedFile().toFile().readText())
        println("DONE")
        return Uni.createFrom().item(Response.ok().build())
    }

Thank you

DMaxter avatar Nov 03 '22 19:11 DMaxter