spring boot multipartFile with application/octet-stream throws exception - java

I wanted to build a simple Rest api using Spring boot which accepts any given file and then performs some operations on it . I went through the spring examples on multipartFile https://spring.io/guides/gs/uploading-files/ and I decided to follow the same approach. The files that will be uploaded through my rest api will have some specific extension. So,i gave the content-type as application/octet-stream . When I try to run my unit test cases for the same,
I always get the exception of
nested exception is org.springframework.web.multipart.MultipartException: The current request is not a multipart request
This exception does not appear if the content type is text/plain or if there is no 'consumes' parameter in the requestMapping.
My controller code looks as follows :
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
#Controller
#RequestMapping("/v1/sample")
public class SampleController {
private static Logger log = LoggerFactory.getLogger(SampleController.class);
#RequestMapping(path = "/{id}/upload",
consumes = {MediaType.APPLICATION_OCTET_STREAM_VALUE},
method = RequestMethod.POST)
public ResponseEntity<String> uploadfile(#PathVariable String id,
#RequestParam("file") MultipartFile upgradeFile) {
log.info("Obtained a upload request for the id {}",id );
return new ResponseEntity<String>("file upload has been accepted.",
HttpStatus.ACCEPTED);
}
}
And my unit test snippet is as follows :
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import com.stellapps.devicemanager.fota.Application;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringApplicationConfiguration(classes = Application.class)
#EnableWebMvc
public class ControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void mockMvcBuilder() {
this.mockMvc = webAppContextSetup(webApplicationContext).build();
}
#Test
public void test_ValidPayload() {
String uri = "/v1/sample/1234/upload";
Path path = Paths.get("src/test/resources/someFile");
try {
byte[] bytes = Files.readAllBytes(path);
MockMultipartFile multipartFile =
new MockMultipartFile("file", "someFile.diff", "application/octet-stream", bytes);
mockMvc.perform(fileUpload(uri).file(multipartFile).contentType(MediaType.APPLICATION_OCTET_STREAM_VALUE))
.andExpect(status().isAccepted());
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
If I use text/plain as the content-type and i give a normal text file, it goes through fine. If I add the content-type as application/octet-stream it throws the following exception
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: org.springframework.web.multipart.MultipartException: The current request is not a multipart request
at org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver.assertIsMultipartRequest(RequestPartMethodArgumentResolver.java:204)
at org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver.resolveArgument(RequestPartMethodArgumentResolver.java:129)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:99)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:817)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:731)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:968)
How Do I make my request accept application/octet-stream and what changes should I make to the test case to ensure it succeeds.
UPDATE:
Removing the consumes header and by not specifying the content-type in MockPartFile is one of the way to upload a file. I suppose by default the controller takes it as application/octet-stream
UPDATE:
Thank you for the response. I was using an earlier version of spring (1.3.1) and after going through the answer, I updated my spring version and the test case to match it and It started working.

Try below steps
Remove the consumes from the RequestMapping attributes.
MockMultipartFile multipartFile = new MockMultipartFile("file","somename","multipart/form-data", fileinputstream);
Change to MockMvc:
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/v1/sample/1234/payload")
.file(multipartFile)
.andExpect(status().isOk());
For help check
Upload file using Spring mvc and MockMVC
EDIT:
I've tried a sample spring-boot-rest app with your scenario. Seems application/octet-stream is no issue. Please check my repo
https://github.com/satya-j/boot-file-manager

Related

Prometheus 'expected label name, got "BCLOSE"' error

I have an application running on port 7070 on my local. It exposes and endpoint /metrics and shows all the tags that are available. Prometheus is not able to get these data and it says 'expected label name, got "BCLOSE"'.
I have been trying to figure this out but not sure why this code doesn't work:
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.prometheus.client.exporter.common.TextFormat;
import metrics.PrometheusRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
#Path("/metrics")
public class MetricsController {
private final PrometheusMeterRegistry prometheusRegistry = PrometheusRegistry.INSTANCE();
#GET
public Response getMetrics(#Context HttpHeaders headers) {
Writer writer = new StringWriter();
try {
TextFormat.write004(writer, prometheusRegistry.getPrometheusRegistry().metricFamilySamples());
} catch (IOException e) {
e.printStackTrace();
}
return writer.toString();
}
}
Also, the application is neither a sprintboot nor a spring project.
Tried this:
#GET
public Response getMetrics(#Context HttpHeaders headers) {
String accept = headers.getRequestHeader("Accept").get(0);
System.out.println("Accept Header --------------------------> " + accept);
return Response.ok(prometheusRegistry.scrape(), "application/openmetrics-text").build();
}
Even then the same error as above SS.
This worked for me:
#GET
public Response getMetrics() {
return Response.ok(prometheusRegistry.scrape(), TextFormat.CONTENT_TYPE_004).build();
}

