File upload and download (super detailed)

File upload and download are very common functions. In many systems or software, file upload and download are often used. For example: QQ avatar, upload is used. There are also attachment upload and download functions in the mailbox. Upload of attached materials is approved in the OA system.

1. Introduction of file upload (emphasis)

  1. There must be a form tag, method=post request
  2. The encType attribute value of the form tag must be a multipart/form-data value
  3. Use input type=file in the form tag to add the uploaded file
  4. Write server code (servlet program is used here) to receive and process the uploaded data.

encType=multipart/form-data means that the submitted data is spliced ​​in the form of multiple segments (one data segment for each form item), and then sent to the server in the form of a binary stream.

upload.jsp:

<body>
    <form action="http://172.25.11.14:8080/09_EL_JSTL/uploadServlet" method="post" enctype="multipart/form-data">
        用户名:<input type="text" name="username" /> <br>
        头像:<input type="file" name="photo" /> <br>
        <input type="submit" value="上传" />
    </form>
</body>

Then write an UploadServlet.java (rewrite the doPost method), and configure the access address to uploadServlet (all omitted here).

1.1 File upload, description of HTTP protocol

Insert picture description here

Content-Type indicates the type of data submitted

multipart/form-data represents the submitted data, which is spliced ​​in the form of multiple segments (one data segment for each form item), and then sent to the server in the form of a binary stream .

boundary represents the separator of each piece of data

----WebKitFormBoundaryfR6ijBPPqbdkIBkO is randomly generated every time by the browser. It is the separator of each piece of data.

The following is an example of the server not receiving in the form of a stream:

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        //以流的形式发给服务器,则只能以流的形式接收
        //以不是流的形式接收为例
        System.out.println(req.getParameter("username"));
        System.out.println(req.getParameter("photo"));
    }

After the browser uploaded the file, two nulls were output on the console of idea.

The following is an example of receiving data in the form of a stream:

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        ServletInputStream inputStream = req.getInputStream();
        //需要创建一个buff缓冲区
        byte[] buffer = new byte[1024000];
        int read = inputStream.read(buffer);
        System.out.println(new String(buffer,0,read));  //从0开始读了 read 个
    }

Output:

Insert picture description here

The output content format is similar to the request body, the garbled code in the middle is the received information, which is omitted in the request body (it becomes a blank line)

1.2 commons-fileupload.jar common API introduction

commons-fileupload.jar needs to rely on the package commons-io.jar, so we have to import both packages.

The first step is to import two jar packages:
commons-fileupload-1.2.1.jar
commons-io-1.4.jar

In the commons-fileupload.jar and commons-io.jar packages, what are the commonly used classes?
The ServletFileUpload class is used to parse the uploaded data.
The FileItem class represents each form item.

boolean ServletFileUpload.isMultipartContent(HttpServletRequest request);
Determine whether the currently uploaded data format is a multipart format.

publicList< FileItem >parseRequest(HttpServletRequestrequest)
parse the uploaded data

boolean FileItem.isFormField()
determines whether the current form item is a normal form item. The type of the uploaded file is
true, which means that the normal type of form item is
false, which means that the type of the uploaded file is

String FileItem.getFieldName() Get the value of the name attribute of the form item

String FileItem.getString() Get the value of the current form item

String FileItem.getName() Get the uploaded file name

void FileItem.write(file) Write the uploaded file to the location of the pumped hard disk pointed to by the parameter file

1.3 Use of fileupload library

