Spring MVC with ajax file upload and MultipartFile - ajax

I have an issue using Ajax upload with Spring 3 MVC. I understand that I have to configure multipartResolver bean in spring config, which I've done. Than I can have controller like this
#RequestMapping(value ="/settingsSim")
#ResponseBody
public Map uploadSimSettings(#RequestParam(value="qqfile", required=true) MultipartFile settings) {
Map<String, Object> ret = new HashMap<String, Object>();
return ret;
}
The problem is that when I actually send the request to the server (actually valums Ajax file upload does this for me), I get an Internal server error response and nothing is shown in the logs. I am really scratching my head now, as I cannot figure out the problem.

my solution:
#RequestMapping(value = "/create/upload", method = RequestMethod.POST, consumes="multipart/form-data", produces="application/json")
#ResponseBody()
public String handleImageUpload(#RequestParam(value="qqfile", required=true) MultipartFile[] files,
#ModelAttribute(value="files") List<MultipartFile> filesSession) throws IOException, FileUploadException {
if (files.length > 0) {
filesSession.addAll(Arrays.asList(files));
// store the bytes somewhere
return "{\"success\": true}";
}
else {
return "{\"success\": false}";
}
}
#RequestMapping(value = "/create/upload", method = RequestMethod.POST, consumes="application/octet-stream", produces="application/json")
#ResponseBody()
public String handleImageUploadApplication(HttpServletRequest request,
#ModelAttribute(value="files") List<MultipartFile> filesSession) throws IOException, FileUploadException {
if (request.getInputStream() != null) {
// creamos el fichero temporal
File file = File.createTempFile("file", "valumns",
RepositoryData.getRepositoryData());
FileOutputStream fos = new FileOutputStream(file);
// copiamos contenido
Streams.copy(request.getInputStream(), fos, true);
//TODO:
//filesSession.addAll(Arrays.asList(files));
// store the bytes somewhere
return "{\"success\": true}";
}
else {
return "{\"success\": true}";
}
}
#ExceptionHandler(Exception.class)
#ResponseStatus(value = HttpStatus.SERVICE_UNAVAILABLE)
public void handleException(Exception ex) {
log.error("Ocurrio un error en el album", ex);
}

I had the same problem with the fineuploader (valums), and I tried using request.getInputStream() but did not get it to work.
The #ResponseBody annotation worked but I got the whole body with headers. I thought processing that and stripping off the unwanted chunks was not very elegant.
I looked further and found the solution is this post:
problem with spring ajax file upload
Like it is said, I added the bean configuration for the multipart resolver to my spring configuration
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
After that, I could easily retrieve my file using
public #ResponseBody Map ajaxUploadFile(#RequestParam MultipartFile qqfile) { ... }
Don't forget to add the Apache commons-io.jar and commons-fileupload.jar libraries in your project to get it to work

When using valums plugin I solved this problem by using #RequestBody Spring annotation.
You could rewrite your code as follows:
#RequestMapping(value ="/settingsSim",method=RequestMethod.POST)
#ResponseBody
public Map uploadSimSettings(#RequestBody String body) {
/*
some controller logic
*/
}
Note that the variable body will contain the contents of the uploaded file. Also there is no method declaration in your example which means that your method will be mapped to GET request.
P.S. I also had this "no multipart boundary" problem when parsing request with Apache Commons. HttpServletRequest#getParts() returns just an empty collection.

#Tomas I encountered same issue while using the same jquery plugin. Please change the Content-Type in the plugin code to xhr.setRequestHeader("Content-Type", "multipart/form-data"); on my plugin its line 1203, after this its now showing a stack trace, however I am encountering another issue where the logs are printing :
Sep 8, 2011 9:43:39 AM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet dispatcher threw exception
org.apache.commons.fileupload.FileUploadException: the request was rejected because no multipart boundary was found

As per my observation the file upload plugin does not send a multipart file but sends a stream. I could get it to work by declaring the controller method to accept filename as request param qqfile and the second parameter as httprequest. I then did further processing using request.getinputstream. Hope that helps!
Regards,
Pradyumna

Related

MockMvc Test does not get to the endpoint for a Multipart file in a RestController

