Spring MVC series (14) - file upload and download

Media Type

When uploading files, you need to specify the media type. There are the following two types.

multipart/form-data

In HTML, the enctype attribute in the form specifies how the form data should be encoded before being sent to the server.

By default, the form data is encoded as "application/x-www-form-urlencoded". That is, all characters are encoded before being sent to the server (spaces are converted to "+" plus signs, and special symbols are converted to ASCII HEX values).

When the form uses POST request, the data will be encoded into the Body in x-www-urlencoded mode for transmission, and if the GET request is attached to the url link for transmission. GET requests only support ASCII character sets, so if we want to send the contents of a larger character set, we should use POST requests.

enctype has three configurable types:

valuedescribe
application/x-www-form-urlencodedEncode all characters before sending (default)
multipart/form-dataNo character encoding. This value must be used when using a form that contains a file upload control.
text/plainSpaces are converted to a "+" plus sign, but special characters are not encoded.

If you want to send a large amount of binary data (non ASCII), application/x-www-form-urlencoded is obviously inefficient because it needs three characters to represent a non ASCII character. Therefore, in this case, the "multipart / form data" format should be used.

Multipart / form data is defined in rfc2388. The earliest HTTP POST does not support file upload, which brings many problems to programming development. However, in 1995, ietf issued rfc1867, namely RFC 1867 - form based file upload in HTML, to support file upload. Therefore, the type of content type extends multipart / form data to support sending binary data to the server.

application/octet-stream

Application / octet stream is a kind of content type in HTTP specification and is rarely used.

Only one binary can be submitted, and only one binary can be submitted. If a file is submitted, only one file can be submitted. There can only be one background receiving parameter, and it can only be a stream (or byte array).

Spring MVC support for file upload

multipart package

Under the multipart package in the spring web module, many classes and interfaces are provided to handle multipart requests.

The commons package supports the commons file upload component in apache.

MultipartFile interface

The MultipartFile interface is the upload file object received in the multipart request.

MultipartFile defines some methods to obtain uploaded file information, stream and operation file.

public interface MultipartFile extends InputStreamSource {
		// Returns the name of the multipart form parameter
		String getName();
		// Returns the original file name in the client file system, which is provided by the client and should not be used blindly.
		// It is not recommended to use this file name directly. It is best to generate a unique and save it
		String getOriginalFilename();
		// Returns the ContentType of the file
		String getContentType();
		// Returns whether the uploaded file is empty, that is, there is no file
		boolean isEmpty();
		// Returns the size of the file in bytes. If it is empty, it returns 0
		long getSize();
		// Returns the contents of the file as a byte array. If it is empty, an empty byte array is returned and IOException is thrown when an access error occurs
		byte[] getBytes() throws IOException;
		// Read the file content and return to InputStream. The user is responsible for closing the returned stream
		// If it is empty, it returns an empty stream and throws an IOException when accessing an error
		InputStream getInputStream() throws IOException;
		// Returns the Resource representation of this multipart file.
		default Resource getResource() {
		return new MultipartFileResource(this);
		// Transfer the received file to a given destination file, which can move the file in the file system
		// Or save the contents in memory to the target file. If the target file already exists, it will be deleted first.
		// 
		void transferTo(File dest) throws IOException, IllegalStateException;
		// Transfer the received files to the given destination folder
		default void transferTo(Path dest) throws IOException, IllegalStateException {
		FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest));
	}
}

MultipartResolver interface

Multipart resolver is used to parse multipart requests including file upload.

Two methods are supported:

  • Implementation based on Commons FileUpload.
  • Multipart request parsing based on servlet 3.0.

MultipartResolver defines three methods:

public interface MultipartResolver {
	// Determines whether the given request contains multipart content
	boolean isMultipart(HttpServletRequest request);
	// Parse the given HTTP request into multipart files and parameters, and wrap the request in multipartttpServletRequest.
	MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
	// Clean up all resources for multipart processing
	void cleanupMultipart(MultipartHttpServletRequest request);
}

MultipartResolver has two implementation classes, CommonsMultipartResolver and StandardServletMultipartResolver.

CommonsMultipartResolver supports the Apache Commons FileUpload component.

StandardServletMultipartResolver is the standard implementation of multipartresolver interface. It is based on Servlet3.0. When used, it should be added to the spring dispatcher servlet context as a bean of "multipartresolver" type (spring boot has been configured by default).

public class StandardServletMultipartResolver implements MultipartResolver {
	// Whether to delay the resolution of multi part requests during execution. The default value is "false". Multi part elements are resolved immediately.
	private boolean resolveLazily = false;
	public void setResolveLazily(boolean resolveLazily) {
		this.resolveLazily = resolveLazily;
	}
	// Check whether the request ContentType contains multipart
	@Override
	public boolean isMultipart(HttpServletRequest request) {
		return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
	}
	// Resolve Multipart to MultipartHttpServletRequest 
	@Override
	public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
		return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
	}
	// clear
	@Override
	public void cleanupMultipart(MultipartHttpServletRequest request) {
	// others....
	}
}

MultipartRequest interface

This interface defines multipart request object access operations.

