Unable to upload file due to Content-Type "multipart/form-data" not set for request body of type StandardMultipartFile - spring-boot

I have a remote service A which does the file upload. I have service B which calls the upload API of service A through FeignClient to upload a file
The method definition in Service A is something like
ResponseEntity<?> upload(#RequestPart("file") MultipartFile file) { }
And the method in Service B is
#FeignClient(url = "http://localhost:5000/")
public interface uploadService {
#RequestMapping(method = RequestMethod.POST, value = "/serviceA/upload")
#Headers("Content-Type: multipart/form-data")
void uploadFile(#RequestPart("file") MultipartFile file);
}
I am getting the error
Content-Type "multipart/form-data" not set for request body of type StandardMultipartFile
I have tried most of the suggestions on https://github.com/spring-cloud/spring-cloud-netflix/issues/867 and
https://github.com/OpenFeign/feign-form but nothing works for me

I was able to solve this issue by simply adding consumes = "multipart/form-data" in the RequestMapping. The reason was that I was mixing spring based annotations with open feign annotations. #Headers("Content-Type: multipart/form-data") works with Open feign. Here I am using spring-cloud-openfeign which provides abstraction to Open feign and make it easy to integration with spring framework components.
#FeignClient(url = "http://localhost:5000/")
public interface uploadService {
#RequestMapping(method = RequestMethod.POST, value = "/serviceA/upload" consumes = "multipart/form-data" )
void uploadFile(#RequestPart("file") MultipartFile file);
}

If you have trouble just within the test just use org.springframework.mock.web.MockMultipartFile where you can set contentType as one of argument in construtor.

Related

How to do java unit test with protobuf for controller?

I have a spring boot rest controller with requestBody & responseBody both protobuf. like below :
#RequestMapping(value = "/position/open", produces = "application/x-protobuf")
#ResponseBody
public MsgProto.Response positionOpen(#RequestBody MsgProto.Request request)throws Exception {
log.info("start /position/open");
return orderPositionService.addOrder(request);
}
Now I want to do a unit test using mockMvc to test the controller, but it failed every time. I believe it is the code below which is wrong to fire an HTTP request with protobuf, any idea how to resolve it?
mockMvc.perform(post("/position/open").contentType("application/x-protobuf")
.content(ObjectsMock.mockMsgProtoRequest().toByteArray())).andDo(print())
.andExpect(status().isOk());
Exception :
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotSupportedException
MockHttpServletResponse:
Status = 415
Error message = null
Headers = [Accept:"application/json, application/octet-stream,
application/xml, application/*+json, text/plain, text/xml, application/x-www-
form-urlencoded, application/*+xml, multipart/form-data, multipart/mixed, */*"]
I assume the ProtobufHttpMessageConverter is missing here. Spring MVC can't read/write any messages without this specific converter.
You can create it as the following:
#Bean
public ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
Next, make sure to add the HTTP Method to your method, as I assume (from reading your test) you want this to be a HTTP POST handler. You can also add the consumes attribute to state that this endpoint also consumes Protobuf.
#RequestMapping(method = RequestMethod.POST, consumes = "application/x-protobuf", value = "/position/open", produces = "application/x-protobuf")
#ResponseBody
public MsgProto.Response positionOpen(#RequestBody MsgProto.Request request)throws Exception {
log.info("start /position/open");
return orderPositionService.addOrder(request);
}
In addition to this, there is an article on the Spring blog available that covers your usecase and explains how to use Protobuf with Spring MVC.
You need to add Protobuf converter to MockMvc builder
MockMvcBuilders.standaloneSetup(controller)
.setMessageConverters(new ProtobufHttpMessageConverter())
.build()
This fixed the issue for me

File-upload fails when called via a service i.e restTemplate.postForEntity

I have below springboot rest end point to upload files.
#RequestMapping(
method = RequestMethod.POST,
value = "/v1/file-upload",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Response> uploadFile (
#RequestParam(value = "multipartFile") MultipartFile multipartFile) throws IOException {
String str = storeFile(multipartFile);
return new ResponseEntity<>(new Response("successfully uploaded with name "+str), HttpStatus.OK);
}
and properties as below
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=30MB
this API when called via postman, working fine.
but when called via restTemplate from a service, it's throwing
{"timestamp":"2020-05-26T09:17:46.369+0000","status":500,"error":"Internal Server Error","message":"Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field multipartFile exceeds its maximum permitted size of 1048576 bytes.","path":"/43c0800d-b992-45b6-8d25-9e81115539d0/Form/files/mock/api/v1/file-upload"}
exception.
my service calls as below
apiCallResponseObj = restClientUtil.postEntity(serviceUrl, Object.class, apiEndPoint.getFormData(), headers);
apiEndPoint.getFormData() has the multipart file data.
my question is, why am I getting an exception when called via a service?
using springboot 2.1.13
the issue was with api-gateway (kong) where the limitations were put.
the configurations given in the question works fine.

Spring boot large file upload and download support

I have a spring boot web application which will handle large file (max size of 5g) upload and then save it to s3. The request and response may last for a long time.
So what is the best practice to handle the upload and download like this? How to make a good performance to prevent my server down when download or upload large files?
you can use multipart/form-data
#RequestMapping(value = "/agency/create", method = RequestMethod.POST, consumes = "multipart/form-data")
public ResponseEntity<List<String>> createAgency(
#RequestParam(value = "username", required = true) String username,
#RequestParam(value = "pic1", required = true)MultipartFile pic1File,
MultipartHttpServletRequest request, ModelAndView modelAndView) {
List<String> requestKeys=new ArrayList<String>();
List<String> originalFileName=new ArrayList<String>();
request.getFileNames().forEachRemaining(requestKeys::add);
for(String multipartFile:requestKeys) {
originalFileName.add(request.getFile(multipartFile).getOriginalFilename());
}
storageService.store(pic1File);
return new ResponseEntity<List<String>>(originalFileName, HttpStatus.CREATED);
}
Posting in case someone finds this useful in the future. This works with a REST controller as of Spring Boot 2.4.2.
Class annotations:
#RestController
#RequestMapping("/api")
Method declaration:
#RequestMapping(path = "/file-upload/{depot}/{fileName}", method = {RequestMethod.POST, RequestMethod.PUT})
public ResponseEntity<String> fileUpload(
#PathVariable(name = "depot") String depot,
#PathVariable(name = "fileName") String fileName,
InputStream inputStream,
HttpServletRequest request,
HttpServletResponse response)
The above is the Spring Boot configuration for a REST Controller that worked for me for large file upload. The key was adding InputStream inputStream directly.

Sending file to Spring Boot REST using Axios

I am trying to send a csv file to my java spring boot backend. The code to send my file is below:
var url = 'http://localhost:3001/UploadFile';
var file = this.state.file;
var formData = new FormData();
formData.append("file", file);
axios.post(url, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
And the code to accept my file from Spring Boot:
#CrossOrigin
#RequestMapping("/UploadFile")
#ResponseBody
public void uploadFile(#RequestParam("file") MultipartFile file) {
}
However, it doesn't seem to work. I keep getting an error saying that the 'Current request is not a multipart request'. Any ideas?
It's not sufficient to specify content-type in frontend you need to do it in controller as well.
You should tell to spring controller what it should consume and also it would be nice to set RequestMethod as POST like this:
#CrossOrigin
#RequestMapping("/UploadFile")
#ResponseBody
public void uploadFile(#RequestParam("file") MultipartFile file, method = RequestMethod.POST, consumes = "multipart/form-data") {
}

Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported for #RequestBody MultiValueMap

Based on the answer for problem with x-www-form-urlencoded with Spring #Controller
I have written the below #Controller method
#RequestMapping(value = "/{email}/authenticate", method = RequestMethod.POST
, produces = {"application/json", "application/xml"}
, consumes = {"application/x-www-form-urlencoded"}
)
public
#ResponseBody
Representation authenticate(#PathVariable("email") String anEmailAddress,
#RequestBody MultiValueMap paramMap)
throws Exception {
if(paramMap == null || paramMap.get("password") == null) {
throw new IllegalArgumentException("Password not provided");
}
}
the request to which fails with the below error
{
"timestamp": 1447911866786,
"status": 415,
"error": "Unsupported Media Type",
"exception": "org.springframework.web.HttpMediaTypeNotSupportedException",
"message": "Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported",
"path": "/users/usermail%40gmail.com/authenticate"
}
[PS: Jersey was far more friendly, but couldn't use it now given the practical restrictions here]
The problem is that when we use application/x-www-form-urlencoded, Spring doesn't understand it as a RequestBody. So, if we want to use this
we must remove the #RequestBody annotation.
Then try the following:
#RequestMapping(
path = "/{email}/authenticate",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = {
MediaType.APPLICATION_ATOM_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE
})
public #ResponseBody Representation authenticate(
#PathVariable("email") String anEmailAddress,
MultiValueMap paramMap) throws Exception {
if (paramMap == null &&
paramMap.get("password") == null) {
throw new IllegalArgumentException("Password not provided");
}
return null;
}
Note that removed the annotation #RequestBody
answer: Http Post request with content type application/x-www-form-urlencoded not working in Spring
It seems that now you can just mark the method parameter with #RequestParam and it will do the job for you.
#PostMapping( "some/request/path" )
public void someControllerMethod( #RequestParam Map<String, String> body ) {
//work with Map
}
Add a header to your request to set content type to application/json
curl -H 'Content-Type: application/json' -s -XPOST http://your.domain.com/ -d YOUR_JSON_BODY
this way spring knows how to parse the content.
In Spring 5
#PostMapping( "some/request/path" )
public void someControllerMethod( #RequestParam MultiValueMap body ) {
// import org.springframework.util.MultiValueMap;
String datax = (String) body .getFirst("datax");
}
#RequestBody MultiValueMap paramMap
in here Remove the #RequestBody Annotaion
#RequestMapping(value = "/signin",method = RequestMethod.POST)
public String createAccount(#RequestBody LogingData user){
logingService.save(user);
return "login";
}
#RequestMapping(value = "/signin",method = RequestMethod.POST)
public String createAccount( LogingData user){
logingService.save(user);
return "login";
}
like that
Simply removing #RequestBody annotation solves the problem (tested on Spring Boot 2):
#RestController
public class MyController {
#PostMapping
public void method(#Valid RequestDto dto) {
// method body ...
}
}
I met the same problem when I want to process my simple HTML form submission (without using thymeleaf or Spring's form tag) in Spring MVC.
The answer of Douglas Ribeiro will work very well. But just in case, for anyone, like me, who really want to use "#RequestBody" in Spring MVC.
Here is the cause of the problem:
Spring need to ① recognize the "Content-Type", and ② convert the
content to the parameter type we declared in the method's signature.
The 'application/x-www-form-urlencoded' is not supported, because, by
default, the Spring cannot find a proper HttpMessageConverter to do
the converting job, which is step ②.
Solution:
We manually add a proper HttpMessageConverter into the Spring's
configuration of our application.
Steps:
Choose the HttpMessageConverter's class we want to use. For
'application/x-www-form-urlencoded', we can choose
"org.springframework.http.converter.FormHttpMessageConverter".
Add the FormHttpMessageConverter object to Spring's configuration,
by calling the "public void
configureMessageConverters(List<HttpMessageConverter<?>>
converters)" method of the "WebMvcConfigurer" implementation class
in our application. Inside the method, we can add any
HttpMessageConverter object as needed, by using "converters.add()".
By the way, the reason why we can access the value by using "#RequestParam" is:
According to Servlet Specification (Section 3.1.1):
The following are the conditions that must be met before post form
data will be populated to the parameter set: The request is an HTTP
or HTTPS request. 2. The HTTP method is POST. 3. The content type is
application/x-www-form-urlencoded. 4. The servlet has made an initial
call of any of the getParameter family of methods on the request
object.
So, the value in request body will be populated to parameters. But in Spring, you can still access RequestBody, even you can use #RequstBody and #RequestParam at the same method's signature.
Like:
#RequestMapping(method = RequestMethod.POST, consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public String processForm(#RequestParam Map<String, String> inputValue, #RequestBody MultiValueMap<String, List<String>> formInfo) {
......
......
}
The inputValue and formInfo contains the same data, excpet for the type for "#RequestParam" is Map, while for "#RequestBody" is MultiValueMap.
I wrote about an alternative in this StackOverflow answer.
There I wrote step by step, explaining with code. The short way:
First: write an object
Second: create a converter to mapping the model extending the AbstractHttpMessageConverter
Third: tell to spring use this converter implementing a WebMvcConfigurer.class overriding the configureMessageConverters method
Fourth and final: using this implementation setting in the mapping inside your controller the consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE and #RequestBody in front of your object.
I'm using spring boot 2.
#PostMapping(path = "/my/endpoint", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE })
public ResponseEntity<Void> handleBrowserSubmissions(MyDTO dto) throws Exception {
...
}
That way works for me
You can try to turn support on in spring's converter
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// add converter suport Content-Type: 'application/x-www-form-urlencoded'
converters.stream()
.filter(AllEncompassingFormHttpMessageConverter.class::isInstance)
.map(AllEncompassingFormHttpMessageConverter.class::cast)
.findFirst()
.ifPresent(converter -> converter.addSupportedMediaTypes(MediaType.APPLICATION_FORM_URLENCODED_VALUE));
}
}
Just add an HTTP Header Manager if you are testing using JMeter :

Resources