public class UploadServlet extends HttpServlet {
    
    
    /**
     * 用来处理文件上传的数据
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        //1.先判断上传的数据是否是多段的数据
        //ServletFileUpload.isMultipartContent(req)返回true就说明是多段的,返回false就说明不是
        if (ServletFileUpload.isMultipartContent(req)) {
    
    

            //创建 FileItemFactory 工厂实现类
            FileItemFactory fileItemFactory = new DiskFileItemFactory();
            //创建用于解析上传数据的工具类 ServletFileUpload
            ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
            //解析上传的数据,得到一个表单项 FileItem
            try {
    
    
                List<FileItem> list = servletFileUpload.parseRequest(req);

                //循环判断每一个表单项是普通类型还是上传的文件
                for (FileItem fileItem : list) {
    
    

                    if (fileItem.isFormField()) {
    
    
                        //普通表单项
                        System.out.println("表单项的name属性值:" + fileItem.getFieldName());
                        //参数 UTF-8 解决中文乱码问题
                        System.out.println("表单项的value属性值:" + fileItem.getString("UTF-8"));
                    } else {
    
    
                        //上传的文件
                        System.out.println("表单项的name属性值:" + fileItem.getFieldName());
                        System.out.println("上传的文件名:" + fileItem.getName());
                        fileItem.write(new File("d:\\study\\" + fileItem.getName()));
                    }

                }

            } catch (Exception e) {
    
    
                e.printStackTrace();
            }

        }
    }
}

"//": This is a one-line comment, I believe everyone knows it, so I won’t say more

"","\": For example, C:\ls\123\JDBC2\bin, "" means the default path separator on the computer disk, but in the Java compiler, it will be changed to "\" by default This form, especially when the relative path and absolute path are required for the io stream, and the subsequent loading of configuration files (load), etc. will be used.

"/": It is equivalent to "\" in the path, which are two ways of writing Java path

2. File download

Insert picture description here

Download commonly used API description:
response.getOutputStream();
servletContext.getResourceAsStream();
servletContext.getMimeType();
response.setContentType();

response.setHeader("Content-Disposition", "attachment; fileName=1.jpg");
This response header tells the browser. This is to be downloaded. And attachment means attachment, which is a downloaded file. After fileName=, it means the name of the downloaded file.

After completing the above two steps, the file download is no problem. But if the file we want to download is a Chinese name. You will find that the download cannot display the correct Chinese name correctly.

The reason is that the response header cannot contain Chinese characters, only ASCII codes.

public class DownLoad extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        //1.获取需要下载的文件名
        String downloadFileName = "a.jpg";

        //2.读取要下载的文件内容(通过 ServletContext 对象可以获取)
        ServletContext servletContext = getServletContext();
        //获取要下载的文件类型
        String mimeType = servletContext.getMimeType("/file/" + downloadFileName);
        System.out.println("下载的文件类型:" + mimeType);

        //4.在回传之前,通过响应头告诉客户端返回的数据类型
        resp.setContentType(mimeType);
        //5.还要告诉客户端收到的数据适用于下载使用(还是使用响应头)
        // Content-Disposition 响应头,表示收到的数据怎么处理
        // attachment 表示附件,下载使用;
        // filename= 表示指定下载的文件名
        resp.setHeader("Content-Disposition","attachment;filename=" + downloadFileName);

        /*
        在服务器端,第一个 / 被解析为: http://ip:port/工程名/  映射到代码的 web 目录
         */
        InputStream resourceAsStream = servletContext.getResourceAsStream("/file/" + downloadFileName);
        // 原先是用 byte[] 来读,这里改变一下,通过使用 commons-io-1.4.jar 包里已经写好的工具类 IOUtils
        // 获取响应的输出流
        OutputStream outputStream = resp.getOutputStream();

        //3.把下载的文件内容传回客户端
        // 读取输出流中全部的数据,复制给输出流,输出给客户端
        IOUtils.copy(resourceAsStream,outputStream);
    }
}

In addition:

resp.setHeader("Content-Disposition","attachment;filename=中国.jpg");

The name of the downloaded file will appear garbled, and this section needs to be Encoder-encoded

resp.setHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode("中国.jpg","UTF-8"));

Attached Chinese name garbled problem solution:

Solution 1: URLEncoder solves the Chinese name problem of attachments in IE and Google browsers

If the client browser is Internet Explorer or Google Chrome. We need to use the URLEncoder class to encode the Chinese name in UTF-8 first.

Because IE browser and Google browser will decode and display in UTF-8 character set after receiving the encoded string.

// 把中文名进行 UTF-8 编码操作。 
String str = "attachment; fileName=" + URLEncoder.encode("中文.jpg", "UTF-8"); 
// 然后把编码后的字符串设置到响应头中 
response.setHeader("Content-Disposition", str);

Solution 2: BASE64 encoding and decoding to solve the Chinese name problem of attachments in Firefox

If the client browser is Firefox. Then we need to perform the BASE64 encoding operation on the Chinese name.

At this time, you need to encode the request header Content-Disposition: attachment; filename=Chinese name into:
Content-Disposition: attachment; filename==?charset?B?xxxxx?=

=?charset?B?xxxxx?= Now we will explain this paragraph.
=?
charset
B
xxxx
?=

BASE64 encoding and decoding operations:

Because Firefox uses the BASE64 encoding and decoding method to restore the Chinese characters in the response. So you need to use the BASE64Encoder class for encoding operations.

// 使用下面的格式进行 BASE64 编码后
String str = "attachment; fileName=" + "=?utf-8?B?" 
    + new BASE64Encoder().encode("中文.jpg".getBytes("utf-8")) + "?=";
// 设置到响应头中
response.setHeader("Content-Disposition", str);

So how do we solve the above two different encoding and decoding methods? We only need to determine what browser it is by judging the browser information carried by the User-Agent request header in the request header.

as follows:

String ua = request.getHeader("User-Agent");
// 判断是否是火狐浏览器
if (ua.contains("Firefox")) {
    
    
// 使用下面的格式进行 BASE64 编码后
String str = "attachment; fileName=" + "=?utf-8?B?"
    + new BASE64Encoder().encode("中文.jpg".getBytes("utf-8")) + "?=";
// 设置到响应头中
response.setHeader("Content-Disposition", str);
} else {
    
    
// 把中文名进行 UTF-8 编码操作。
String str = "attachment; fileName=" + URLEncoder.encode("中文.jpg", "UTF-8");
// 然后把编码后的字符串设置到响应头中
response.setHeader("Content-Disposition", str);
}

Guess you like

Origin blog.csdn.net/weixin_45024585/article/details/112748075