public interface MultipartRequest {
	// Returns a {@ link java.util.Iterator} string object containing
	// Parameter name of the multipart file contained in this request. These are the field names of the form (similar to normal parameters)				   		
	//Instead of the original file name.
	Iterator<String> getFileNames();
	// Returns the MultipartFile object of the uploaded file in this request according to the
	MultipartFile getFile(String name);
	// Returns a collection of MultipartFile objects that upload files in this request based on
	List<MultipartFile> getFiles(String name);
	// Returns {@ link java.util.Map} of the multipart file contained in this request.
	// @Returns a map containing the parameter name as the key and the {@ link MultipartFile} object as the value
	Map<String, MultipartFile> getFileMap();
	// Return to MultiValueMap
	MultiValueMap<String, MultipartFile> getMultiFileMap();
	// Return content type
	String getMultipartContentType(String paramOrFileName);
}

MultipartRequest has the following implementation classes:

DefaultMultipartHttpServletRequest is the default implementation of MultipartHttpServletRequest and provides relevant methods for generating parameter values.

StandardMultipartHttpServletRequest wrapper Servlet 3.0 HttpServletRequest
And some of its objects. Parameters are exposed through the requested getParameter.

Upload file case

Method 1. Use fileupload

  1. Add a thymeleaf upload file page.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="zh">
<head>
    <meta charset="UTF-8">
    <title>file management</title>
</head>
<body>
<form name="form1" method="post" action="/file/common/upload" enctype="multipart/form-data">
    Select file: <input type="file" name="file">
    <input type="submit" value="Click upload"/>
</form>
</body>
</html>

Add a controller to jump to the thymeleaf page.

    @GetMapping("/commonView")
    public Object commonView() {
        return "file/file";
    }
  1. Add the fileupload toolkit.
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>
  1. Add a processor method to handle the logic of uploading files.
    @PostMapping("/common/upload")
    @ResponseBody
    public Object commonUpload(@RequestParam CommonsMultipartFile file) throws IOException {
        if (file == null || file.isEmpty()) {
            return "File cannot be empty";
        }
        try {
            // 1. Create target folder
            File dir = new File("D:\\tmp" + File.separator + "files");
            if (!dir.exists()) {
                // Create when folder does not exist
                dir.mkdirs();
            }
            // 2. Obtain file information
            String contentType = file.getContentType();
            System.out.println("contentType========" + contentType);
            String name = file.getName();
            System.out.println("name========" + name);
            String originalFilename = file.getOriginalFilename();
            System.out.println("originalFilename========" + originalFilename);
            long size = file.getSize();
            System.out.println("size========" + size);
            Resource resource = file.getResource();
            System.out.println("resource========" + resource.toString());
            String storageDescription = file.getStorageDescription();
            System.out.println("storageDescription========" + storageDescription);
            // 3. Create and save file objects
            String storeFileName = UUID.randomUUID() + "_"+originalFilename; // The file name adopts UUID
            File storeFile = new File(dir.getAbsolutePath() + File.separator + storeFileName);
            file.transferTo(storeFile);
        } catch (Exception e) {
            e.printStackTrace();
            return "Upload failed";
        }
        return "success";
    }
  1. Inject CommonsMultipartResolver parser.
    @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
    @ConditionalOnMissingBean(MultipartResolver.class)
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setResolveLazily(false);
        return multipartResolver;
    }
  1. test

Select the file, click upload, and return success, indicating that the upload is successful. Go to the target folder and find the uploaded file

Method 2. Upload using Spring Multipart parser

  1. Note CommonsMultipartResolver. Spring boot has automatically injected StandardServletMultipartResolver.

  2. Write a controller method. Except that the access path is different from the input parameter object, the other processing logic is the same as above.

    @PostMapping("/spring/upload")
    @ResponseBody
    public Object springUpload(@RequestParam MultipartFile file) throws IOException {
	// Omit...
    }
  1. Add an upload file form to the page.
<form name="form1" method="post" action="/file/spring/upload" enctype="multipart/form-data">
    Select file form1: <input type="file" name="file">
    <input type="submit" value="Click upload"/>
</form>
  1. Test, same as above.

File download case

When downloading files, we only need to write the stream into the response message.

  1. Create a download page
<form name="form3" method="post" action="/file/download" enctype="application/x-www-form-urlencoded">
    Enter the full path of the file: <input type="text" name="filePath">
    <input type="submit" value="Download File"/>
</form>
  1. Create a download controller, take the stream directly, and then put it into the response.
    @PostMapping("/download")
    public ResponseEntity<?> download(@RequestParam String filePath) {
        try (InputStream in = new FileInputStream(filePath)) {
            // Get file stream
            byte[] body = new byte[in.available()];
            in.read(body); // Write to byte array
            String fileName = filePath.substring(filePath.lastIndexOf("\\") + 1); // Intercepted file name
            MultiValueMap<String, String> headers = new HttpHeaders(); // Write file information to message header
            headers.add("Content-Disposition", "attachment;filename=" + fileName);
            return new ResponseEntity<>(body, headers, HttpStatus.OK);
        } catch (FileNotFoundException exception) {
            exception.printStackTrace();
            return new ResponseEntity<>("The server FileNotFoundException", HttpStatus.INTERNAL_SERVER_ERROR);

        } catch (IOException e) {
            e.printStackTrace();
            return new ResponseEntity<>("The server IOException", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
  1. test

Tags: Spring Spring Cloud http mvc

Posted on Sun, 19 Sep 2021 05:25:46 -0400 by littlehelp