JavaWeb, springMVC, springboot file upload

As we all know, if our form submission contains a file, the value of the enctype attribute of our form must be multipart / form data.

Here, we use the data in w3cschool for explanation, and put a portal here https://www.w3school.com.cn/tags/att_form_enctype.asp

And it is worth noting that if we want to submit documents, the submission method must be post.

Here, I think the reason why post must be used is that the data that can be submitted by post request is much larger than that of get, and the size of the file is much larger than the size limit that get can accommodate. I haven't found a specific argument for this sentence, so it's not necessarily true. It's for reference only

With the pre understanding, let's get to the point

Java web file upload

(1) Page form submission

<form action="/upload" method="post" enctype="multipart/form-data">
        <input type="text" name="username">
        <input type="password" name="pwd">
        <input type="file" name="pic">
        <input type="submit">
    </form>

(2) Receive file stream UploadServlet:

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        InputStream is = request.getInputStream();
        // Set the buffer to read the bytes in the character input stream
        byte[] bytes = new byte[2048];
        is.read(bytes);
        System.out.println(new String(bytes));
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

Then look at what you read

In fact, this is the content in the request body. We can view it through the browser's own packet capture tool
The content is the same. In fact, when we submit, we stream the content in the form to the background, and then we can operate on the stream. For example, if we want to upload files, we output the stream to a place on our server, and when we write our own projects, This place may be the disk on your computer

Print the console and look back

------WebKitFormBoundaryzRSPAU9UKnMzdAUZ – is a separator used to separate each field of the form. This is the same as that in the packet capture tool
The function is the same, in order to split different fields in the form

At this point, if we use request Getparamert (string a) this method can't get the parameters we want, because this method gets the character content. When using the submission method of file upload, it is a byte content and a byte stream

At this time, if we want to get the content in the byte stream, for example, when we want to get the value with name pwd, we need to deal with it ourselves, which is very troublesome. Therefore, we need to import other people's classes to simplify the operation of our developers

Here we use a dependency under apache to deal with this problem

	<dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>

fileupload overview

fileupload is an upload component provided by the commons component of apache. Its main job is to help us parse the request getInputStream()

The JAR packages required by the fileupload component include:

  1. commons-fileupload.jar, core package
  2. commons-io.jar, dependent package

The core classes of fileupload include DiskFileItemFactory, ServletFileUpload and FileItem

//1. Create a factory class DiskFileItemFactory object
DiskFileItemFactory factory = new DiskFileItemFactory();

//2. Create parser object using factory
ServletFileUpload fileUpload = new ServletFileUpload(factory);

//3. Use the parser to parse the request object
List<FileItem> list = fileUpload.parseRequest(request);

DiskFileItemFactory disk file item factory class

  1. public DiskFileItemFactory(int sizeThreshold, File repository) specifies the memory buffer size and temporary file storage location when constructing the factory
  2. public void setSizeThreshold(int sizeThreshold) sets the memory buffer size, which is 10K by default
  3. public void setRepository(File repository) sets the storage location of temporary files. The default is system getProperty(“java.io.tmpdir”). Memory buffer: when uploading a file, the contents of the uploaded file are preferentially saved in the memory buffer. When the size of the uploaded file exceeds the size of the buffer, a temporary file will be generated on the server. Storage location of temporary files: temporary files generated by saving uploaded files that exceed the size of memory buffer can be deleted through the delete() method of FileItem

FileItem represents each data part in the file upload form
Introduce the FileItem class grandly, which is the final result we want. A FileItem object corresponds to a form item (form field). There are file fields and common fields in a form. You can use the isFormField() method of FileItem class to judge whether the form field is a common field. If it is not a common field, it is a file field
Note: because the file upload form adopts the encoding method of multipart / form data, which is different from the traditional url encoding, setCharacterEncoding() cannot be used in all getParameter() methods, and the problem of input item garbled code cannot be solved

ServletFileUpload file upload core class

Example demonstration file upload

(1) First step
Complete index JSP, only one form is required. Note that the form must be post, and the enctype must be mulitpart / form data

	<form action="/upload" method="post" enctype="multipart/form-data">
        <input type="text" name="username">
        <input type="password" name="pwd">
        <input type="file" name="pic">
        <input type="submit">
    </form>

