I am refactoring some legacy code used to upload files using Commons Fileupload and JSPs, to use Spring Controllers. Given that lots of the code for file uploads is based on Commons Fileupload, I want to reuse that code. The first attempt was made directly moving the code from the JSP to a Spring Controller, like this:
#RequestMapping(path = { "/my/file/upload/request/mapping" })
public ModelAndView myControllerMethod(HttpServletRequest request, HttpServletResponse response){
List<FileItem> items = null;
if (ServletFileUpload.isMultipartContent(request)) {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
items = upload.parseRequest(request);
System.out.println("number of items: " + (items != null ? items.size() : "null"));
}
...
Although the program enters the if, recognizing it as a multipart request, this always shows number of items: 0.
I have included a MultipartViewResolver like this:
#Configuration
#EnableWebMvc
#SuppressWarnings("deprecation")
public class WebMvcConfiguration extends WebMvcConfigurerAdapter{
#Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(maxFileUploadSizeBytes);
return multipartResolver;
}
but the result is the same with or without it.
As stated in many places (like here (Baeldung) and here (SO)), it seems that Commons Fileupload cannot read the multipart because it has already been read by Spring. So the solution seems to be to disable Spring multipart support. Many posts tell how to do that for Spring Boot but I have no idea about how to do that for raw Spring 5. Any idea about how to do it?
Note: I've also tried to get the multipart files using the Spring way, like this:
#RequestMapping(path = { "/my/file/upload/request/mapping" })
public ModelAndView myControllerMethod(#RequestParam MultipartFile file){
and even #RequestParam MultipartFile[] files but I obtain null files or empty files array respectively. Again, I've checked with Firefox console that the request being made is exactly the same as with the old JSP code, which worked, so the problem is not in the request being made.
You are missing the name of the request parameter to bind to.
Refer spring's java doc here
For example,
#RequestParam("file") MultipartFile multipartFile
"file" is the name of the request parameter in your JSP.
<input type="file" name="file"><br />
And make sure you are using POST method as well.
#RequestMapping(value = "/my/file/upload/request/mapping", method = RequestMethod.POST)
Make sure your form has the attribute, enctype="multipart/form-data";
<form enctype="multipart/form-data" >
</form>
afterwards your request param need to be corrected to ;
#RequestParam("file") MultipartFile file
not you can test using the following condition, and store the multipart in a byte array
if(file.isEmpty()){
byte [] bytefile= file.getBytes();
}
else{
logger.info("not caught");
}
Related
I have a microservice architecture, where one service acts as a proxy, and must only forward the uploaded form data payload to the downstream service using restTemplate, preferably without loading anything from the request on disk or into memory.
I managed to resolve the issue taking the following steps.
Here I will describe the approaches, and the limitations used:
I have the following rest template configuration:
#Bean
public RestTemplate myRestTemplate() {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.setInterceptors(new ArrayList<>()); // to avoid interceptors loading data into memory
return restTemplate;
}
in my controller I am processing the HttpServletRequest directly using Apache Commons FileUpload Streaming Api with one asterix:
Special care on the multipart form data, so first the form fields are processed in the while loop, and then only one file was I able to process, since:
FileItemStream fileItemStream = uploadItemIterator.next();
return fileItemStream.openStream();
must be returned without invoking itemIterator.hasNext(), because that will result in FileItemStream.ItemSkippedException
which works wonderfully, no data is saved on disk
c:\Users\myuser\AppData\Local\Temp\tomcat.11416588345568217859.8077\
note: I have set the following property as stated in the documentation.
spring.application.servlet.multipart.enabled: false
From here, Using the streaming api I have an inputStream, which I will pass further down to create my HttpEntity as follows (simplified in example, full inspiration to include filename in request: here):
MultiValueMap<String, Object> multiPartBody = new LinkedMultiValueMap<>();
multiPartBody.add(FILE, inputStream);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(multiPartBody, myHeaders);
after this, I do make the call to my rest Template:
myRestTemplate.postForEntity(url, requestEntity, MyResponse.class);
this goes all the way via the following sequence:
RestTemplate.doExecute()
HttpAccessor.createRequest()
HttpComponentsClientHttpRequestFactory.createRequest() -> which will return a **HttpComponentsStreamingClientHttpRequest** <- this one is important
RestTemplate.doWithRequest(ClientHttpRequest httpRequest) -> calls: ((HttpMessageConverter<Object>) messageConverter).write(
requestBody, requestContentType, httpRequest);
FormHttpMessageConverter.write()
FormHttpMessageConverter.writeMultipart() -> where outputMessage instanceof StreamingHttpOutputMessage is true
HttpComponentsStreamingClientHttpRequest.executeInternal -> creates a new StreamingHttpEntity(...)
after which this goes down on InternalCLientExecution, and in execChain
sooner or later it will enter in the chain:
HttpComponentsStreamingClientHttpRequest.StreamingHttpEntity.writeTo(OutputStream outputStream) throws IOException {
this.body.writeTo(outputStream);
}
where body is a FormHttpMessageConverter.lambda from above:
if (outputMessage instanceof StreamingHttpOutputMessage streamingOutputMessage) {
streamingOutputMessage.setBody(outputStream -> {
writeParts(outputStream, parts, boundary);
writeEnd(outputStream, boundary);
});
}
so we get further down, and end up in:
FormHttpMessageConverter.writeParts()
FormHttpMessageConverter.writePart()
here a multipartMessage is composed and passed further down (or invoked the superclass AbstractHttpMessageConverter method)
multipartMessage = new MultipartHttpOutputMessage(os, charset);
...
((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage);
from here we get into AbstractHttpMessageConverter.write where condition
if (outputMessage instanceof StreamingHttpOutputMessage)
evaluates to false because MultipartHttpOutputMessage is not an instance of StreamingHttpOutputMessage
But this seems not to affect anything, since the whole thing is invoked in the above mentioned lambda, sooner or later, we need to write the bytes from the inputStream into the outputStream.
one impediment:
if I configure the restTemplate as follows:
#Bean
#org.springframework.cloud.client.loadbalancer.LoadBalanced
public RestTemplate myRestTemplate() {
...
}
there is an interceptor/aspect overriding the RestTemplate HttpComponentsClientHttpRequestFactory with RibbonClientHttpRequestFactory (using spring netflix stack), which does not support setBufferRequestBody(false).
That is how I managed to solve the file streaming issue, hope it helps others too:
Limitations/Constraints:
You cannot use MultipartFile in your controllers since spring by default saves data into temp files on fileSystem (can't use resolve-lazily either: because), I was able to overcome this issue only with Apache Commons FileUpload
Using Apache Commons FileUpload I managed to process only one file, and the form data need to be processed before the file data
spring.application.servlet.multipart.enabled: false -> affects other endpoints too
composing downstream form data with correct Content-Disposition: form-data; name="file"; filename="my.txt" needs some strange embedded HttpEntity constructions
#LoadBalanced overrides the whole restTemplate requestFactory
Good luck everyone, and any feedback is welcome.
I am writing a backend api which consumes a json request with multipart image files. I am unable to figure out a way to do so.
Anyone, faced and found a solution to a problem similar to mine...???
You can define a Spring Controller endpoint accepting a DTO and a MultipartFile parameter
#RestController
#RequestMapping("/svc")
public class MyController {
#PostMapping
public String create(
#RequestPart(name = "dto") Dto dto, // my Java DTO
#RequestPart(value = "file") MultipartFile file) throws Exception {
}
You should be able to use MultipartFile[] files if you need to consume multiple files.
I have an app with an endpoint and a tomcat server running with it. I can hit an endpoint on postman with a file and have it uploaded to an excel file on my server.
The problem is in order to get it to work I have to add allowCasualMultipartParsing="true" in the context.xml of my tomcat I don't want to have to add this fix on my local tomcat.
I need a fix that will work despite the tomcat server it's running on. So if someone were to add a new tomcat they wouldn't have this issue. For example on tomcat 9.0.6
Its hard to demonstrate the issue because it's due to running the app on a standalone tomcat. It works when I run it without the standalone tomcat 9.0.38 .
Code for uploading file
#Controller
#RequestMapping("/ListCtrl")
public class listController {
#RequestMapping(method = {RequestMethod.POST}, value = "/list")
#Consumes (MediaType.MULTIPART_FORM_DATA)
#Produces (MediaType.TEXT_XML)
#ResponseBody public Map<String, Object> uploadFile(
#FormDataParam("file") InputStream uploadedInputStream,
#FormDataParam("file") MultipartFile file,
#RequestParam("listName") String listName,
#RequestParam Integer
listid){
Map<String, Object> resultMap = null;
resultMap = new HashMap<>();
resultMap.put("status", "successful");
resultMap.put("file", file.getName());
System.out.println(file.getOriginalFilename());
return resultMap;
}
The error I'm getting on the project that isn't working is
HTTP Status 500 - Could not parse multipart servlet request; nested exception is java. lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided
I found the solution. I've seen it before but didn't fully understand so I wanted to elaborate here in case anyone else sees it. This solution will work despite the tomcat its running on.
You need to create a META-INF folder. For me I created under a deployed resources folder and I added a context.xml. After I put allowCasualMultipartParsing="true" in the context tag.
Webapp/Deployed Resources/META-INF/context.xml.
Spark Java: Unable to process parts as no multi-part configuration has been provided
It is enough if you provide a configuration. As it has been mentioned in the spring documentation, there are two concrete implementations included in Spring.
CommonsMultipartResolver for Apache Commons FileUpload and
StandardServletMultipartResolver for the Servlet 3.0+ Part API
For better flexibility and configurabilities sake, I choose to use CommonsMultipartResolver. Among the advantages, it provides maxUploadSize, maxInMemorySize, and defaultEncoding settings as bean properties. But, you have to import it as:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
And in your code:
#Configuration
public class WebConfig {
private int maxUploadSizeInMb = 2 * 1024 * 1024; // 2 MB
...
...
#Bean("multipartResolver")
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver cmr = new CommonsMultipartResolver();
cmr.setMaxUploadSize(maxUploadSizeInMb * 2); //sum size of all files/parts of a file. Since, a file may be partitioned
cmr.setMaxUploadSizePerFile(maxUploadSizeInMb);//maximum file size of each file
return cmr;
}
}
I am trying to merge two Spring based projects into one but I am having an issue with MultiPartResolver. Inside my merged application there are two classes that use uploaded POST methods using different upload methods.
One uses the HttpServletRequest to retrieve the uploaded file :
#RequestMapping(method = RequestMethod.POST)
public #ResponseBody void handleResult(HttpServletRequest request,
HttpServletResponse response)
and the other uses MultipartFile to get the file and another field called notes which is passed to the form:
#RequestMapping(method = RequestMethod.POST)
public #ResponseBody String handleFormUpload(#RequestParam("notes")
String notes, #RequestParam("file") MultipartFile file)
The code then references 'file' to process the uploaded file.
The problem I have is the MultipartFile class requires this in the applicationContext.xml:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="250000000"/>
</bean>
When this is in the applicationContext.xml file the original Servlet based method of posting fails. The POST request reaches this class but the file seems to have been stripped out as it processes it. As soon as I comment this out of the applicationContext.xml, the servlet method works again. However, the section of the code that uses the MultipartResolver now fails!
I do not have much experience of Spring but I'm trying my best to do this. I cannot work out how to prevent CommonsMultipartResolver from manipulating the POST files that are destined for the Servlet based class.
Can anybody help me?
I am using Spring MVC on a J2EE Web application.
I have created a method that bounds the request body to a model like the above
#RequestMapping(value = "/", method = RequestMethod.POST, produces = "application/json")
public AModel createEntity(#Valid #ModelAttribute MyInsertForm myInsertForm) {
// coding..
}
Everything are working great and when i include a property of type MultipartFile in the MyEntityForm, then i have to make the request with content type "multipart/form-data".
Also, everything are working great with this scenario too.
The problem i am facing is that i would like to have the MultipartFile property as optional.
When a client request include a file my method works great but when a client request does not include a file spring throws a
HTTP Status 500 - Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is org.apache.commons.fileupload.FileUploadException: Stream ended unexpectedly
Is there any way to solve this issue without creating two methods on my controller (one with a MultipartFile and another without)?
I had the same issue and just adding the required=false worked for me; because, I don't send a file all the time. Please find the sample code below,
#RequestMapping(value = "/", method = RequestMethod.POST, produces = "application/json")
public AModel createEntity(#Valid #ModelAttribute MyInsertForm myInsertForm, #RequestParam(value ="file", required=false) MultipartFile file) {
// coding..
}
Give a try by adding
(required=false)
to multipart property in method signature.
When you wish to send one or more files using HTTP, you have to use multipart request. This means that the body of the request will be like the above,
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="text"
text default
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain
Content of a.txt.
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html
When you wish to send only data (and not files) you can send them as json, key-value pairs etc.
Spring framework uses the #ModelAttribute annotation when you wish to map a multipart request to an object.
When you have a normal key-value request, you use the #RequestBody annotation.
Thus, you can't have the MultipartFile optional, because you have to use different annotations. Using two different methods, one per request type, solves the issue. Example,
#RequestMapping(value = "/withFile", method = RequestMethod.POST, produces = "application/json")
public ReturnModel updateFile(#ModelAttribute RequestModel rm) {
// do something.
}
#RequestMapping(value = "/noFile", method = RequestMethod.PUT, produces = "application/json")
public ReturnModel updateJson(#RequestBody RequestModel rm) {
// do something else.
}