Search code examples
file-uploadparametersspring-boothttp-status-code-400bad-request

Spring Boot File Upload Bad Request 400


Hello im writing an Webapplication using Spring Boot and AngularJs and need an simple file upload which is not working at the moment.

I already read that spring boot should autoconfigure the multipart upload itself when the mvc dependency is present. From :https://spring.io/guides/gs/uploading-files/

As part of auto-configuring Spring MVC, Spring Boot will create a MultipartConfigElement bean and make itself ready for file uploads.

Javascript Function which sends the Request:

var postFormData = function (file, url, successCallback, errorCallback, progressCallback) {
    var xhr = new XMLHttpRequest(),
        formData = new FormData();

    formData.append("file", file);

    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                successCallback(xhr.status); //File is Uploaded
            } else {
                errorCallback(xhr.status);
            }
        }
    };

    function progress(evt) {
        if (evt.lengthComputable) {
            var percentComplete = evt.loaded / evt.total;
            progressCallback(percentComplete);
        } else {
            progressCallback(evt.loaded);
        }
    }

    xhr.open("POST", url, true);

    //xhr.setRequestHeader("Content-Type", "multipart/form-data");

    xhr.addEventListener("progress", progress, false);

    xhr.send(formData);
}

Multipart configuration Bean in my main app class:

 @Bean
    MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setMaxFileSize(MAXIMUM_FILE_SIZE);
        factory.setMaxRequestSize(MAXIMUM_FILE_SIZE);
        return factory.createMultipartConfig();
    }

Upload Controller:

@Controller
@RequestMapping("/api")
public class FileUploadController {

        @Autowired
        UserRepository userRepository;

        @Autowired
        VolumeMetaRepository volumeMetaRepository;

        /**
         * Handles File upload of volumedata
         *
         * @param file Must not be null
         **/
        @RequestMapping(value = "/volumedata/meta/test", consumes= "multipart/form-data", method=RequestMethod.POST)
        @ResponseBody
        public void handleFileUpload(
                @RequestParam("file") MultipartFile file) {

            if (!file.isEmpty()) {
               /* try {
                    InputStream fileStream = file.getInputStream();
                    OutputStream out = new FileOutputStream(file.getOriginalFilename());
                    IOUtils.copy(fileStream, out);
                } catch (IOException e) {
                    throw new IllegalStateException("Error uploading file.", e);
                }*/

/*                String filePath = request.getServletContext().getRealPath("/");
                try {
                    file.transferTo(new File(filePath+ "/"  + file.getOriginalFilename()));
                } catch (IOException e) {
                    e.printStackTrace();
                }*/
            }
        }

And because there are other Stackoverflow Threads were a new Spring boot Version solved the issue Im using '1.2.2.BUILD-SNAPSHOT', my gradle dependencies:

version = '1.2.2.BUILD-SNAPSHOT'
dependencies {
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version:version
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version:version
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version:version
    compile group: 'org.springframework.hateoas', name: 'spring-hateoas', version:'0.16.0.RELEASE'
    compile group: 'org.springframework.data', name: 'spring-data-rest-webmvc', version:'2.1.4.RELEASE'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version:version
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-rest', version:version
    compile group: 'commons-io', name: 'commons-io', version:'2.1'
    compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version:'2.3.4'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-integration', version:version
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-websocket', version:version
    compile group: 'org.springframework.session', name: 'spring-session', version:'1.0.0.BUILD-SNAPSHOT'
    compile group: 'org.springframework.data', name: 'spring-data-redis', version:'1.4.1.RELEASE'
    compile group: 'redis.clients', name: 'jedis', version:'2.4.2'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-remote-shell', version:version
    compile group:'com.google.guava', name:'guava', version:'18.0'

    compile files ('lib/vendor/niftijio.jar');

    compile("com.h2database:h2")

    testCompile(group: 'org.springframework.boot', name: 'spring-boot-starter-test', version:'1.1.6.RELEASE') {
        exclude(module: 'commons-logging')
    }

    testCompile group: 'com.jayway.jsonpath', name: 'json-path', version:'0.9.1'
    testCompile 'org.springframework:spring-test:3.2.3.RELEASE'
    testCompile 'junit:junit:4.+'
    testCompile "org.mockito:mockito-all:1.9.5"
}

The thrown error is:

{
  "timestamp" : "2015-01-19T10:22:53.710Z",
  "status" : 400,
  "error" : "Bad Request",
  "exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
  "message" : "Required MultipartFile parameter 'file' is not present",
  "path" : "/api/volumedata/meta/test"
}

It tells me that the "file" parameter is not present, but my Request payload shows that the parameter is there.

Request Payload:
------WebKitFormBoundary40qPAhpvA20pd8V1
Content-Disposition: form-data; name="file"

C:\fakepath\test.gz
------WebKitFormBoundary40qPAhpvA20pd8V1--

Has somebody an Idea what is missing in my configuration, or what could cause the error ?

Thanks in advance


Solution

  • I found the reason for the error. It originated not from my Spring controller, but from my angularJS html code.

    This was my Upload Input field:

     <div class="form-group"
                     ng-class="{ 'has-error': volume_upload_form.file_field.$invalid && volume_upload_form.file_field.$dirty}">
                    <label name=file-field-label>Volume Dataset</label>
                    <input value=file
                           name=file
                           ng-model=upload.file
                           required
                           id=file
                           file_ext_validation
                           type="file"
                           extensions="nii,NII,gz,jpeg,JPG"/>
    
                    <div class="error"
                         ng-show="volume_upload_form.volume.$invalid">
    
                        <small class="error"
                               ng-show="volume_upload_form.volume.$error.fileExtValidation">
                            File must be of type ".nii"
                        </small>
                    </div>
                </div>
    

    As you can see is use the default ng-model too communicate with my angular controller("upload" is my Controller alias).

    ng-model=upload.file
    

    But Angular does not support the file input html field by default. So only a string containing the file path was stored in upload.file, NOT an actual File Object. I had to write a custom directive:

    var fileModel = function ($parse) {
        return {
            restrict: 'A',
            link: function(scope, element, attrs) {
                var model = $parse(attrs.fileModel);
                var modelSetter = model.assign;
    
                element.bind('change', function () {
                    scope.$apply(function () {
                        modelSetter(scope, element[0].files[0]);
                    });
                });
            }
        };
    }
    
    module.exports = fileModel;
    

    Which returns the actual File object. My new upload html code was as follows:

    <div class="form-group"
                     ng-class="{ 'has-error': volume_upload_form.file_field.$invalid && volume_upload_form.file_field.$dirty}">
                    <label name=file-field-label>Volume Dataset</label>
                    <input name=file
                           file-model="upload.fileModel"
                           ng-model="file"
                           required
                           file_ext_validation
                           type="file"
                           extensions="nii,NII,gz,jpeg,JPG"/>
    
                    <div class="error"
                         ng-show="volume_upload_form.volume.$invalid">
    
                        <small class="error"
                               ng-show="volume_upload_form.volume.$error.fileExtValidation">
                            File must be of type ".nii"
                        </small>
                    </div>
                </div>
    

    you can see

    file-model="upload.fileModel"
    

    links the File Object from the file input field to the angular controller. Here is the correct Request Payload.

    ------WebKitFormBoundaryR1AXAwLepSUKJB3i
    Content-Disposition: form-data; name="file"; filename="test.nii.gz"
    Content-Type: application/x-gzip
    
    
    ------WebKitFormBoundaryR1AXAwLepSUKJB3i--