I am calling a service in an orders controller which receives a multipart file and processes it and saving it into a database. I am trying to create a Spring Rest Doc for it but it is not even hitting the endpoint. I am creating a list of orders which is what the service expects. It receives the order as a stream as shown and converts into a stream of orders before saving it into a database. I have shown the main part of the controller and my code for generating the rest docs. When I run the code I get the following exception, it never even hits the endpoint when I set a breakpoint. I also used fileupload() but that did not work either.
Exception is:
Content type = application/json
Body = {"path":"/orders/order_reception","exceptionName":
"MissingServletRequestPartException","message":"Required request part 'uploadFile' is not
present",
"rootExceptionName":"MissingServletRequestPartException",
"rootMessage":"MissingServletRequestPartException: Required request part 'uploadFile' is not present"}
#RestController
#RequestMapping(value = "/orders")
#Validated
class OrderController{
#PostMapping(path = "/order_reception")
public ResponseEntity receiveData(#RequestPart MultipartFile uploadFile,
HttpServletRequest request,
HttpServletResponse response) {
if (!uploadFile.isEmpty()) {
try {
Reader reader = new InputStreamReader(request.getInputStream()));
... save file
return new ResponseEntity<>(HttpStatus.HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
#Test
public void sendData() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Order order = repository.getOrder("1233333");
List<Order> orderList = new ArrayList<>():
resourceList.add(order);
MockMultipartFile orderFile = new MockMultipartFile("order-data", "order.json", "application/json",
mapper.writeValueAsString(orderList).getBytes(Charset.defaultCharset()));
mockMvc.perform(multipart("/orders/order_reception")
.file(orderFile))
.andExpect(status().isCreated())
.andDo(document("send-order",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint())));
}
Thank you Marten Deinum, your suggestion that the file name was wrong fixed it.
I simply changed name in the MockMultipartFile( "uploadsFile", ...)

How to fix multipart/form-data MediaType not being set with Jackson Spring MVC

I'm trying to send a Product and product images from Angular 7 frontend to a SpringMVC backend.
To add support for Multipart files I've added this bean inside my AppConfig.
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1000000);
return multipartResolver;
}
Since I want to receive the Product object separately inside the controller I'm using #RequestPart to fetch both separately like this:
#RequestMapping(value = "save", method = RequestMethod.POST)
public ResponseEntity addProduct(#Valid #RequestPart Product product, #RequestPart MultipartFile[] images, BindingResult bindingResult, HttpServletRequest
}
On the frontend I'm adding the image to FormData like this:
let formData = new FormData();
formData.append('product', new Blob([JSON.stringify(this.product)],{ type: "application/json" }));
// I iterate and append all the images like this
formData.append('image[]', this.images, this.images.name);
this.http.post(this.appService.getApiUrl() + "api/product/save/", product);
The problem is that whenever I submit the form, I get this exception as a response: HTTP Status 415 – Unsupported Media Type.
I tried debugging this issue by setting breakpoints inside CommonsMultipartResolver class and after tracing the request through the code I've found that when the getSupportedMediaTypes() is called it returns only two media types:
application/json
application/*+json
Inside the following method in AbstractHttpMessageConverter:
protected boolean canRead(#Nullable MediaType mediaType) {
if (mediaType == null) {
return true;
} else {
Iterator var2 = this.getSupportedMediaTypes().iterator();
MediaType supportedMediaType;
do {
if (!var2.hasNext()) {
return false;
}
supportedMediaType = (MediaType)var2.next();
} while(!supportedMediaType.includes(mediaType));
return true;
}
}
Finding this I tried adding MediaType.MULTIPART_FORM_DATA like this inside AppConfig:
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
List<MediaType> types = new ArrayList<>();
types.add(MediaType.APPLICATION_JSON);
types.add(MediaType.APPLICATION_JSON_UTF8);
types.add(MediaType.MULTIPART_FORM_DATA);
((MappingJackson2HttpMessageConverter) converter).setSupportedMediaTypes(types);
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(hibernate5Module);
}
}
}
But it still wouldn't work. When the app starts up, I do see the constructor of AbstractJackson2HttpMessageConverter being called with my MediaTypes but they get overwritten by more calls to the same constructor after it.
Is there any way I can get the MediaType to persist? I might be looking in the wrong direction so any insight will be helpful.
The Jackson library is required on the classpath. Spring does not declare this by default. Make sure that at least com.fasterxml.jackson.core:jackson-databind is available in the classpath of the Spring MVC application. Example for Apache Maven:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
Example for the jackson.version value: 2.9.0
1) You need to give input data supported at server end. Since you are sending File, means server is consuming the Multipart Data.
For multipart we need to set consumes = "multipart/form-data"
#RequestMapping(value = "save", method = RequestMethod.POST, consumes = "multipart/form-data")
public ResponseEntity addProduct(#Valid #RequestPart Product product, #RequestPart MultipartFile[] images, BindingResult bindingResult, HttpServletRequest
}
2) Since form is sending multipart data we need to set content-type at front end too in http header in post call.
content-type: multipart/form-data"

Spring Controller return static HTML site from any directory

I would like to return in my #Controller static HTML website that was generated by other process. Let's say that generated .html files are in /tmp/generated. I'm trying to read file and pass its content to ResponseEntity:
#GetMapping(value = "test")
ResponseEntity<String> test(#RequestParam("filename") String filename) throws IOException {
String content = new String(Files.readAllBytes(Paths.get("/tmp/generated/" + filename)), "UTF-8");
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity<String>(content, headers, HttpStatus.OK);
}
But when I open url in browser I get badly encoded html content (stating and ending with '"'):
"\u003chtml\u003e\n\u003chead\u003e\n \u003cmeta charset\u003d\"utf-8\" /\u003e\n \u003cmeta http-equiv\u003d\"X-UA-Compatible\" content\u003d\"IE\u003dedge\" /\u003e\n \u003cmeta name\u003d\"viewport\" content\u003d\"width\u003ddevice-width, initial-scale\u003d1\" /\u003e [.....]
If I add produces = MediaType.TEXT_HTML_VALUE to my #GetMapping annotation then I get 406 Not Acceptable error response (but no exception in my spring app)...
How to fix it?
I'm not sure why you are facing problems when using produces in your mapping.
I gave a quick try and it worked for me.
#GetMapping(value = "test", produces=MediaType.TEXT_HTML_VALUE)
public ResponseEntity<String> test(#RequestParam("filename") String filename) throws IOException {
String content = new String(Files.readAllBytes(Paths.get("/tmp/generated/" + filename)), "UTF-8");
return new ResponseEntity<String>(content, HttpStatus.OK);
}
Tested in Chrome browser:
File
NOTE: I tested this controller using SpringBoot v2.0.5.RELEASE
Cheers!
I have successfully build application with Spring Boot 1.5.2.RELEASE and it will return static HTML site from any directory
you can checkout here

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 :

Apache HttpClient making multipart POST to Spring #Controller class

It seems like there are several posts such as here asking how to use Apache Commons HTTPClient libraries in Java to do a POST to a Servlet. However, it seems like I'm having some problems doing the same thing with a annotated Spring controller method. I've tried a few things but gotten HTTP 401 Bad Request responses from the server. Any examples of doing this would be greatly appreciated.
EDIT: Code I am trying to use:
//Server Side (Java)
#RequestMapping(value = "/create", method = RequestMethod.POST)
public void createDocument(#RequestParam("userId") String userId,
#RequestParam("file") MultipartFile file, HttpServletResponse response) {
// Do some stuff
}
//Client Side (Groovy)
void processJob(InputStream stream, String remoteAddress) {
HttpClient httpclient = new DefaultHttpClient()
httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1)
HttpPost httppost = new HttpPost("http://someurl/rest/create")
MultipartEntity mpEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE)
InputStreamBody uploadFilePart = new InputStreamBody(stream, 'application/octet-stream', 'test.file')
mpEntity.addPart('file', uploadFilePart)
mpEntity.addPart('userId', new StringBody('testUser'))
httppost.setEntity(mpEntity)
HttpResponse response = httpclient.execute(httppost);
println(response.statusLine)
}
Still getting 400 Bad Request in the response from the server.
I hate to answer my own question when it shows incompetence, but it turns out the code was fine, this particular controller did not have a CommonsMultipartResolver defined in its servlet-context.xml file (multiple DispatcherServlets...long story :()
Here's what I added to make it work:
<!-- ========================= Resolver DEFINITIONS ========================= -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes -->
<property name="maxUploadSize" value="50000000"/>
</bean>
Here is an example from the Spring Reference:
#Controller
public class FileUpoadController {
#RequestMapping(value = "/form", method = RequestMethod.POST)
public String handleFormUpload(#RequestParam("name") String name,
#RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
} else {
return "redirect:uploadFailure";
}
}
}

Resources