Having trouble in verifying the PACT from provider side using java junit5 maven spring-boot

I have to do PACT verification in java + spring-boot + maven.
I am currently running this tests with junit5.
My pom.xml looks like
<dependency>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-provider-junit5</artifactId>
<version>4.0.10</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-consumer-junit5</artifactId>
<version>4.0.10</version>
</dependency>
<plugin>
<groupId>au.com.dius.pact.provider</groupId>
<artifactId>maven</artifactId>
<version>4.1.11</version>
<configuration>
<pactDirectory>${basedir}/target/pacts</pactDirectory>
<pactBrokerUrl><BROKERURL></pactBrokerUrl>
<projectVersion><PROJECTVERSION></projectVersion>
<trimSnapshot>true</trimSnapshot>
</configuration>
</plugin>
Consumer side code is runnning perfectly and published on pact broker too, here is the snippet of it.
package com.contract;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.fluent.Request;
import org.apache.http.entity.StringEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testng.Assert;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import au.com.dius.pact.consumer.MockServer;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
import au.com.dius.pact.consumer.junit5.PactTestFor;
import au.com.dius.pact.core.model.RequestResponsePact;
import au.com.dius.pact.core.model.annotations.Pact;
#ExtendWith(PactConsumerTestExt.class)
class ContractTest {
public static final String jwt_token = "Bearer eyJraWQiOiIyZjFiNzlmMS0xMDQ2LTQ2NGYtYjM5YS0xOGY4MDg5ZGMyMzIiLCJ0eXAiOiJhdCtqd3QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJodHRwczovL3RlbmFudDE1LWFkZW1hdHJpeHN0YWNrNC10cmlhbC5kc21sYWIuYm1jLmNvbSIsImNsaWVudF9pZCI6IjVmNDU4ZjBlLWZmMzAtNDQ2ZC04ZTQwLWYzNjBlNjgyZjYyNSIsInRlbmFudCI6InN0YWNrNHRlbmFudDYiLCJqdGkiOiI4NDc3MDBhNC1hYWY0LTQ2NWMtYmNhYi05MDM0MzYxOTY0MDIiLCJzdWIiOiJEZW1vIiwiaXNzIjoiaHR0cHM6Ly9hZGVpdHNtMi1yc3NvLmRzbWxhYi5ibWMuY29tL3Jzc28iLCJpYXQiOjE2MjY3Njg0MjQsImV4cCI6MTYyNjc2OTMyNH0.EoXpqiyo5nvdPeYHfqq0sa15dQKYayD60UEboPVJojuYBKmvvj2yU03e61wy9Fkq2pZdoTNTifAeLDiG0dYKdlYOI5YTx_K6HGMav6ofYeOwIUj4OBVu7NyWxXVQz-3aXoIyu-MifUtvs6oQwf2YZmOEVtbPuCBxa9C9yA4i72g1TD31Rba1-e5cGG5ipiIE7UaunJ2K-mkt-BL2kzmu6OHdIP6vly7iTzxfOccdrjXEmedgX2hpnPcL_2os5wHCLwdHJwuYLPlgqDbSLHgpXdGL43Jg4ASBbFHg3h30y1yXYJazlgOwvVeBoOVcQYnXBh7wHTMik7zVMAo1VL8N_Q";
public static final String API_V1_VIEW_ALGORITHM = "/aif/api/v1.0/algorithm/b82d4617-c39f-448b-bdf8-3fd52e3250ba";
public static final String BODY_GET_VIEW_ALGO_V1 = "{\"tenantId\":\"6881408\",\"id\":\"5bc098db-7acb-4361-86a9-3c439b477142\",\"name\":\"New Job\",\"description\":\"New Job\",\"creationTime\":\"1625054173273\",\"template\":\"\",\"modifiedTime\":\"1625054173273\",\"owner\":\"ppan\",\"enable\":\"true\",\"executionMode\":\"onDemand\",\"definition\":\"\"}";
private final static Map<String, String> headers;
static {
headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("Authorization", jwt_token);
headers.put("authtype", "rsso-jwt");
}
#BeforeEach
public void setUp(MockServer mockServer) {
assertThat(mockServer, is(notNullValue()));
}
/* ----------------------------------Pact methods ----------------------------------------------------*/
#Pact(provider = <provider name> , consumer = <consumer name>)
public RequestResponsePact createPactViewAlgoV1(PactDslWithProvider builder) {
return builder
.given("algorithm id")
.uponReceiving("a request with details of Algorithm")
.path(API_V1_VIEW_ALGORITHM)
.method("GET")
.headers(headers)
.willRespondWith()
.status(200)
.body(BODY_GET_VIEW_ALGO_V1)
.toPact();
}
/* ----------------------------------Test methods ----------------------------------------------------*/
#Test
#PactTestFor(pactMethod = "createPactViewAlgoV1")
public void testTokenRequest(MockServer mockServer) throws ClientProtocolException, IOException {
HttpResponse httpResponse = Request.Get(mockServer.getUrl() + API_V1_VIEW_ALGORITHM)
.addHeader("Content-Type", headers.get("Content-Type"))
.addHeader("Authorization",headers.get("Authorization"))
.addHeader("authtype", headers.get("authtype"))
.execute()
.returnResponse();
assertEquals(httpResponse.getStatusLine().getStatusCode(), 200);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
AIFAlgorithmModelNew actualResult = objectMapper.readValue(httpResponse.getEntity().getContent(), AIFAlgorithmModelNew.class);
assertEquals(actualResult.getTenantId().toString(), "6881408");
assertEquals(actualResult.getExecutionMode().toString(), "onDemand");
assertEquals(actualResult.getEnable().toString(), "true");
assertEquals(actualResult.getName().toString(), "New Job");
}
}
I have written my Pact provider test class with standard pact format.
I am currently trying to run this on port: 8091.
Tried with port 9362 as well.
looks like this
package com.aif.api.contract;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
<Code libraries>
import au.com.dius.pact.provider.junit.Provider;
import au.com.dius.pact.provider.junit.State;
import au.com.dius.pact.provider.junit.loader.PactBroker;
import au.com.dius.pact.provider.junit5.HttpTestTarget;
import au.com.dius.pact.provider.junit5.HttpsTestTarget;
import au.com.dius.pact.provider.junit5.PactVerificationContext;
import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.restassured.RestAssured;
import io.restassured.http.Cookie;
import io.restassured.config.SSLConfig;
import static org.mockito.Mockito.when;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Base64;
import java.util.UUID;
import javax.ws.rs.core.Response;
import org.apache.http.HttpRequest;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
#ExtendWith(SpringExtension.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = main.class)
#AutoConfigureMockMvc
#TestPropertySource(locations = "classpath:application-contract-test.properties")
#Provider("Provider")
#PactBroker(
host="${PACTBROKER_HOST:PACTBroker URL}",
port="${PACTBROKER_PORT:9292}"
)
public class BasicTest {
static {
System.setProperty("javax.net.ssl.keyStore", "./conf/server_key.p12");
System.setProperty("javax.net.ssl.keyStorePassword", "Y2hhbmdlaXQ=");
}
#Autowired
private ApplicationContext context;
#MockBean
Algor
#MockBean
AlgorithmResource algorithmResource;
#Value("${server.port}")
private int serverPort;
#Value("${pact.provider.version}")
private String version;
#Value("${pact.verifier.publishResults}")
private String publishResults;
#BeforeEach
void setupTestTarget(PactVerificationContext context) {
System.out.println("Inside setupTestTarget");
MockitoAnnotations.initMocks(this);
context.setTarget(new HttpTestTarget("localhost", serverPort, "/"));
System.setProperty("pact.provider.version", version);
System.setProperty("pact.verifier.publishResults", publishResults);
}
#TestTemplate
#ExtendWith(PactVerificationInvocationContextProvider.class)
void pactVerificationTestTemplate(PactVerificationContext context, HttpRequest request) {
context.verifyInteraction();
}
/* consumer = <Consumer Name> */
#State("algorithm id")
public void getAlgorithmById() throws Exception {
System.out.println("Inside getAlgorithmById");
AlgorithmResponseTOFull responseTO = new AlgorithmResponseTOFull();
responseTO.setTenantId("6881408");
responseTO.setId("5bc098db-7acb-4361-86a9-3c439b477142");
responseTO.setName("New Job");
responseTO.setDefinition("");
responseTO.setDescription("New Job");
responseTO.setCreationTime("1625054173273");
responseTO.setModifiedTime("1625054173273");
responseTO.setOwner("ppans");
responseTO.setEnable("true");
responseTO.setExecutionMode("onDemand");
responseTO.setTemplate(getAlgorithmTemplateResponseTO());
Response response = Response.ok(Util.buildResponse(responseTO, Constant.STATUS_MSG_SUCCESS)).build();
when(algorithmResource.getById(Mockito.any())).thenReturn(response);
}
}
When I run this, I get error like
Verifying a pact between <consumer name> and <provider name>
[Using Pact Broker PACTBROKERURL:9292]
Given algorithm id
a request with details of Algorithm
Inside pactVerificationTestTemplate
returns a response which
has status code 200 (FAILED)
has a matching body (FAILED)
Failures:
0) Verifying a pact between <consumer name> and <provider name> - a request with details of Algorithm returns a response which has statusResult code 200
expected status of 200 but was 401
1) Verifying a pact between <consumer name> and <provider name> - a request with details of Algorithm returns a response which has a matching body
Expected a response type of 'application/json' but the actual type was 'text/html'
Please help me to resolve this, as I am getting above error and not able to resolve it
In the consumer where the test is written , it is expecting a response of 200 and response type of "application/json".
If possible, please share the consumer test also that you have written.
Please check that the pact which is getting generated at the pact broker url, and also what is the response and response type it is expecting.
A 401 indicates the request contained invalid or no credentials.
Given that your consumer test has a JWT in it, my guess is that it's expired by the time the provider test runs.
See https://docs.pact.io/provider/handling_auth/ for strategies in dealing with this and the workshops here to see how to out them into action: https://docs.pact.io/implementation_guides/workshops/