(2) Step two
Completing FileUploadServlet

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // Because you want to print with response, set its encoding
        response.setContentType("text/html;charset=utf-8");

        // Create factory
        DiskFileItemFactory dfif = new DiskFileItemFactory();
        // Create parser objects using factories
        ServletFileUpload fileUpload = new ServletFileUpload(dfif);
        try {
            // Use the parser object to parse the request and get the FileItem list
            List<FileItem> list = fileUpload.parseRequest(request);
            // Traverse all form items
            for(FileItem fileItem : list) {
                // If the current form item is a normal form item
                if(fileItem.isFormField()) {
                    // Gets the field name of the current form item
                    String fieldName = fileItem.getFieldName();
                    // If the field name of the current form item is username
                    if(fieldName.equals("username")) {
                        // Print the content of the current form item, that is, the content entered by the user in the username form item
                        response.getWriter().print("user name:" + fileItem.getString() + "<br/>");
                    }
                } else {
                    //If the current form item is not an ordinary form item, the description is a file field
                    //Get the name of the uploaded file
                    String name = fileItem.getName();
                    // If the name of the uploaded file is empty, the uploaded file is not specified
                    if(name == null || name.isEmpty()) {
                        continue;
                    }
                    // Get the real path, corresponding to ${project directory} / uploads. Of course, this directory must exist
                    String savepath = this.getServletContext().getRealPath("/uploads");
                    // Create a File object by uploads directory and File name
                    File file = new File(savepath, name);
                    // Save the uploaded file to the specified location
                    fileItem.write(file);
                    // Print the name of the uploaded file
                    response.getWriter().print("Upload file name:" + name + "<br/>");
                    // Print the size of the uploaded file
                    response.getWriter().print("Upload file size:" + fileItem.getSize() + "<br/>");
                    // Print the type of uploaded file
                    response.getWriter().print("Upload file type:" + fileItem.getContentType() + "<br/>");
                }
            }
        } catch (Exception e) {
            throw new ServletException(e);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

File upload details:

  1. Put the uploaded files in the WEB-INF Directory:
    If the file uploaded by the user is not stored in the WEB-INF directory, the user can directly access the uploaded file through the browser, which is very dangerous.
    Usually, we will create an uploads directory under the WEB-INF directory to store the uploaded files. To find this directory in the Servlet, we need to use the getRealPath(String) method of ServletContext. For example, in my upload1 project, there are the following statements:
ServletContext servletContext = this.getServletContext();
String savepath = servletContext.getRealPath("/WEB-INF/uploads");

Here, getRealPath doesn't know what's going on. Put a portal

https://blog.csdn.net/ccmm_/article/details/79113744

  1. File name (full path, file name):
    The name of the uploaded file may be the full path:
    The upload file name obtained by IE6 is the full path, while the upload file name obtained by other browsers is just the file name. We still need to deal with the problem of browser differences
String name = file1FileItem.getName();
response.getWriter().print(name);

Using different browsers for testing, IE6 will return the full path of uploaded files. I don't know what IE6 is doing, which brings us a lot of trouble. We need to deal with this problem.
It is also very simple to deal with this problem. Whether it is a complete path or not, we can intercept the content after the last "\"

String name = file1FileItem.getName();
int lastIndex = name.lastIndexOf("\\");//Gets the location of the last '\'
if(lastIndex != -1) {//Note that there will be no "\" if it is not a full path.
name = name.substring(lastIndex + 1);//Get file name
}
response.getWriter().print(name);

I originally intended to verify here, but I made 500 errors when uploading files with my ie browser, and other browsers can work normally, so the above conclusion has not been verified by myself

  1. Chinese garbled Code:
    The name of the uploaded file contains Chinese:
    When the uploaded name contains Chinese, you need to set the code. The Commons fileUpload component provides us with two ways to set the code:
   request.setCharacterEncoding(String): This is the way we are most familiar with
   fileUpload.setHeaderEncdoing(String): This method has a higher priority than the previous one

The contents of the uploaded file include Chinese:
Usually we don't need to care about the content of the uploaded file, because we will save the uploaded file to the hard disk! In other words, what is the original appearance of the file? What is it like to go to the server!
However, if you need to display the uploaded file content on the console, you can use fileitem GetString ("utf-8") to handle encoding
Text file content and ordinary form item content use getString("utf-8") of FileItem class to process encoding.

  1. Upload file with the same name (file rename):
    Usually, we will save the files uploaded by the user to the uploads directory, but what if the user uploads a file with the same name? This will lead to coverage. The way to deal with this problem is to use UUID to generate a unique name, and then use "" Original name of connection file upload
    For example, the file uploaded by the user is "my one inch photo. jpg". After processing, the file name is "891b3881395f4175b969256a3f7b6e10# my one inch photo. jpg". This method will not cause the file to lose the extension, and because of the uniqueness of UUID, the uploaded file has the same name, but there will be no problem of the same name on the server
public void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 request.setCharacterEncoding("utf-8");
 DiskFileItemFactory dfif = new DiskFileItemFactory();
 ServletFileUpload fileUpload = new ServletFileUpload(dfif);
 try {
 List<FileItem> list = fileUpload.parseRequest(request);
 //Get the second form item, because the first form item is username and the second is file
 FileItem fileItem = list.get(1);
 String name = fileItem.getName();//Get file name

 // If the client uses IE6, you need to get the file name from the full path
 int lastIndex = name.lastIndexOf("\\");
 if(lastIndex != -1) {
 name = name.substring(lastIndex + 1);
 }

 // Get the save directory of the uploaded file
 String savepath = this.getServletContext().getRealPath("/WEB-INF/uploads");
 String uuid = CommonUtils.uuid();//Generate uuid
 String filename = uuid + "_" + name;//The new file name is uuid + underline + original name

 //Create a file object and save the uploaded file to the path specified by the file
 //savepath is the saving directory of the uploaded file
 //filename, file name
 File file = new File(savepath, filename);

 // Save file
 fileItem.write(file);
 } catch (Exception e) {
 throw new ServletException(e);
 } 
 }

  1. One directory cannot store too many files (the storage directory is scattered)
    A directory should not store too many files. Generally, 1000 files in a directory is the upper limit. If there are too many files, it will be "stuck" when opening the directory. You can try to print the C:\WINDOWS\system32 directory, and you will feel it
    In other words, we need to put the uploaded files in different directories. However, you can't have a directory for each uploaded file. This method will lead to too many directories. So we should use some algorithm to "break up"!
    There are many ways to break up, such as using dates to break up and generating a directory every day. You can also use the first letter of the file name to generate a directory, and the files with the same first letter are placed in the same directory.
    Date disaggregation algorithm: if too many files are uploaded on a certain day, there will also be too many directory files;
    Initial scattering algorithm: if the file name is Chinese, too many Chinese will lead to too many directories.
    Here we use hash algorithm to break up:

    Gets the name of the file hashCode: int hCode = name.hashCode()
    obtain hCode The lower 4 bits of are then converted to hexadecimal characters
     obtain hCode 5 of~8 Bit and then converted to hexadecimal characters
     Use these two hexadecimal characters to generate a directory chain. For example, the lower 4 characters are "5"
    

