Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test handling a multipart request part as a file based on the content-type #961

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ Additional coverage:
- Gzip compression
- REST Client reactive - support for POJO JSON serialization in multipart forms.
- Request matching - selecting from multiple media types
- Handling Multipart Form data

### `http/rest-client`
Verifies Rest Client configuration using `quarkus-rest-client-jaxb` (XML support) and `quarkus-rest-client-jsonb` (JSON support).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.quarkus.ts.http.jaxrs.reactive;

import java.io.File;
import java.util.List;

import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.PartType;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.multipart.FileUpload;

public class MultipartBody {

Expand All @@ -20,4 +22,11 @@ public class MultipartBody {
@RestForm("data")
@PartType(MediaType.APPLICATION_OCTET_STREAM)
public File data;

@RestForm
public File plainTextFile;

@RestForm(FileUpload.ALL)
public List<FileUpload> allFiles;

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.quarkus.ts.http.jaxrs.reactive;

import java.io.IOException;
import java.nio.file.Files;
import java.util.Collection;
import java.util.stream.Collectors;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
Expand All @@ -9,42 +12,58 @@
import javax.ws.rs.core.MediaType;

import org.apache.commons.io.IOUtils;
import org.jboss.resteasy.reactive.MultipartForm;
import org.jboss.resteasy.reactive.multipart.FilePart;

@Path("/multipart")
public class MultipartResource {

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.MULTIPART_FORM_DATA)
public MultipartBody postForm(@MultipartForm MultipartBody multipartBody) {
public MultipartBody postForm(MultipartBody multipartBody) {
return multipartBody;
}

@POST
@Path("/text")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.TEXT_PLAIN)
public String postFormReturnText(@MultipartForm MultipartBody multipartBody) {
public String postFormReturnText(MultipartBody multipartBody) {
return multipartBody.text;
}

@POST
@Path("/image")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public byte[] postFormReturnFile(@MultipartForm MultipartBody multipartBody) throws IOException {
public byte[] postFormReturnFile(MultipartBody multipartBody) throws IOException {
return IOUtils.toByteArray(multipartBody.image.toURI());
}

@POST
@Path("/data")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public byte[] postFormReturnData(@MultipartForm MultipartBody multipartBody) throws IOException {
public byte[] postFormReturnData(MultipartBody multipartBody) throws IOException {
return IOUtils.toByteArray(multipartBody.data.toURI());
}

@POST
@Path("/plain-text-file")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.TEXT_PLAIN)
public String postFormReturnPlainTextFile(MultipartBody multipartBody) throws IOException {
return Files.readString(multipartBody.plainTextFile.toPath());
}

@POST
@Path("/all-file-control-names")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public Collection<String> postFormReturnAllFileControlNames(MultipartBody multipartBody) {
return multipartBody.allFiles.stream().map(FilePart::name).collect(Collectors.toList());
}

@POST
@Path("/echo")
@Consumes(MediaType.MULTIPART_FORM_DATA)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.MultipartForm;

@Path("/multipart/echo")
@RegisterRestClient
Expand All @@ -16,5 +15,5 @@ public interface MultipartService {
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.TEXT_PLAIN)
String sendMultipartData(@MultipartForm ClientMultipartBody data);
String sendMultipartData(ClientMultipartBody data);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ io.quarkus.ts.http.jaxrs.reactive.client.MultipartService/mp-rest/url=http://loc
quarkus.http.enable-compression=true
//TODO https://github.com/quarkusio/quarkus/issues/29642
quarkus.openshift.env.vars.quarkus-opts=-Xmx2024M -Xms80M -Xmn120M
quarkus.http.body.multipart.file-content-types=text/xml,custom/content-type
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
import static javax.ws.rs.core.MediaType.WILDCARD;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import io.quarkus.test.scenarios.QuarkusScenario;
import io.restassured.RestAssured;

@Tag("QUARKUS-2743")
@QuarkusScenario
public class MediaTypeSelectionIT {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class RESTEasyReactiveMultipartIT {
private static final String IMAGE_FILE_NAME = "/quarkus.png";
private static final String TEXT_WITH_DIACRITICS = "Přikrášlený žloťoučký kůň úpěl ďábelské ódy.";
private static byte[] randomBytes = new byte[120];
private static final String DATA = "data";
private static final String IMAGE = "image";
private static File imageFile;
private static byte[] imageBytes;

Expand Down Expand Up @@ -80,30 +82,116 @@ public void testDataPartFromMultipart() {
assertThat(receivedBytes, equalTo(randomBytes));
}

private ValidatableResponse whenSendMultipartData(String path) {
@Tag("QUARKUS-2744")
@Test
public void testPlainTextFilePartFromMultipart() {
// verifies that every multipart form data field regardless of media type can be used as file
// as long as Java data type of DTO field is java.io.File
String text = "Old Time Rock & Roll";
MultiPartSpecification textSpec = new MultiPartSpecBuilder(text)
.controlName("plainTextFile")
.fileName("plainTextFile")
.header("Content-Type", MediaType.TEXT_PLAIN)
.build();
String receivedString = RestAssured.given()
.contentType(MediaType.MULTIPART_FORM_DATA)
.multiPart(textSpec)
.post("/multipart/plain-text-file")
.then()
.statusCode(200)
.extract()
.asString();
assertThat(receivedString, equalTo(text));
}

@Tag("QUARKUS-2744")
@Test
public void testAllFilesPartFromMultipart() {
// test all file uploads from a multipart form are accessible
String otherImage = "otherImage";
String xmlFileName = "xmlFile";
String customContentTypeFileName = "customContentTypeFile";
String[] controlNames = RestAssured.given()
.contentType(MediaType.MULTIPART_FORM_DATA)
.multiPart(createXmlSpec(xmlFileName))
.multiPart(createCustomContentTypeSpec(customContentTypeFileName))
.multiPart(createDataSpec())
.multiPart(createImageSpec())
.multiPart(createOtherImage(otherImage))
.post("/multipart/all-file-control-names")
.then()
.statusCode(200)
.extract()
.as(String[].class);
// verify files with content types specified via 'quarkus.http.body.multipart.file-content-types' property
assertThat(controlNames.length, equalTo(5));
assertThat(controlNames[0], equalTo(xmlFileName));
assertThat(controlNames[1], equalTo(customContentTypeFileName));
// verify files that are also MultipartBody fields
assertThat(controlNames[2], equalTo(DATA));
assertThat(controlNames[3], equalTo(IMAGE));
// verify file that is neither MultipartBody field nor file content type specified via config property
// file is present as image/png is known file type
assertThat(controlNames[4], equalTo(otherImage));
}

private static MultiPartSpecification createOtherImage(String otherImage) {
return new MultiPartSpecBuilder(imageFile)
.controlName(otherImage)
.fileName("other.png")
.mimeType("image/png")
.build();
}

private static ValidatableResponse whenSendMultipartData(String path) {
MultiPartSpecification textSpec = new MultiPartSpecBuilder(TEXT_WITH_DIACRITICS)
.controlName("text")
.mimeType("text/plain")
.charset(StandardCharsets.UTF_8)
.build();
MultiPartSpecification dataSpec = new MultiPartSpecBuilder(randomBytes)
.controlName("data")
.fileName("random.dat")
.header("Content-Type", "application/octet-stream")
.build();
MultiPartSpecification imageSpec = new MultiPartSpecBuilder(imageFile)
.controlName("image")
.fileName("quarkus.png")
.mimeType("image/png")
.build();
MultiPartSpecification dataSpec = createDataSpec();
MultiPartSpecification imageSpec = createImageSpec();

return RestAssured.given()
.contentType("multipart/form-data")
.contentType(MediaType.MULTIPART_FORM_DATA)
.multiPart(textSpec)
.multiPart(imageSpec)
.multiPart(dataSpec)
.post(path)
.then()
.statusCode(200);
}

private static MultiPartSpecification createImageSpec() {
return new MultiPartSpecBuilder(imageFile)
.controlName(IMAGE)
.fileName("quarkus.png")
.mimeType("image/png")
.build();
}

private static MultiPartSpecification createDataSpec() {
return new MultiPartSpecBuilder(randomBytes)
.controlName(DATA)
.fileName("random.dat")
.header("Content-Type", "application/octet-stream")
.build();
}

private static MultiPartSpecification createXmlSpec(String xmlFileName) {
return new MultiPartSpecBuilder("<song>Born To Be Wild</song>")
.controlName(xmlFileName)
.fileName("my.xml")
.header("Content-Type", MediaType.TEXT_XML)
.build();
}

private static MultiPartSpecification createCustomContentTypeSpec(String customContentTypeFileName) {
return new MultiPartSpecBuilder("The Rocky Road to Dublin")
.controlName(customContentTypeFileName)
.fileName("your.custom")
.header("Content-Type", "custom/content-type")
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.MultipartForm;

import io.smallrye.mutiny.Uni;

Expand Down Expand Up @@ -50,6 +49,6 @@ public interface FileClient {
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.TEXT_PLAIN)
@Path("/upload-multipart")
String sendMultipart(@MultipartForm FileWrapper data);
String sendMultipart(FileWrapper data);

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.resteasy.reactive.MultipartForm;
import org.jboss.resteasy.reactive.RestResponse;

import io.quarkus.logging.Log;
Expand Down Expand Up @@ -61,7 +60,7 @@ public Uni<String> upload(File body) {
@Produces(MediaType.TEXT_PLAIN)
@Path("/upload-multipart")
@Blocking
public String uploadMultipart(@MultipartForm FileWrapper body) {
public String uploadMultipart(FileWrapper body) {
deathRow.add(body.file);
return utils.getSum(body.file.getAbsoluteFile().toPath());
}
Expand Down