Cannot using "response.setService/.setMesage/.setData" in java maven springboot project

I try to build API with springboot, when i make controller and using response.response.setService/.setMesage/.setData i get error "The method setService(String) is undefined for the type Response", please help me to solve this problem.
package com.example.restapi.controller;
import com.example.restapi.entity.Hardware;
import com.example.restapi.service.HardwareService;
import com.example.restapi.util.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping(value = "hardware")
public class HardwareController {
#Autowired
HardwareService hardwareService;
private String serviceString = "Hardware";
ResponseEntity<Response> create (#RequestBody #Validated Hardware hardware)
{
String nameofCurrMethod = new Throwable()
.getStackTrace()[0]
.getMethodName();
Response response = new Response();
response.setService(this.getClass().getName() + nameofCurrMethod); <==This is error code
response.setMessage("Berhasil Membuat Data"); <==This is error code
response.setData(hardwareService.create(hardware)); <==This is error code
return ResponseEntity
.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(response);
}
}

Spring WebFlux File Upload: Unsupported Media Type 415 with Multipart upload

I'm running into some issues handling a file upload using spring's reactive framework. I think I'm following the docs, but can't get away from this 415 / Unsupported Media Type issue.
My controller looks like below (as per the example here: https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-multipart-forms)
package com.test.controllers;
import reactor.core.publisher.Flux;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class TestController {
#RequestMapping(value = "/upload", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<String> uploadHandler(#RequestBody Flux<Part> parts) {
return parts
.filter(part -> part instanceof FilePart)
.ofType(FilePart.class)
.log()
.flatMap(p -> Flux.just(p.filename()));
}
}
POSTing to this endpoint though, always gives me the same output:
curl -X POST -F "data=#basic.ppt" http://localhost:8080/upload
---
"Unsupported Media Type","message":"Content type 'multipart/form-data;boundary=------------------------537139718d79303c;charset=UTF-8' not supported"
I've attempted to use #RequestPart("data") too, but get a similar Unsupported Media Type error, albeit with the content type of the file.
It seems that Spring is having issues converting these to a Part..? I'm stuck - any help is apprecitated!
Well, it's not a direct answer for your question, because I use functional endpoints, but I hope it will help you somehow.
import org.springframework.context.annotation.Bean;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.stereotype.Controller;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import java.io.File;
import java.util.Map;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
import static org.springframework.web.reactive.function.server.RouterFunctions.nest;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
#Controller
public class FileUploadController {
#Bean
RouterFunction<ServerResponse> apiRoutes() {
return nest(path("/api"),
route(POST("/upload"), fileUpload()));
}
private HandlerFunction<ServerResponse> fileUpload() {
return request -> {
return request.body(BodyExtractors.toMultipartData()).flatMap(parts -> {
Map<String, Part> map = parts.toSingleValueMap();
final FilePart filePart = (FilePart) map.get("file");
final String dir = "C:\\JDeveloper\\mywork\\Spring\\SpringTest\\webflux-file-upload\\uploaded";
filePart.transferTo(new File(dir + "/" + filePart.filename()));
return ServerResponse.ok().body(fromObject("ok, file uploaded"));
}
);
};
}
}
You can upload a file with curl like this:
curl -F "file=#C:\Users\Wojtek\Desktop\img-5081775796112008742.jpg" localhost:8080/api/fileupload
Thanks #kojot for your answer, but in this case I discovered the issue was my inclusion of spring-webmvc transiently in addition to spring-webflux. Your solution would likely have worked too, but I wanted to stick with the Controller style so ended up forcibly excluding spring-webmvc from my build.gradle:
configurations {
implementation {
exclude group: 'org.springframework', module: 'spring-webmvc'
}
}
After that it worked as documented.

