jquery file upload with spring mvc : 400 bad request

maxime picture maxime · Aug 21, 2012 · Viewed 11.7k times · Source

I coded a file upload with SpringMVC, following the manual example (LINK) and it works fine.

Then, I changed the client to use jQuery file upload
http://blueimp.github.com/jQuery-File-Upload/
in order to have a more powerful client (with drag and drop).
I downloaded the widget and implemented a jsp with it.
But it does not work. The browser receives a response 400 (bad request) and no exception is throw with tomcat7.

I saw a difference in the request: with the working spring mvc doc exemple the request look like (firebug) :

-----------------------------26989305212543489571047641949 Content-Disposition: form-data; name="name"
-----------------------------26989305212543489571047641949 Content-Disposition: form-data; name="file"; filename="content.txt"
Content-Type: text/plain hello
-----------------------------26989305212543489571047641949--

and for the non working jsp with jQuery fileupload default implementation the request in firebug looks like :

-----------------------------205805102810633831841397435868 Content-Disposition: form-data; name="files"; filename="content.txt" Content-Type: text/plain hello
-----------------------------205805102810633831841397435868--

So, with the jQuery non working version, more is sent to the server. Some kind of extra meta-data. Is that the problem or am i searching in the wrong way? I’m no expert at all in file upload / multipart.

Thanks for your help.

