From 7210f5d0ea4f78869ba7e510c19b0e726590f0b4 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 12 Apr 2024 18:32:09 +0300 Subject: [PATCH 1/2] Add a note about FileUpload and FileDownload These types should not have been added to the common module, they should have gone into the server part --- .../org/jboss/resteasy/reactive/multipart/FileDownload.java | 5 +++++ .../org/jboss/resteasy/reactive/multipart/FileUpload.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileDownload.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileDownload.java index 8a797e2766aee..25f48b6819786 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileDownload.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileDownload.java @@ -1,4 +1,9 @@ package org.jboss.resteasy.reactive.multipart; +/** + * Represent a file that should be pushed to the client. + *

+ * WARNING: This type is currently only supported on the server + */ public interface FileDownload extends FilePart { } diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileUpload.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileUpload.java index 7576168d51aa6..57a2bc9996ffa 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileUpload.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/multipart/FileUpload.java @@ -2,6 +2,11 @@ import java.nio.file.Path; +/** + * Represent a file that has been uploaded. + *

+ * WARNING: This type is currently only supported on the server + */ public interface FileUpload extends FilePart { /** From 35f322fc8b1352e8ff9bc53fe54a0e7643985e02 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 12 Apr 2024 18:29:09 +0300 Subject: [PATCH 2/2] Improve the documentation about sending multipart with the REST Client Co-authored-by: Guillaume Smet --- docs/src/main/asciidoc/rest-client.adoc | 199 ++++++++++++------------ 1 file changed, 99 insertions(+), 100 deletions(-) diff --git a/docs/src/main/asciidoc/rest-client.adoc b/docs/src/main/asciidoc/rest-client.adoc index 549b2fd6fa886..4df2562e291fe 100644 --- a/docs/src/main/asciidoc/rest-client.adoc +++ b/docs/src/main/asciidoc/rest-client.adoc @@ -325,106 +325,6 @@ public interface ExtensionsService { } ---- - -=== Using ClientMultipartForm - -MultipartForm can be built using the Class `ClientMultipartForm` which supports building the form as needed: - -`ClientMultipartForm` can be programmatically created with custom inputs and/or from `MultipartFormDataInput` and/or from custom Quarkus REST Input annotated with `@RestForm` if received. - -[source, java] ----- -public interface MultipartService { - - @POST - @Path("/multipart") - @Consumes(MediaType.MULTIPART_FORM_DATA) - @Produces(MediaType.APPLICATION_JSON) - Map multipart(ClientMultipartForm dataParts); // <1> -} ----- - -<1> input to the method is a custom Generic `ClientMultipartForm` which matches external application api contract. - - -More information about this Class and supported methods can be found on the javadoc of link:https://javadoc.io/doc/io.quarkus.resteasy.reactive/resteasy-reactive-client/latest/org/jboss/resteasy/reactive/client/api/ClientMultipartForm.html[`ClientMultipartForm`]. - - -Build `ClientMultipartForm` from `MultipartFormDataInput` programmatically - -[source, java] ----- -public ClientMultipartForm buildClientMultipartForm(MultipartFormDataInput inputForm) // <1> - throws IOException { - ClientMultipartForm multiPartForm = ClientMultipartForm.create(); // <2> - for (Entry> attribute : inputForm.getValues().entrySet()) { - for (FormValue fv : attribute.getValue()) { - if (fv.isFileItem()) { - final FileItem fi = fv.getFileItem(); - String mediaType = Objects.toString(fv.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE), - MediaType.APPLICATION_OCTET_STREAM); - if (fi.isInMemory()) { - multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(), - Buffer.buffer(IOUtils.toByteArray(fi.getInputStream())), mediaType); // <3> - } else { - multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(), - fi.getFile().toString(), mediaType); // <4> - } - } else { - multiPartForm.attribute(attribute.getKey(), fv.getValue(), fv.getFileName()); // <5> - } - } - } - return multiPartForm; -} ----- - -<1> `MultipartFormDataInput` inputForm supported by Quarkus REST (Server). -<2> Creating a `ClientMultipartForm` object to populate with various dataparts. -<3> Adding InMemory `FileItem` to `ClientMultipartForm` -<4> Adding physical `FileItem` to `ClientMultipartForm` -<5> Adding any attribute directly to `ClientMultipartForm` if not `FileItem`. - -Build `ClientMultipartForm` from custom Attributes annotated with `@RestForm` - -[source, java] ----- -public class MultiPartPayloadFormData { // <1> - - @RestForm("files") - @PartType(MediaType.APPLICATION_OCTET_STREAM) - List files; - - @RestForm("jsonPayload") - @PartType(MediaType.TEXT_PLAIN) - String jsonPayload; -} - -/* - * Generate ClientMultipartForm from custom attributes annotated with @RestForm - */ -public ClientMultipartForm buildClientMultipartForm(MultiPartPayloadFormData inputForm) { // <1> - ClientMultipartForm multiPartForm = ClientMultipartForm.create(); - multiPartForm.attribute("jsonPayload", inputForm.getJsonPayload(), "jsonPayload"); // <2> - inputForm.getFiles().forEach(fu -> { - multiPartForm.binaryFileUpload("file", fu.name(), fu.filePath().toString(), fu.contentType()); // <3> - }); - return multiPartForm; -} ----- - -<1> `MultiPartPayloadFormData` custom Object created to match the API contract for calling service which needs to be converted to `ClientMultipartForm` -<2> Adding attribute `jsonPayload` directly to `ClientMultipartForm` -<3> Adding `FileUpload` objects to `ClientMultipartForm` as binaryFileUpload with contentType. - -[NOTE] -==== -When sending multipart data that uses the same name, problems can arise if the client and server do not use the same multipart encoder mode. -By default, the REST Client uses `RFC1738`, but depending on the situation, clients may need to be configured with `HTML5` or `RFC3986` mode. - -This configuration can be achieved via the `quarkus.rest-client.multipart-post-encoder-mode` property. -==== - === Sending large payloads The REST Client is capable of sending arbitrarily large HTTP bodies without buffering the contents in memory, if one of the following types is used: @@ -1554,6 +1454,105 @@ You can also send JSON multiparts by specifying the `@PartType` annotation: String sendMultipart(@RestForm @PartType(MediaType.APPLICATION_JSON) Person person); ---- +==== Programmatically creating the Multipart form + +In cases where the multipart content needs to be built up programmatically, the REST Client provides `ClientMultipartForm` which can be used in the REST Client like so: + +[source, java] +---- +public interface MultipartService { + + @POST + @Path("/multipart") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + Map multipart(ClientMultipartForm dataParts); +} +---- + + +More information about this class and supported methods can be found on the javadoc of link:https://javadoc.io/doc/io.quarkus.resteasy.reactive/resteasy-reactive-client/latest/org/jboss/resteasy/reactive/client/api/ClientMultipartForm.html[`ClientMultipartForm`]. + +===== Converting a received multipart object into a client request + +A good example of creating `ClientMultipartForm` is one where it is created from the server's `MultipartFormDataInput` (which represents a multipart request received by xref:rest.adoc#multipart[Quarkus REST]) - the purpose being to propagate the request downstream while allowing for arbitrary modifications: + +[source, java] +---- +public ClientMultipartForm buildClientMultipartForm(MultipartFormDataInput inputForm) // <1> + throws IOException { + ClientMultipartForm multiPartForm = ClientMultipartForm.create(); // <2> + for (Entry> attribute : inputForm.getValues().entrySet()) { + for (FormValue fv : attribute.getValue()) { + if (fv.isFileItem()) { + final FileItem fi = fv.getFileItem(); + String mediaType = Objects.toString(fv.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE), + MediaType.APPLICATION_OCTET_STREAM); + if (fi.isInMemory()) { + multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(), + Buffer.buffer(IOUtils.toByteArray(fi.getInputStream())), mediaType); // <3> + } else { + multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(), + fi.getFile().toString(), mediaType); // <4> + } + } else { + multiPartForm.attribute(attribute.getKey(), fv.getValue(), fv.getFileName()); // <5> + } + } + } + return multiPartForm; +} +---- + +<1> `MultipartFormDataInput` is a Quarkus REST (Server) type representing a received multipart request. +<2> A `ClientMultipartForm` is created. +<3> `FileItem` attribute is created for the request attribute that represented an in memory file attribute +<4> `FileItem` attribute is created for the request attribute that represented a file attribute saved on the file system +<5> Non-file attributes added directly to `ClientMultipartForm` if not `FileItem`. + + +In a similar fashion if the received server multipart request is known and looks something like: + +[source, java] +---- +public class Request { // <1> + + @RestForm("files") + @PartType(MediaType.APPLICATION_OCTET_STREAM) + List files; + + @RestForm("jsonPayload") + @PartType(MediaType.TEXT_PLAIN) + String jsonPayload; +} +---- + +the `ClientMultipartForm` can be created easily as follows: + +[source, java] +---- +public ClientMultipartForm buildClientMultipartForm(Request request) { // <1> + ClientMultipartForm multiPartForm = ClientMultipartForm.create(); + multiPartForm.attribute("jsonPayload", request.getJsonPayload(), "jsonPayload"); // <2> + request.getFiles().forEach(fu -> { + multiPartForm.binaryFileUpload("file", fu.name(), fu.filePath().toString(), fu.contentType()); // <3> + }); + return multiPartForm; +} +---- + +<1> `Request` representing the request the server parts accepts +<2> A `jsonPayload` attribute is added directly to `ClientMultipartForm` +<3> A `binaryFileUpload` is created from the request's `FileUpload` (which is a Quarkus REST (Server) type used to represent a binary file upload) + +[NOTE] +==== +When sending multipart data that uses the same name, problems can arise if the client and server do not use the same multipart encoder mode. +By default, the REST Client uses `RFC1738`, but depending on the situation, clients may need to be configured with `HTML5` or `RFC3986` mode. + +This configuration can be achieved via the `quarkus.rest-client.multipart-post-encoder-mode` property. +==== + === Receiving Multipart Messages REST Client also supports receiving multipart messages. As with sending, to parse a multipart response, you need to create a class that describes the response data, e.g.