Spring MultipartFile parameter not respecting configured maxFileSize

I have a file upload Controller. I'm trying to make the max file size configurable, but I'm not able to figure out why the configuration as documented (https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-multipart-file-upload-configuration) is not being applied.
plugins {
id 'org.springframework.boot' version '2.1.4.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.MultipartConfigElement;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
#Controller
public class FileUploadController {
private MultipartConfigElement multipartConfigElement;
#Autowired
public FileUploadController(MultipartConfigElement multipartConfigElement) {
this.multipartConfigElement = multipartConfigElement;
}
#PostMapping("/upload")
public void upload(#RequestParam("file") MultipartFile file) throws IOException {
InputStream inputStream = new BufferedInputStream(file.getInputStream());
// TODO something with inputStream
long fileSize = file.getSize();
boolean fileSizeLimitExceeded = fileSize > multipartConfigElement.getMaxFileSize();
return;
}
}
Debug screenshot
I expect the multipartConfigElement.getMaxFileSize() should prevent larger files getting this far and automatically return a 400 or some other type of exception.
But instead the maxFileSize seems to be completely ignored.
So it turns out that the limits do work, and do automatically throw Exceptions.
I saw this when I ran a request against my Controller using Postman.
{
"timestamp": "2019-04-05T09:52:39.839+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeLimitExceededException: The field file exceeds its maximum permitted size of 1 bytes.",
"path": "/upload"
}
The reason I wasn't seeing that was because I was testing with MockMVC (snippet below). MockMVC doesn't seem to trigger the exceptions for some reason – maybe because it is not running on a compatible web server. Probably related to https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#spring-mvc-test-vs-end-to-end-integration-tests.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.io.FileInputStream;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#RunWith(SpringRunner.class)
#SpringBootTest(classes = DemoApplication.class)
#AutoConfigureMockMvc
public class FileUploadTest {
#Autowired
private MockMvc mockMvc;
#Test
public void givenAFileThatExceedsTheLimit_whenUploaded_responseWith400Error() throws Exception {
MockMultipartFile file =
new MockMultipartFile("file", new FileInputStream(TestUtils.loadLargeFile()));
this.mockMvc
.perform(MockMvcRequestBuilders.multipart("/upload").file(file)
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE))
.andExpect(status().isBadRequest());
}
}
As commented by Andrew E, MockMvc won't replicate the max file size that you set with the properties spring.servlet.multipart.max-file-size and spring.servlet.multipart.max-request-size.
I managed to test the file size limite like this (adapted from here https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.with-running-server)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class RunningServerTest {
// my upload endpint requires authentication, so I need to mock the Oauth2 authorization server response
#MockBean private JwtDecoder jwtDecoder;
#Test
void shouldNotUploadMultipartFileGreaterThanMaxAllowedSize(#Autowired WebTestClient webClient) {
// GIVEN
Jwt jwt =
Jwt.withTokenValue("some-bearer-token")
.header("key", "value")
.claim("email", "user_email#example.com")
.build();
when(jwtDecoder.decode("some-bearer-token")).thenReturn(jwt);
ClassPathResource res = new ClassPathResource("file-bigger-than-max-size.txt");
// WHEN + THEN
webClient
.post()
.uri("/upload")
.header("Authorization", "Bearer some-bearer-token")
.header("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE)
.body(BodyInserters.fromResource(res))
.exchange()
.expectStatus()
// you might need to define a custom exception handler
// to map org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException
// to a 400 http error
.isEqualTo(HttpStatus.BAD_REQUEST);
}
}

Categories