JavaWeb—文件上传下载
哔哩哔哩蛙课网【动力节点】JavaWeb-Eclipse版学习视频网址 文章只为分享学习经验和自己复习用,学习还是该去查看正规视频网站和官方文档才更有效
| 解析 | 归属 | 备注 |
---|
mutipart/form-data | 多部分请求/表单数据 | 文件上传 | enctype 特殊请求 | getInputStream() | 获得输入流 | IO | | FileUpload | apache上传工具 | 文件上传 | apache上传工具 | ServletFileUpload | Servlet文件上传 | 文件上传 | | .isMultipartContent() | 是多部分内容请求 | 文件上传 | | DiskFileItemFactory() | 磁盘 文件项目 工厂 | FileUpload | | ServletFileUpload() | Servlet 文件上传 组件 | FileUpload | | .parseRequest() | 利用工具解析请求 | 基本上传 | | .isFormField() | 是普通表单字节 | 基本上传 | | .getInputStream() | 获得输入流上传内容 | 基本上传 | | .getRealPath | 获得真实路径 | 基本上传 | | FileOutputStream | 文件输出流输出 | 基本上传 | | setSizeThreshold | 设置临时目录门槛大小 | 临时目录 | | setRepository | 设置临时存储库 | 临时目录 | | .delete() | 临时文件删除 | 临时目录 | | .setHeaderEncoding(“UTF-8”) | 解决文件中文名 | 参数名问题 | | System.currentTimeMillis() | 当前系统时间 | 参数名问题 | | .setFileSizeMax | 上传文件大小 | 参数名问题 | | .setSizeMax | 上传所有文件总大小 | 参数名问题 | | Calendar.getInstance() | 获得当前系统时间 | 参数名问题 | | Calendar.YEAR,.MONTH | 年,月 | 参数名问题 | MONTH是从0开始算的 | Calendar.DAY_OF_MONTH | 日 | 参数名问题 | | .mkdir() | 创建目录 | 参数名问题 | .mkdirs() 多级目录 | new SimpleDateFormat(“yyyyMMdd”) | 格式化日期 | Servlet 下载 | | format() | 格式化日期 | Servlet 下载 | | setHeader | 头部信息 | Servlet 下载 | | “content-disposition”,"attachment“ | 下载的头部信息 | Servlet 下载 | | filename | 下载后的文件名 | Servlet 下载 | |
文件上传与下载
1、 文件上传
1.1、什么是上传与下载?
数据上传是指客户端向服务器上传数据,客户端向服务器发送的所有请求都属于数据上传。文件上传是数据上传的一种特例,指客户端向服务器上传文件。即将保存在客户端的文件上传至服务器中一个副本,保存到服务器中。
数据下载是指客户端从服务器上获取数据的过程。文件下载是数据下载的一种特例,指客户端从服务器下载文件,即将原本保存在服务器中的文件下载到到客户端中一个副本保存。通常我们对服务器所发出的请求,大多是文件下载请求,从服务器中下载文本、图片、声音、视频等文件,然后由客户端浏览器对这些文件进行解析后,才可能看到这些多媒体信息。
但我们这里所说的文件下载,指的是文件从服务器下载到浏览器后,浏览器并不直接解析,而是以附件的形式保存到客户端。
上传与下载的文件可以是文本文件、图片、声音、视频等各种类型。
1.2、文件上传的实现
1.2.1、上传表单要求
文件上传要求客户端表单提交特殊的请求 ---- multipart 请求,即包含多部分数据的请求。所以文件上传表单对于表单数据的编码类型要求,必须为 mutipart/form-data。即要为<form/> 标签指定 enctype 属性值为“mutipart/form-data”。enctype,即 encoding type,编码类型。
由于客户端上传文件的大小是不确定的,所以 HTTP 协议规定,文件上传的数据要存放于请求正文中,而不能出现在 URL 的地址栏中,因为地址栏中可以存放的数据量太小。也就是说,文件上传的表单,必须提交 POST 请求,而不能提交 GET 请求。
1.2.2、multipart/form-data 协议
multipart/form-data 的编码类型,是一种固定格式的编码方案,是 HTTP 请求协议规定好的一种通信格式。该编码方案首先告诉服务器,现在发送的请求是一个来自表单数据的多部分请求,请求体中包含多部分数据。
请求的具体格式可以通过火狐浏览器的“网络/参数”查看到。打开火狐浏览器的开发者工具栏窗口,当请求发出后,点击该请求,然后选择“网络/参数”即可看到发送的请求正文内容。注意,不要在 IE 下使用 HttpWatch 来查看文件上传的请求正文,因为 HttpWatch会拦截下请求,使请求无法发送成功。
就本例而言,表单中包含 name、age,与要上传的文件 img 三部分数据。其中 name 值为 zhangsan,age 值为 23,而文件 img 则为一个文本文件,文本文件在客户端的文件名为abc.txt。
HTTP 协议规定,multipart/form-data 的请求正文包含以下几部分:
请求正文头
其中包含请求的类型 Content-Type,当然,固定为 multipart/form-data。multipart 请求各部分间的分隔符 boundary,这个分隔符由若干中划线和一个随机数构成。请求正文的内容长度 Content-Length。
请求正文体
请求正文头与请求正文体,及请求正文体间都使用前面指定的分隔符进行分隔,将请求正文分隔为了多部分,即 multipart。每一部分,我们称其为一个 Item。
每个 Item 又由三部分构成:
- 参数信息:参数信息包含内容来源 Content-Disposition,form-data 为表单数据;参数名称;若参数为要上传的文件,还包含文件原始名称 filename,文件的 MIME 类型Content-Type,text/plain 表示普通的文本文件。
- 分隔空行:就一个空行,用于分隔参数信息与参数值。
- 参数值:具体上传的参数的值。若上传的文件为文本文件,则会将正常的文本上写入到这里。若上传的文件为图片、视频、音频等,则会将其二进制文件上传。下图为上传的图片文件的参数值。
1.2.3、服务端手工接收上传文件
服务端接收上传的文件,可以通过输入流来完成。而输入流可以通过 HttpServletRequest的 **getInputStream()**获取到。
接收到后,可以对输入流中的数据进行解析,然后响应给客户端。 multipart/form-data对协议的解析,手工完成比较麻烦,这里就不手工完成了。仅仅是接收到,在控制台显示一下即可。
定义表单页面 index.jsp
定义 UploadServlet
注册 UploadServlet
1.2.4、使用第三方工具上传
可以完成上传功能的第三方工具很多,但比较著名的是 Apache 的 FilterUpload 工具。该工具可以 Apache 的官网上下载。Apache 的官网为:http://apache.org 。
FileUpload 工具下载
FileUpload 工具存放在 Apacher 的 Commons 中,所以需要在 Commons 下下载。
在链接上右击,选择“将链接另存为”,即可下载。
IO 包下载
进一步跟踪该 Jar 包的信息,会看到如下注意:该版本需要依赖于 Apache 的 Commons下的 IO2.2 的包。
官网用户向导
打开 Apache 官网的文件上传主页的用户向导 User guide,其中就有使用 FileUpload 工具实现文件上传的示例。
1、代码实现-版本 1-基本上传
该版本完成了基本的文件上传功能。
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
public class RegisterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (!ServletFileUpload.isMultipartContent(request)) {
throw new RuntimeException("当前请求不支持文件上传");
}
try {
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
String fieldName = item.getFieldName();
String fieldValue = item.getString();
System.out.println(fieldName + " = " + fieldValue);
}else {
String fileName = item.getName();
InputStream is = item.getInputStream();
String path = this.getServletContext().getRealPath("/images");
File descFile = new File(path, fileName);
OutputStream os = new FileOutputStream(descFile);
int len = -1;
byte[] buf = new byte[1024];
while((len = is.read(buf)) != -1) {
os.write(buf, 0, len);
}
os.close();
is.close();
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
}
2、代码实现-版本 2-临时目录
文件由浏览器通过网络上传到服务器,并不是直接通过一条网络线路将所有请求数据发送到了服务器的。
而是将这些数据分为了很多个数据包,这些数据包分别编号后,经由不同的网络线路最终发送到了同一台机器 ---- 服务器。
这些数据包到达服务器的时间会根据不同的网络线路的情况不同,分别先后到达服务器,不一定是按照编号顺序到达的,或者是大多数情况下是不会按照编号顺序到达的。那么,服务器就会在其临时目录中,创建一个临时文件,将这些数据包进行拼接组装。
factory.setSizeThreshold(1024 * 1024 * 1);
String tempPath = this.getServletContext().getRealPath("/temp");
File temp = new File(tempPath);
factory.setRepository(temp);
Tomcat 默认情况下的临时目录是 Tomcat 服务器安装目录的 temp 子目录。当然,我们也可以修改临时目录的默认位置。
Apache 的 FileUpload 支持设置创建临时文件的最小临界值,即只有上传的文件大小超出这个值,才会创建临时文件。通过 DiskFileItemFactory 的 setSizeThreshold() 方法可以设置临界值,单位为字节。
通过 DiskFileItemFactory 的 setRepository() 方法可以指定临时目录。
临时文件一旦用完,就可将其删除了,否则占用服务器的硬盘空间。而对临时文件的删除,使用的是 FileItem 的 delete() 方法。
需要注意的是,对于临时文件的删除,需要在 IO 流关闭后,否则,无法删除。
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
public class RegisterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (!ServletFileUpload.isMultipartContent(request)) {
throw new RuntimeException("当前请求不支持文件上传");
}
try {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(1024 * 1024 * 1);
String tempPath = this.getServletContext().getRealPath("/temp");
File temp = new File(tempPath);
factory.setRepository(temp);
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
String fieldName = item.getFieldName();
String fieldValue = item.getString();
System.out.println(fieldName + " = " + fieldValue);
}else {
String fileName = item.getName();
InputStream is = item.getInputStream();
String path = this.getServletContext().getRealPath("/images");
File descFile = new File(path, fileName);
OutputStream os = new FileOutputStream(descFile);
int len = -1;
byte[] buf = new byte[1024];
while((len = is.read(buf)) != -1) {
os.write(buf, 0, len);
}
os.close();
is.close();
item.delete();
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
}
3、代码实现-版本 3-参数名问题
当前的程序存在如下问题:
-
当用户从表单中提交的普通参数包含中文字符时,会出现乱码问题。 -
当上传文件的文件名包含中文时,上传到服务器的文件名会出现乱码。 -
不同的浏览器,向服务器上传的文件名是不同的,即 FileItem 的 getName()方法获取到的文件名是不同的。 例如,用户从客户端上传了 D:\abc\xxx.jpg 文件,火狐浏览器上传的文件名为 xxx.jpg,而 IE 浏览器上传的文件名为 D:\abc\xxx.jpg。 -
不同的用户若提交了相同的文件名的文件,后面的用户的文件将无法上传。
解决这些问题的思路:
(1)解决普通参数的中文乱码问题,只需使用 FileItem 的带参 getString(String Encoding)方法获取参数名即可。
upload.setHeaderEncoding("UTF-8");
(2)上传文件名的中文乱码问题,需要通过 ServletFileUpload 的方法 setHeadEncoding()指定上传文件请求头部编码的方式解决。不过,需要注意的是,该设置方式不会改变普通参数请求头部的编码。
String fieldName = item.getFieldName();
String fieldValue = item.getString("UTF-8");
(3)为了解决浏览器向服务器发送文件名不同这个问题,需要使用 String 的 substring()方法截取出文件名。因为文件名一定是最后一个”\”后面部分。
(4)对于相同文件名的上传问题,只需要使保存在服务器端的文件名称唯一即可。例如,为原始文件名前添加一个当前系统时间 System.currentTimeMillis()。
String fileName = item.getName();
fileName = System.currentTimeMillis() + fileName;
(7) 代码实现-版本 4-文件大小
对于上传文件的大小,可以通过 ServletFileUpload 的 setFileSizeMax() 与 setSizeMax() 方法进行控制。setFileSizeMax()用于设置单个文件上传的最大值,而 setSizeMax()用于设置单次上传的最大值。即若一次上传多个文件,每个文件的大小边界值与所有文件加起来的最大小值。
upload.setFileSizeMax(1024 * 1024 * 2);
upload.setSizeMax(1024 * 1024 * 5);
(8) 代码实现-版本 5-自建目录
无论是 Windows 系统、Linux 系统,还是其它系统,其目录中所包含的文件数量是有上限的。所以对于上传的文件,应该分目录进行管理。若文件不是太多,可以在 images 下按照 yyyyMMdd 日期格式再建一级子目录。若文件较多,则可按照年、月、日创建多级子目录。这样,即方便管理,又不会超出目录的文件数量上限。
下面实现了以 yyyyMMdd 格式命名的一级子目录。
String path = this.getServletContext().getRealPath("/images");
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String now = sdf.format(date);
path = path + "/" + now;
File dirFile = new File(path);
if (!dirFile.exists()) {
dirFile.mkdir();
}
下面实现了是多级目录
String path = this.getServletContext().getRealPath("/images");
Calendar now = Calendar.getInstance();
int year = now.get(Calendar.YEAR);
int month = now.get(Calendar.MONTH) + 1;
int day = now.get(Calendar.DAY_OF_MONTH);
path = path + "/" + year + "/" + month + "/" + day;
File dirFile = new File(path);
if (!dirFile.exists()) {
dirFile.mkdirs();
}
全部代码
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
public class RegisterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("执行上传");
if( !ServletFileUpload.isMultipartContent(request) ) {
throw new RuntimeException("当前请求不支持文件上传");
}
try {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(1024 * 1024 * 1);
String tempPath = this.getServletContext().getRealPath("/temp");
File temp = new File(tempPath);
factory.setRepository(temp);
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
upload.setFileSizeMax(1024 * 1024 * 2);
upload.setSizeMax(1024 * 1024 * 5);
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if(item.isFormField()) {
String fieldName = item.getFieldName();
String fieldValue = item.getString("UTF-8");
System.out.println(fieldName + " = " + fieldValue);
} else {
String fileName = item.getName();
fileName = System.currentTimeMillis() + fileName;
InputStream is = item.getInputStream();
String path = this.getServletContext().getRealPath("/images");
Calendar now = Calendar.getInstance();
int year = now.get(Calendar.YEAR);
int month = now.get(Calendar.MONTH) + 1;
int day = now.get(Calendar.DAY_OF_MONTH);
path = path + "/" + year + "/" + month + "/" + day;
File dirFile = new File(path);
if (!dirFile.exists()) {
dirFile.mkdirs();
}
File descFile = new File(path, fileName);
OutputStream os = new FileOutputStream(descFile);
int len = -1;
byte[] buf = new byte[1024];
while((len = is.read(buf)) != -1) {
os.write(buf, 0, len);
}
os.close();
is.close();
item.delete();
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
}
2、文件下载
2.1、超链接下载
所谓超链接下载是指,将下载资源作为超链接的链接目的文件出现。
若浏览器可以解析该资源文件,则将在浏览器上直接显示文件内容;若浏览器不支持该文件的解析,则会弹出另存为对话框,要求用户保存。
缺点:不同的浏览器,以及相同的浏览器所安装的插件不同,那么其对于资源的解析能力也就不同,其是否弹出另存为对话框的情况也就不一样,决定权由浏览器掌握。
存放资源
在项目的 WebRoot 下新建一个目录 resources,在其中存放各类资源文件。
编写链接
在 index.jsp 页面中编写如下超链接。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 超链接方式的文件下载 -->
<a href="${pageContext.request.contextPath }/resources/aaa.jpg">aaa.jpg</a> <br>
<a href="${pageContext.request.contextPath }/resources/bbb.jar">bbb.jar</a> <br>
<a href="${pageContext.request.contextPath }/resources/ccc.zip">ccc.zip</a> <br>
<a href="${pageContext.request.contextPath }/resources/ddd.pdf">ddd.pdf</a> <br>
<a href="${pageContext.request.contextPath }/resources/eee.exe">eee.exe</a> <br>
<a href="${pageContext.request.contextPath }/resources/fff.txt">fff.txt</a> <br>
</body>
</html>
2.2、Servlet 下载
若要使下载的文件以附件的形式出现在浏览器,则需要设置响应头的属性content-disposition 的值为 attachment,且需要指定浏览器下载后显示的文件名。
即需要 response.setHeader 指定 content-disposition 的值为 attachment ; filename=文件名
2.2.1、下载的实现
定义 index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 超链接方式的文件下载 -->
<a href="${pageContext.request.contextPath }/downloadServlet">跑车</a> <br>
</body>
</html>
定义 DownloadServlet
package com.bjpowernode.servlets;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DownloadServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String fileName = "超跑.jpg";
byte[] bytes = fileName.getBytes("UTF-8");
fileName = new String(bytes, "ISO8859-1");
response.setHeader("content-disposition", "attachment;filename=" + fileName);
InputStream is = this.getServletContext().getResourceAsStream("/resources/aaa.jpg");
ServletOutputStream os = response.getOutputStream();
int len = -1;
byte[] buf = new byte[1024];
while((len = is.read(buf)) != -1) {
os.write(buf, 0, len);
}
os.close();
is.close();
}
}
注册 DownloadServlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>08-download-1</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<description></description>
<display-name>DownloadServlet</display-name>
<servlet-name>DownloadServlet</servlet-name>
<servlet-class>com.bjpowernode.servlets.DownloadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DownloadServlet</servlet-name>
<url-pattern>/downloadServlet</url-pattern>
</servlet-mapping>
</web-app>
2.2.2、解决文件名乱码问题
-
将当前收到的字符编码进行打散 -
再按目标字符编码进行组装 -
最后根据浏览器设定的编码呈现
String fileName = "超跑.jpg";
byte[] bytes = fileName.getBytes("UTF-8");
fileName = new String(bytes, "ISO8859-1");
apache帮助文档网址
http://commons.apache.org/proper/commons-fileupload/using.html
|