In the development of web application system, file upload and download functions are very commonly used functions. Today, let’s talk about the implementation of file upload and download functions in JavaWeb.
File Upload Overview
1. The function of file upload
For example, network hard drive! It is used to upload and download files.
To fill in a complete resume on Zhilian Recruitment, you also need to upload photos.
2. Requirements for page uploading
There are many requirements for uploading files, please remember:
The form must be used, not a hyperlink form method must be POST, not a GET
The enctype of the form must be multipart/form-data
Add file form field in the form, i.e. <input type="file" name="xxx"/>
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data"> Username: <input type="text" name="username"/><br/> File 1: <input type="file" name="file1"/><br/> File 2: <input type="file" name="file2"/><br/> <input type="submit" value="submit"/> </form>3. Compare the difference between file upload form and normal text form
View the difference between "File Upload Form" and "Normal Text Form" through httpWatch.
The enctype of the file upload form = "multipart/form-data", which represents multi-part form data;
Normal text forms can be set without setting the enctype attribute:
When method=”post”, the default value of enctype is application/x-www-form-urlencoded, which means that when method=”get” is used, the default value of enctype is null and there is no text, so there is no need to test the normal text form:
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post"> Username: <input type="text" name="username"/><br/> File 1: <input type="file" name="file1"/><br/> File 2: <input type="file" name="file2"/><br/> <input type="submit" value="submit"/></form>Through httpWatch testing, looking at the request data body of the form, we found that there is only the file name in the request, but no file content. That is to say, when the enctype of the form is not multipart/form-data, the request does not contain the file content, but only the file name, which means that there is no difference between input:file and input:text in a normal text form.
Testing file upload form:
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data"> Username: <input type="text" name="username"/><br/> File 1: <input type="file" name="file1"/><br/> File 2: <input type="file" name="file2"/><br/> <input type="submit" value="submit"/> </form>Through httpWatch test, we view the body part of the request data of the form and found that the body part is composed of multiple components, each component corresponds to a form field, and each component has its own header information. Below the header information is a blank line, and below the blank line is the body part of the field. Multiple parts are separated by randomly generated dividers.
The header information of the text field contains only one header information, namely Content-Disposition. The value of this header information has two parts. The first part is fixed, namely form-data, and the second part is the name of the field. Behind the blank line is the main part, and the main part is the content filled in in the text box.
The header information of the file field contains two headers, Content-Disposition and Content-Type. There is an extra filename in Content-Disposition, which specifies the uploaded file name. Content-Type specifies the type of uploaded file. The main part of the file field is the content of the file.
Please note that because the files we upload are all normal text files, i.e. txt files, they can be displayed normally in httpWatch. If the uploaded files are exe, mp3, etc., then what you see on httpWatch is garbled.
4. Requirements for Servlets when uploading files
When the submitted form is a file upload form, there are also requirements for the Servlet.
First of all, we need to be sure that the data of the file upload form is also encapsulated into the request object.
The request.getParameter(String) method gets the specified form field character content, but the file upload form is no longer the character content, but the byte content, so it is invalid.
At this time, you can use the getInputStream() method of request to obtain the ServletInputStream object. It is a subclass of InputStream. This ServletInputStream object corresponds to the body part of the entire form (starting from the first divider to the end), which shows the data in the parsing stream we need. Of course, parsing it is a very troublesome thing, and Apache has provided us with tools to parse it: commons-fileupload
You can try to print out the contents of the request.getInputStream() stream and compare the request data in httpWatch.
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { InputStream in = request.getInputStream(); String s = IOUtils.toString(in); System.out.println(s);}-----------------------------7ddd3370ab2Content-Disposition: form-data; name="username"hello-----------------------------7ddd3370ab2Content-Disposition: form-data; name="file1"; filename="a.txt"Content-Type: text/plainaaa-----------------------------7ddd3370ab2Content-Disposition: form-data; name="file2"; filename="b.txt"Content-Type: text/plainbbb-----------------------------7ddd3370ab2--
commons-fileupload
Why use fileupload:
There are many requirements for uploading files, please remember:
Must be a POST form;
The enctype of the form must be multipart/form-data;
Add file form field to the form, i.e.
Servlet requirements:
You can no longer use request.getParameter() to get form data. You can use request.getInputStream() to get all the form data, instead of the data of a form item. This means that you do not use fileupload, we need to parse the content of request.getInputStream() ourselves.
1. Fileupload overview
fileupload is an upload component provided by apache's commons component. Its main job is to help us parse request.getInputStream()
The JAR packages required by the fileupload component are:
commons-fileupload.jar, core package
commons-io.jar, dependency package
2. Simple application of fileupload
The core classes of fileupload are: DiskFileItemFactory, ServletFileUpload, FileItem
The steps to use the fileupload component are as follows:
//1. Create the factory class DiskFileItemFactory object DiskFileItemFactory factory = new DiskFileItemFactory();//2. Create a parser object using the 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
public DiskFileItemFactory(int sizeThreshold, File repository)
When constructing a factory, specify the memory buffer size and temporary file storage location.
public void setSizeThreshold(int sizeThreshold)
Set the memory buffer size, default 10K
public void setRepository(File repository)
Set the temporary file storage location, default System.getProperty("java.io.tmpdir").
Memory buffer: When uploading a file, the content of the uploaded file is saved in the memory buffer first. When the uploaded file size exceeds the buffer size, temporary files will be generated on the server side.
Temporary file storage location: Upload files exceeding the memory buffer size to generate temporary files. The temporary files can be deleted through FileItem's delete() method
FileItem represents each part of the data in the file upload form
We will introduce the FileItem class solemnly, which is the final result we want. A FileItem object corresponds to a form item (form field). File fields and normal fields exist in a form. You can use the isFormField() method of FileItem class to determine whether the form field is a normal field. If it is not a normal field, then it is a file field.
Note: Because the file upload form is encoded using multipart/form-data, different from traditional url encoding, all getParameter () methods cannot use setCharacterEncoding () cannot solve the garbled problem of input items.
ServletFileUpload file upload core class
3. Simple upload example
Write a simple upload example:
The form contains a user name field and a file field;
Servlet saves uploaded files to the uploads directory, displaying username, file name, file size, file type.
first step:
To complete index.jsp, only one form is needed. Note that the form must be post, and the enctype must be mulitpart/form-data
<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data"> Username: <input type="text" name="username"/><br/> File 1: <input type="file" name="file1"/><br/> <input type="submit" value="submit"/></form>Step 2: Complete FileUploadServlet
public void doPost(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 object using factory 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()) { // Get 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 username form item response.getWriter().print("Username:" + fileItem.getString() + "<br/>"); } } else {//If the current form item is not a normal form item, it means the file field String name = fileItem.getName();//Get the name of the uploaded file// If the uploaded file name is empty, no uploaded file is specified if(name == null || name.isEmpty()) { continue; } // Get the real path, corresponding to ${project directory}/uploads. Of course, this directory must have String savepath = this.getServletContext().getRealPath("/uploads"); // Create File object through the uploads directory and file name File file = new File(savepath, name); // Save the upload file to the specified location fileItem.write(file); // Print the name of the upload file response.getWriter().print("Upload file name: " + name + "<br/>"); // Print the size of the upload 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); } }File upload details
1. Put the uploaded file in the WEB-INF directory
If the files uploaded by the user are not stored in the WEB-INF directory, the user can directly access the uploaded files through the browser, which is very dangerous.
If the user uploads an a.jsp file and then the user accesses the a.jsp file through the browser, then the content in a.jsp will be executed. If there is the following statement in a.jsp: Runtime.getRuntime().exec("shutdown st 1"); then you will...
Usually we will create an uploads directory in 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, there is the following statement in my upload1 project:
ServletContext servletContext = this.getServletContext();String savepath = servletContext.getRealPath("/WEB-INF/uploads");The savepath is: F:/tomcat6_1/webapps/upload1/WEB-INF/uploads.
2. File name (full path, file name)
The uploaded file name 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 to test, IE6 will return the full path to upload the file. I don’t know what IE6 is doing, which brings us a lot of trouble, which is to deal with this problem.
It is also very simple to deal with this problem. Whether it is a complete path or not, we just intercept the content after the last "/"
String name = file1FileItem.getName();int lastIndex = name.lastIndexOf("//");//Get the position of the last "/" if(lastIndex != -1) {//Note that if it is not the full path, then there will be no "/". name = name.substring(lastIndex + 1);//Get the file name} response.getWriter().print(name);3. Chinese garbled problem
The uploaded file name contains Chinese:
When the uploaded name contains Chinese, you need to set the encoding. The commons-fileupload component provides us with two ways to set the encoding:
request.setCharacterEncoding(String): This method is the most familiar way we are.
fileUpload.setHeaderEncdoing(String): This method has higher priority than the previous one
The file content of the uploaded file contains Chinese:
Usually we don’t need to care about the content of uploading files, because we will save the uploaded files to the hard drive! In other words, what does the file look like and what it looks like on the server!
But if you have such a requirement and have to display the uploaded file contents on the console, then you can use fileItem.getString("utf-8") to handle encoding
Text file content and normal form item content use getString ("utf-8") of the FileItem class to handle encoding.
4. The issue of uploading file with the same name (file renaming)
Usually we save the file uploaded by the user to the uploads directory, but what if the user uploads a file with the same name? This will cause coverage. The method to deal with this problem is to use the UUID to generate a unique name, and then use the "_" to connect the original name uploaded by the file.
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 its extension. Because of the uniqueness of the UUID, the uploaded file has the same name, but there will be no problem with the same name on the server side.
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, the second is the file form item FileItem fileItem = list.get(1); String name = fileItem.getName();//Get the file name// If the client is using IE6, then 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 uploaded file savepath = this.getServletContext().getRealPath("/WEB-INF/uploads"); String uuid = CommonUtils.uuid();//Generate uuid String filename = uuid + "_" + name;//The new file name is uuid + underscore + original name//Create a file object, and the uploaded file will be saved to the path specified by this file//savepath, that is, the uploaded file directory//filename, file name File file = new File(savepath, filename); //Save file Item.write(file); } catch (Exception e) { throw new ServletException(e); } }5. A directory cannot store too many files (store directory to break up)
There should not be too many files stored in a directory. Generally, 1,000 files are stored in a directory, and if there are many, it will be very "cracky" when opening the directory. You can try printing the C:/WINDOWS/system32 directory, you will feel it
That is, we need to put the uploaded files into different directories. However, one directory cannot be used for each uploaded file, as this method will lead to too many directories. So we should use some algorithm to "break up"!
There are many ways to break it up, such as using dates to break it up, generating a directory every day. You can also use the first letter of the file name to generate a directory, and files with the same initial letter are placed in the same directory.
Date breaking algorithm: If there are too many files uploaded on a certain day, there will also be too many directory files;
The algorithm for breaking the first letter: If the file name is in Chinese, because there are too many Chinese, it will lead to too many directories.
We use the hash algorithm here to break it up:
Get the hashCode of the file name: int hCode = name.hashCode()
Get the lower 4 bits of hCode, then convert it into hexadecimal characters to get the 5~8 bits of hCode, then convert it into hexadecimal characters to generate a directory chain using these two hexadecimal characters. For example, the lower 4-bit characters are "5"
The advantage of this algorithm is that a maximum of 16 directories are generated in the uploads directory, and a maximum of 16 directories are generated in each directory, that is, 256 directories, and all uploaded files are placed in these 256 directories. If the maximum number of each directory is 1000 files, a total of 256,000 files can be saved.
For example, the upload file name is: New text document.txt, then obtain the hash code of "New text document.txt", and then obtain the lower 4 digits of the hash code, and 5 to 8 digits. If the lower 4 bits are: 9, and 5~8 bits are 1, then the file save path is uploads/9/1/
int hCode = name.hashCode();//Get the hashCode of the file name//Get the low 4 bits of hCode and convert it into the hexadecimal string String dir1 = Integer.toHexString(hCode & 0xF);//Get the low 5~8 bits of hCode and convert it into the hexadecimal string String dir2 = Integer.toHexString(hCode >>> 4 & 0xF);//Connect the file save path to the full path savepath = savepath + "/" + dir1 + "/" + dir2;//Because this path may not exist, create it as a File object, and then create a directory chain to ensure that the directory already exists new before saving the file File(savepath).mkdirs();
6. Size limits for individual uploaded files
It is very simple to limit the size of uploaded files, just setFileSizeMax(long) of the ServletFileUpload class. The parameter is the upper limit number of bytes of the uploaded file. For example, servletFileUpload.setFileSizeMax(1024*10) means that the upper limit is 10KB.
Once the uploaded file exceeds the upper limit, a FileUploadBase.FileSizeLimitExceededException exception will be thrown. We can get this exception in the servlet and output "uploaded file exceeds 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 the uploaded single file 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, the second is the file form item FileItem = list.get(1); String name = fileItem.getName();//Get the file name// If the client is using IE6, then 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 uploaded file savepath = this.getServletContext().getRealPath("/WEB-INF/uploads"); String uuid = CommonUtils.uuid();//Generate uuid String filename = uuid + "_" + name;//The new file name is uuid + underscore + original name int hCode = name.hashCode();//Get the hashCode of the 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 the file save path into 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 the uploaded file will be saved to the path specified by this file //savepath, that is, the uploaded file save directory //filename, filename, filename; //Save fileItem.write(file); } catch (Exception e) { // Determine whether the type of the thrown exception is FileUploadBase.FileSizeLimitExceededException // If so, it means that the limit was exceeded when uploading the file. if(e instanceof FileUploadBase.FileSizeLimitExceededException) { // Save the error message in the request request.setAttribute("msg", "Upload failed! The uploaded file exceeded 10KB!"); // Forward to the index.jsp page! In the index.jsp page, you need to use ${msg} to display the error message request.getRequestDispatcher("/index.jsp").forward(request, response); return; } throw new ServletException(e); } }7. Total size limit for uploading files
The form to upload a file may allow multiple files to be uploaded, for example:
Sometimes we need to limit the size of a request. That is to say, the maximum number of bytes for this request (sum of all form items)! Implementing this function is also very simple. You only need to call the setSizeMax(long) method of the ServletFileUpload class.
For example, fileUpload.setSizeMax(1024 * 10);, the upper limit for the entire request is 10KB. When the request size exceeds 10KB, the parseRequest() method of the ServletFileUpload class will throw a FileUploadBase.SizeLimitExceededException exception.
8. Cache size and temporary directory
Think about it, if I upload a Blu-ray movie, save the movie to memory first, and then copy it to the server hard disk through memory, then can your memory be eaten?
Therefore, the fileupload component cannot save all files in memory. Fileupload will determine whether the file size exceeds 10KB. If so, save the file to the hard disk. If it does not exceed it, 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 system temporary directory. 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 fileItem.write(path(path(savepath, name)); } catch (Exception e) { throw new ServletException(e); } } private File path(String savepath, String filename) { // Get the file name int from the full path lastIndex = filename.lastIndexOf("//"); if(lastIndex != -1) { filename = filename.substring(lastIndex + 1); } // Generate first- and second-level directories through 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(); // Add uuid prefix to the file name String uuid = CommonUtils.uuid(); filename = uuid + "_" + filename; // Create file completion path return new File(savepath, filename); }File download
1. Download 1 through Servlet
The downloaded resources must be placed in the WEB-INF directory (it is OK as long as the user cannot access directly through the browser), and then download it through the Servlet.
Give the hyperlink in the jsp page, link to the DownloadServlet, and provide the file name to download. Then DownloadServlet gets the real path of the file and writes the file to the response.getOutputStream() stream.
download.jsp
<body> This is my JSP page. <br> <a href="<c:url value='/DownloadServlet?path=a.avi'//>">a.avi</a><br/> <a href="<c:url value='/DownloadServlet?path=a.jpg'///>">a.jpg</a><br/> <a href="<c:url value='/DownloadServlet?path=a.txt'////>">a.txt</a><br/> </body>
DownloadServlet.java
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 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; } IOUtils.copy(new FileInputStream(file), response.getOutputStream());}The above code has the following problems:
1. You can download a.avi, but the file name in the download box is DownloadServlet;
2. You cannot download a.jpg and a.txt, but display them in the page.
2. Download 2 through Servlet
Let’s deal with the problem in the previous example, so that the download box can display the correct file name, and you can download a.jpg and a.txt files
Handle the above problem by adding content-disposition header. When the content-disposition header is set, the browser will pop up the download box
And 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 already handle the downloading of files such as txt and jpg, and also handle the problem of displaying file names in the download box, if the downloaded file name is in Chinese, then it still cannot
3. Download 3 through Servlet
Below is the problem of handling the display of Chinese in the download box!
In fact, this question is very simple. You only need to encode Chinese through the URL!
download.jsp
<a href="<c:url value='/DownloadServlet?path=This killer is not too cold.avi'//>">This killer is not too cold.avi</a><br/><a href="<c:url value='/DownloadServlet?path=Baibing.jpg'/>">Baibing.jpg</a><br/><a href="<c:url value='/DownloadServlet?path=Description.txt'/>">Description.txt</a><br/>
DownloadServlet.java
String filename = request.getParameter("path");// In the GET request, the Chinese parameter contains the Chinese and needs to be converted by yourself. // Of course, if you use the "global encoding filter", then you don't need 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 encoding, that is, the Chinese operating system uses GBK// After the browser receives this file name, it will use iso-8859-1 to decode filename = new String(filename.getBytes("GBK"), "ISO-8859-1");response.addHeader("content-disposition", "attachment;filename=" + filename);IOUtils.copy(new FileInputStream(file), response.getOutputStream());The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.