The advantage of this algorithm is that up to 16 directories are generated in the uploads directory, and up to 16 directories are generated in each directory, that is, 256 directories. All uploaded files are placed in these 256 directories. If the maximum number of files per directory is 1000, a total of 256000 files can be saved
For example, the name of the uploaded file is: new text document txt, then obtain the hash code of "new text document. txt", and then obtain the lower 4 bits and 5 ~ 8 bits of the hash code. If the lower 4 bits are 9 and the 5 ~ 8 bits are 1, the file saving path is uploads/9/1/

int hCode = name.hashCode();//Get hashCode of file name
//Get the lower 4 bits of hCode and convert it into hexadecimal string
String dir1 = Integer.toHexString(hCode & 0xF);
//Get the lower 5 ~ 8 bits of hCode and convert it into hexadecimal string
String dir2 = Integer.toHexString(hCode >>> 4 & 0xF);
//Connect with the file saving directory to form a full path
savepath = savepath + "/" + dir1 + "/" + dir2;
//Because this path may not exist, create a File object and then create a directory chain to ensure that the directory already exists before saving the File
new File(savepath).mkdirs();

  1. Size limit of single file uploaded
    It's easy to limit the size of the uploaded file, just setFileSizeMax(long) of the ServletFileUpload class. Parameter is the maximum number of bytes of the uploaded file, such as ServletFileUpload Setfilesizemax (1024 * 10) indicates that the upper limit is 10KB.
    Once the uploaded file exceeds the upper limit, fileuploadbase.exe will be thrown Filesizelimitexceededexception exception. We can get this exception in the Servlet, and then output "the uploaded file exceeds the limit" to the page.
