'Cannot get Spring Boot to lazily resolve a multipart file

I have created a Spring Boot 2 demo application with the Spring Initializr and added the controller below:

@Controller
@RequestMapping("/demo")
public class UploadController {
    private final static Logger LOG = LoggerFactory.getLogger(UploadController.class);

    @PostMapping("/upload")
    public ResponseEntity<String> uploadFile(
        @RequestParam("metadata") MultipartFile metadata,
        @RequestParam("payload") MultipartFile payload) throws IOException {

        ObjectMapper mapper = new ObjectMapper();
        Map metadataMap = mapper.readValue(metadata.getInputStream(), Map.class);
        LOG.info("Received call to upload file {}", metadataMap.get("filename"));
        LOG.info("File size: {}", payload.getBytes().length);
        LOG.info("File {} successfully uploaded", metadataMap.get("filename"));

        return ResponseEntity.ok().build();
    }

}  

I then added an application.yaml file containing this configuration:

    spring:
      servlet:
        multipart:
          max-file-size: 2000000MB
          max-request-size: 2000000MB  
          resolve-lazily: true

My goal is to have the controller parse and log the metadata file before it starts reading the payload file, but the resolve-lazily setting seems to be ignored by Boot: the code inside the controller won't be executed until the whole body is read.

I use the command below to test the controller:

curl -F [email protected] -F [email protected] http://localhost:8080/demo/upload

Is there anything wrong with my code/configuration? Am I getting the meaning of the setting right?



Solution 1:[1]

At present, if you want to avoid reading (and buffering) the whole body all at once, I think you will have to provide your own parser, as described in the answers here. What would be really interesting (but generally unnecessary) would be to do so in the form of a new MultipartResolver implementation.


There are two existing implementations documented for interface MultipartResolver, and both supply a function setResolveLazily(boolean) (standard), (commons). I have tried with both, and neither seem to allow for parsing or streaming multipart files or parameters independently.

Default is "false", resolving the multipart elements immediately, throwing corresponding exceptions at the time of the resolveMultipart(javax.servlet.http.HttpServletRequest) call. Switch this to "true" for lazy multipart parsing, throwing parse exceptions once the application attempts to obtain multipart files or parameters.

Despite what it says in the documentation, I have found that once you call resolveMultipart, the entire body is parsed and buffered before the call returns. I know this because I can watch the temp-files being created.


One note about "Is there anything wrong with my code"...

Answer: Yes, because by using @RequestParam you have indirectly asked Spring to resolve your parameters ahead of time, before your controller is ever called. What you should be able to do instead (if the documentation were correct) is request the parameters independently from inside your controller:

Configuration (application.properties):

spring.servlet.multipart.enabled = true
spring.servlet.multipart.resolve-lazily = true

Controller:

@PostMapping(path = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Void> postUpload(HttpServletRequest rawRequest) {

    multipartResolver.setResolveLazily(true); // unclear why this is exists
    MultipartHttpServletRequest request = multipartResolver.resolveMultipart(rawRequest);

    String p1 = request.getParameter("first-parameter");
    String p2 = request.getParameter("second-parameter");
    System.out.println("first-parameter="+p1+", second-parameter"+p2);

    multipartResolver.cleanupMultipart(request);
    return new ResponseEntity<Void>(HttpStatus.ACCEPTED);
}

One useful aspect of resolve-lazily that I have discovered is that it allows you to write your own parser for some rest controllers while using the built-in parser for others (see my answer here). In other words, you don't have to use spring.servlet.multipart.enabled = false to get your parser to work. This is a minor breakthrough relative to other advice that I had seen previously.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Brent Bradburn