Search code examples
javamultipartform-datatrelloquarkus-rest-client

Sending multipart/form-data with binary file with Quarkus REST client to Trello (API)


I'm trying to update an old Java software and port it to Quarkus with RESTEASY / Reactive / Microprofile, which adds an image to a card at Trello (Trello doc: Create attachment at card. Please note: The documentation has some lacks and cannot be taken as fully "true"). For this I have to send a multipart/form-data with some properties (name, filename, mimeType, setCover and binary file). The big point: Sending the file as BINARY.

My old software worked properly:

        String name = "Cover.jpg";
        File tempImage = new File(name);
        try {
        
            Client client = ClientBuilder.newClient(new ClientConfig())
                    .register(MultiPartFeature.class);

            String mime = "image/jpg";

            byte[] buffer = Files.readAllBytes(Paths.get("/PATH/" + name));

            // Save temporary
            FileOutputStream fos = new FileOutputStream(tempImage);
            fos.write(buffer);
            fos.flush();
            fos.close();

            FileDataBodyPart filePart = new FileDataBodyPart("file", tempImage);

            FormDataMultiPart formDataMultiPart = new FormDataMultiPart();
            final FormDataMultiPart multipart = (FormDataMultiPart) formDataMultiPart
                    .field("mimeType", mime)
                    .field("setCover", "true").field("name", name).bodyPart(filePart);


            // Add Cover
            WebTarget coverService = client.target(URI.create("https://api.trello.com/1/cards/CARD_ID/attachments"));
            String coverResponse = coverService.queryParam("key", "KEY").queryParam("token", "TOKEN").request()
                    .header("Accept", "application/json")
                    .post(Entity.entity(multipart, multipart.getMediaType()), String.class);

Which produces something like that:

1 * Sending client request on thread main
1 > POST https://api.trello.com/1/cards/CARD_ID/attachments?key=KEY&token=TOKEN
1 > Accept: application/json
1 > Content-Type: multipart/form-data
--Boundary_1_2089589253_1678121120116
Content-Type: text/plain
Content-Disposition: form-data; name="mimeType"

image/jpg
--Boundary_1_2089589253_1678121120116
Content-Type: text/plain
Content-Disposition: form-data; name="setCover"

true
--Boundary_1_2089589253_1678121120116
Content-Type: text/plain
Content-Disposition: form-data; name="name"

Cover.jpg
--Boundary_1_2089589253_1678121120116
Content-Type: image/jpeg
Content-Disposition: form-data; filename="Cover.jpg"; modification-date="Mon, 06 Mar 2023 16:45:19 GMT"; size=105034; name="file"

<BINARY! content>
--Boundary_1_2089589253_1678121120116--

I tried many things, including the docs for multipart at quarkus.io. The main point of the doc:

@POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.TEXT_PLAIN)
    String sendMultipartData(@MultipartForm MultipartBody data);

Also I tried nearly everything I found at the internet, but nothing works. The problem is most of the time: The content of the file is not sent as binary, instead as Base64.

So how can I send multipart with a binary content exactly like with the "old" way, but with Quarkus? I'm trying for weeks now and am really frustrated.

Here is the last code I tried:

    @Path(ADD_ATTACHMENT_CARD_URL)
    @POST
    @javax.ws.rs.Consumes(MediaType.MULTIPART_FORM_DATA)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @ClientHeaderParam(name = "Accept", value = MediaType.APPLICATION_JSON)
    @ClientHeaderParam(name = "User-Agent", value = "FawnKeeper")
    public void setCover(@QueryParam("key") String key, @QueryParam("token") String token, @PathParam("id") String id,@MultipartForm MultipartBody file);

Where MultipartBody is something like this (changed it a lot, first try was with InputStream like at Quarkus docs, but resulted in something like "Did not find converter to convert to String..."):

Please note: I tried first with Annotation @FormParam on every member, but did not work ("Converter...").

public class MultipartBody implements Serializable {

    @PartType(MediaType.TEXT_PLAIN)
    public String mimeType = "image/jpg";

    @PartType(MediaType.TEXT_PLAIN)
    public String setCover = "false";

    @PartType(MediaType.TEXT_PLAIN)
    public String name = "Cover.jpg";


    @PartType("image/jpeg")
    public File file;

}

I also tried with vertx client, but had no luck...

Please note: I'm not able to use the old code because of conflicting dependencies.

I'm really happy if someone can point me to the right direction or had done this before with Trello. Thank you for your time!


Solution

  • Got it to work by random...

    For all who has similar problems, here's how I got it to work.

    Create MultiDataFormOutput instance, fill it like this:

        byte[] buffer = getBufferFromFile();
        String filename = "MyFile.jpg";
        MultipartFormDataOutput dataOutput = new MultipartFormDataOutput();
        dataOutput.addFormData("mimeType", mimeType, MediaType.TEXT_PLAIN_TYPE);
        dataOutput.addFormData("setCover", "true", MediaType.TEXT_PLAIN_TYPE);
        dataOutput.addFormData("name", filename, MediaType.TEXT_PLAIN_TYPE);
        dataOutput.addFormData("file", buffer, MediaType.APPLICATION_OCTET_STREAM_TYPE, filename);
    
    

    At the client interface I did it this way:

        // Just for clarification
        import javax.ws.rs.Consumes;
        import javax.ws.rs.Produces;
        import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;
        import org.jboss.resteasy.annotations.providers.multipart.PartType;
        import jakarta.ws.rs.core.MediaType;
        import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
    
        @Path(ADD_ATTACHMENT_CARD_URL)
        @POST
        @Consumes(MediaType.MULTIPART_FORM_DATA)
        @ClientHeaderParam(name = "Accept", value = MediaType.APPLICATION_JSON)
        @ClientHeaderParam(name = "User-Agent", value = "FawnKeeper")
        public void setCover(@QueryParam("key") String key, @QueryParam("token") String token, @PathParam("id") String id, @MultipartForm MultipartFormDataOutput file);
    
    

    I tried this several times before, but did not work. The body contained always just the string representation of "file" - the MultipartFormDataOutput object.

    To get this working with Quarkus rest client reactive, an additional configuration is needed at application.properties/yaml:

    quarkus.index-dependency.resteasy-multipart.group-id=org.jboss.resteasy
    quarkus.index-dependency.resteasy-multipart.artifact-id=resteasy-multipart-provider
    
    

    This is the main difference from classic rest client to reactive rest client for getting multipart to work.

    Hope this helps someone!