public void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 request.setCharacterEncoding("utf-8");
 DiskFileItemFactory dfif = new DiskFileItemFactory();
 ServletFileUpload fileUpload = new ServletFileUpload(dfif);
 // Set the upper limit of a single file uploaded to 10KB
 fileUpload.setFileSizeMax(1024 * 10);
 try {
 List<FileItem> list = fileUpload.parseRequest(request);
 //Get the second form item, because the first form item is username and the second is file
 FileItem fileItem = list.get(1);
 String name = fileItem.getName();//Get file name
 
 // If the client uses IE6, you need to get the file name from the full path
 int lastIndex = name.lastIndexOf("\\");
 if(lastIndex != -1) {
 name = name.substring(lastIndex + 1);
 }
 
 // Get the save directory of the uploaded file
 String savepath = this.getServletContext().getRealPath("/WEB-INF/uploads");
 String uuid = CommonUtils.uuid();//Generate uuid
 String filename = uuid + "_" + name;//The new file name is uuid + underline + original name
 
 int hCode = name.hashCode();//Get hashCode of file name
 //Get the lower 4 bits of hCode and convert it into hexadecimal string
 String dir1 = Integer.toHexString(hCode & 0xF);
 //Get the lower 5 ~ 8 bits of hCode and convert it into hexadecimal string
 String dir2 = Integer.toHexString(hCode >>> 4 & 0xF);
 //Connect with the file saving directory to form a full path
 savepath = savepath + "/" + dir1 + "/" + dir2;
 //Because this path may not exist, create a File object and then create a directory chain to ensure that the directory already exists before saving the File
 new File(savepath).mkdirs();
 
 //Create a file object and save the uploaded file to the path specified by the file
 //savepath is the saving directory of the uploaded file
 //filename, file name
 File file = new File(savepath, filename);
 
 // Save file
 fileItem.write(file);
 } catch (Exception e) {
 // Judge whether the type of exception thrown is fileuploadbase FileSizeLimitExceededException
 // If yes, it indicates that the limit was exceeded when uploading files.
 if(e instanceof FileUploadBase.FileSizeLimitExceededException) {
 // Save error information in request
 request.setAttribute("msg", "Upload failed! The number of uploaded files exceeds 10 KB!");
 // Forward to index JSP page! In index You need to use $} in msg page to display error information
 request.getRequestDispatcher("/index.jsp").forward(request, response);
 return;
 }
 throw new ServletException(e);
 } 
}
  1. Total size limit of uploaded files
    Multiple files may be allowed to be uploaded in the form of uploading files, for example:
    Sometimes we need to limit the size of a request. That is, the maximum number of bytes of this request (the sum of all form items)! This function is also very simple. You only need to call the setSizeMax(long) method of ServletFileUpload class.
    For example, fileUpload setSizeMax(1024 * 10);, The maximum size for displaying the entire request is 10KB. When the request size exceeds 10KB, the parseRequest() method of ServletFileUpload class will throw fileuploadbase Sizelimitexceededexception exception.

  2. Cache size and temporary directory
    Let's think about it. If I upload a blu ray movie, first save the movie to memory, and then copy it to the server hard disk through memory, can your memory be consumed?
    Therefore, it is impossible for the fileupload component to save all files in memory. fileupload will judge whether the file size exceeds 10KB. If so, save the file to the hard disk. If not, save it in memory.
    10KB is the default value of fileupload. We can set it.
    When the file is saved to the hard disk, fileupload saves the file to the temporary directory of the system. Of course, you can also set the temporary directory