Spring v3.1.2
Tomcat v7.0
jQuery 1.8
The project use sitemesh (I don't think it makes a difference but...)

The controller code

import java.io.IOException;

import org.springframework.stereotype.Controller;
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.multipart.MultipartFile;

@Controller
public class FileUploadController {

    @RequestMapping(value = "/form", method = RequestMethod.POST)
    public String handleFormUpload(@RequestParam(value="name",required=false) String  name,
       @RequestParam("file") MultipartFile file) throws IOException {

       if (!file.isEmpty()) {
           byte[] bytes = file.getBytes();
           // store the bytes somewhere
          return "redirect:home";
      } else {
          return "redirect:uploadFailure";
      }
       System.out.println(file.length);
       return "home";
    }

}

working jsp:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<html>
    <head>
       <title>Upload a file please</title>
    </head>
    <body>
       <h1>Please upload a file</h1>
       <form method="post" action="form" enctype="multipart/form-data">
           <input type="text" name="name"/>
           <input type="file" name="file"/>
           <input type="submit"/>
       </form>
    </body>
</html>

Non working jsp (with jQuery) :

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator"

    prefix="decorator"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html >
<html>
<head>
<link rel="stylesheet" type="text/css" href="/ReformYourCountry/css/test-style.css" />
<link rel="stylesheet" type="text/css" href="/ReformYourCountry/css/template.css" />
<link rel="stylesheet" type="text/css" href="/ReformYourCountry/css/content.css" />
<!-- Shim to make HTML5 elements usable in older Internet Explorer versions -->
<!--[if lt IE 9]><script src="http://html5shim.googlecode.com/svn/trunk/html5.js">  </script><![endif]-->



<!-- for imageupload page -->

<!-- Force latest IE rendering engine or ChromeFrame if installed -->
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><![endif]-->
<meta charset="utf-8">
<title>jQuery File Upload Demo</title>
<meta name="description" content="File Upload widget with multiple file selection, drag&amp;drop support, progress bar and preview images for jQuery. Supports cross-domain, chunked and resumable file uploads. Works with any server-side platform (Google App Engine, PHP, Python, Ruby on Rails, Java, etc.) that supports standard HTML form file uploads.">
<meta name="viewport" content="width=device-width">
<!-- jQuery UI styles -->
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/base/jquery-ui.css" id="theme">
<!-- jQuery Image Gallery styles -->
<link rel="stylesheet" href="http://blueimp.github.com/jQuery-Image-Gallery/css/jquery.image-gallery.min.css">
<!-- CSS to style the file input field as button and adjust the jQuery UI progress bars -->
<link rel="stylesheet" href="css/jquery.fileupload-ui.css">
<!-- Generic page styles -->
<link rel="stylesheet" href="css/style.css">
<!-- Shim to make HTML5 elements usable in older Internet Explorer versions -->
<!--[if lt IE 9]><script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->



<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>

<body>
    <!-- The file upload form used as target for the file upload widget -->
    <form id="fileupload" action="form" method="POST" enctype="multipart/form-data">
       <!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload -->
       <div class="row fileupload-buttonbar">
           <div class="span7">
               <!-- The fileinput-button span is used to style the file input field as button -->
               <span class="btn btn-success fileinput-button">
                   <i class="icon-plus icon-white"></i>
                   <span>Add files...</span>
                   <input type="file" name="files" multiple>
               </span>
               <button type="submit" class="btn btn-primary start">
                   <i class="icon-upload icon-white"></i>
                   <span>Start upload</span>
               </button>
               <button type="reset" class="btn btn-warning cancel">
                   <i class="icon-ban-circle icon-white"></i>
                   <span>Cancel upload</span>
               </button>
               <button type="button" class="btn btn-danger delete">
                   <i class="icon-trash icon-white"></i>
                   <span>Delete</span>
               </button>
               <input type="checkbox" class="toggle">
           </div>
           <!-- The global progress information -->
           <div class="span5 fileupload-progress fade">
               <!-- The global progress bar -->
               <div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100">
                   <div class="bar" style="width:0%;"></div>
               </div>
               <!-- The extended global progress information -->
               <div class="progress-extended">&nbsp;</div>
           </div>
       </div>
       <!-- The loading indicator is shown during file processing -->
       <div class="fileupload-loading"></div>
       <br>
       <!-- The table listing the files available for upload/download -->
       <table role="presentation" class="table table-striped"><tbody class="files" data-toggle="modal-gallery" data-target="#modal-gallery"></tbody></table>
    </form>
<!-- The template to display files available for upload -->
<script id="template-upload" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
    <tr class="template-upload fade">
       <td class="preview"><span class="fade"></span></td>
       <td class="name"><span>{%=file.name%}</span></td>
       <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
       {% if (file.error) { %}
           <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
       {% } else if (o.files.valid && !i) { %}
           <td>
               <div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="bar" style="width:0%;"></div></div>
           </td>
           <td class="start">{% if (!o.options.autoUpload) { %}
               <button class="btn btn-primary">
                   <i class="icon-upload icon-white"></i>
                   <span>{%=locale.fileupload.start%}</span>
               </button>
           {% } %}</td>
       {% } else { %}
           <td colspan="2"></td>
       {% } %}
       <td class="cancel">{% if (!i) { %}
           <button class="btn btn-warning">
               <i class="icon-ban-circle icon-white"></i>
               <span>{%=locale.fileupload.cancel%}</span>
           </button>
       {% } %}</td>
    </tr>
{% } %}
</script>
<!-- The template to display files available for download -->
<script id="template-download" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
    <tr class="template-download fade">
       {% if (file.error) { %}
           <td></td>
           <td class="name"><span>{%=file.name%}</span></td>
           <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
           <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
       {% } else { %}
           <td class="preview">{% if (file.thumbnail_url) { %}
               <a href="{%=file.url%}" title="{%=file.name%}" rel="gallery" download="{%=file.name%}"><img src="{%=file.thumbnail_url%}"></a>
           {% } %}</td>
           <td class="name">
               <a href="{%=file.url%}" title="{%=file.name%}" rel="{%=file.thumbnail_url&&'gallery'%}" download="{%=file.name%}">{%=file.name%}</a>
           </td>
           <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
           <td colspan="2"></td>
       {% } %}
       <td class="delete">
           <button class="btn btn-danger" data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}">
               <i class="icon-trash icon-white"></i>
               <span>{%=locale.fileupload.destroy%}</span>
           </button>
           <input type="checkbox" name="delete" value="1">
       </td>
    </tr>
{% } %}
</script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js"></script>
<!-- The Templates plugin is included to render the upload/download listings -->
<script src="http://blueimp.github.com/JavaScript-Templates/tmpl.min.js"></script>
<!-- The Load Image plugin is included for the preview images and image resizing functionality -->
<script src="http://blueimp.github.com/JavaScript-Load-Image/load-image.min.js"></script>
<!-- The Canvas to Blob plugin is included for image resizing functionality -->
<script src="http://blueimp.github.com/JavaScript-Canvas-to-Blob/canvas-to-blob.min.js"></script>
<!-- jQuery Image Gallery -->
<script src="http://blueimp.github.com/jQuery-Image-Gallery/js/jquery.image-gallery.min.js"></script>
<!-- The Iframe Transport is required for browsers without support for XHR file uploads -->
<script src="js/jquery.iframe-transport.js"></script>
<!-- The basic File Upload plugin -->
<script src="js/jquery.fileupload.js"></script>
<!-- The File Upload file processing plugin -->
<script src="js/jquery.fileupload-fp.js"></script>
<!-- The File Upload user interface plugin -->
<script src="js/jquery.fileupload-ui.js"></script>
<!-- The File Upload jQuery UI plugin -->
<script src="js/jquery.fileupload-jui.js"></script>
<!-- The localization script -->
<script src="js/locale.js"></script>
<!-- The main application script -->
<script src="js/main.js"></script>
<!-- The XDomainRequest Transport is included for cross-domain file deletion for IE8+ -->
<!--[if gte IE 8]><script src="js/cors/jquery.xdr-transport.js"></script><![endif]-->
</body>
</html>

Answer

Jer picture Jer · Aug 23, 2012

I've used the jQuery File Upload widget with Spring MVC with controller code like the following (note there is a dependency on apache commons fileupload for handing the multipart content):

@RequestMapping(value="/project/file", method=POST) 
public void addFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (!ServletFileUpload.isMultipartContent(request)) {
            throw new IllegalArgumentException("Request is not multipart, please 'multipart/form-data' enctype for your form.");
        }
        ServletFileUpload uploadHandler = new ServletFileUpload(new DiskFileItemFactory());

        try {
            List<FileItem> items = uploadHandler.parseRequest(request);
            for (FileItem item : items) {
                if (!item.isFormField()) {
                    byte [] fileBytes = item.get();
                }
         } catch(FileUploadException e) {
             throw new RuntimeException(e);
         }

   }