public void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 request.setCharacterEncoding("utf-8");
 DiskFileItemFactory dfif = new DiskFileItemFactory(1024*20, new File("F:\\temp"));
 ServletFileUpload fileUpload = new ServletFileUpload(dfif);
 
 try {
 List<FileItem> list = fileUpload.parseRequest(request);
 FileItem fileItem = list.get(1);
 String name = fileItem.getName();
 String savepath = this.getServletContext().getRealPath("/WEB-INF/uploads");
 
 // Save file
 fileItem.write(path(savepath, name));
 } catch (Exception e) {
 throw new ServletException(e);
 } 
 }
 
 private File path(String savepath, String filename) {
 // Get full path name from file
 int lastIndex = filename.lastIndexOf("\\");
 if(lastIndex != -1) {
 filename = filename.substring(lastIndex + 1);
 }
 
 // Generate primary and secondary directories by file name
 int hCode = filename.hashCode();
 String dir1 = Integer.toHexString(hCode & 0xF);
 String dir2 = Integer.toHexString(hCode >>> 4 & 0xF);
 savepath = savepath + "/" + dir1 + "/" + dir2;
 // Create directory
 new File(savepath).mkdirs();
 
 // Prefix the file name with uuid
 String uuid = CommonUtils.uuid();
 filename = uuid + "_" + filename;
 
 // Create file completion path
 return new File(savepath, filename);
 }

File download

Download via servlet

The downloaded resources must be placed in the WEB-INF directory (OK as long as the user can't access directly through the browser), and then the download can be completed through the Servlet.
Give a hyperlink in the jsp page, link to the DownloadServlet, and provide the name of the file to be downloaded. Then the DownloadServlet obtains the real path of the file and writes the file to the response Getoutputstream() is in the stream.However, there is a problem with the file download at this time:
The first problem is: you can download a.avi, but the file name in the download box is DownloadServlet;
The second problem is that you can't download a.jpg and a.txt, but display them on the page.

To solve these two problems, the correct file name can be displayed in the download box, and a.jpg and a.txt files can be downloaded

Handle the above problem by adding the content disposition header. When the content disposition header is set, the browser will pop up the download box

You can also specify the name of the downloaded file through the content disposition header!

String filename = request.getParameter("path");
 String filepath = this.getServletContext().getRealPath("/WEB-INF/uploads/" + filename);
 File file = new File(filepath);
 if(!file.exists()) {
 response.getWriter().print("The file you want to download does not exist!");
 return;
 }
 response.addHeader("content-disposition", "attachment;filename=" + filename);
 IOUtils.copy(new FileInputStream(file), response.getOutputStream());

Although the above code can handle the download of txt, jpg and other files, and also deal with the problem of displaying the file name in the download box, if the downloaded file name is Chinese, it still can't
In fact, this problem is very simple. You only need to encode Chinese through URL!

<a href="<c:url value='/DownloadServlet?path=Léon.avi'/>">Léon.avi</a><br/>
<a href="<c:url value='/DownloadServlet?path=White ice.jpg'/>">White ice.jpg</a><br/>
<a href="<c:url value='/DownloadServlet?path=Documentation.txt'/>">Documentation.txt</a><br/>
String filename = request.getParameter("path");
// In the GET request, the parameters contain Chinese, which needs to be converted by yourself.
// Of course, if you use the "global coding filter", you don't have to deal with it here
filename = new String(filename.getBytes("ISO-8859-1"), "UTF-8");

String filepath = this.getServletContext().getRealPath("/WEB-INF/uploads/" + filename);
File file = new File(filepath);
if(!file.exists()) {
 response.getWriter().print("The file you want to download does not exist!");
 return;
}
// All browsers will use local coding, that is, the Chinese operating system uses GBK
// After the browser receives the file name, it will use iso-8859-1 to decode it
filename = new String(filename.getBytes("GBK"), "ISO-8859-1");
response.addHeader("content-disposition", "attachment;filename=" + filename);
IOUtils.copy(new FileInputStream(file), response.getOutputStream());

This is the content uploaded by javaweb. The reference blog is as follows:
https://www.jb51.net/article/96745.htm

Spring MVC file upload

I'm too lazy to write. Put two portals here and fill them up another day
https://www.cnblogs.com/fjsnail/p/3491033.html
https://blog.csdn.net/suifeng3051/article/details/51659731

SpringBoot file upload

Portal https://blog.csdn.net/gnail_oug/article/details/80324120

Keywords: Java

Added by dexter_sherban on Tue, 01 Feb 2022 02:11:51 +0200