一、文件的上传
HTML代码需要一个 <form> 表单,注意如下:
- 要有一个 <form> 标签,
method="post"
- <form> 标签的
encType
属性值必须为"multipart/form-data"
值 - <form> 标签中的 <input> 标签,
type="file"
- 编写服务器代码,接收上传的数据
说明: encType="multipart/form-data"
表示提交的数据以多段(每一个input标签为一个段)的形式进行拼接,然后以二进制流的形式发送给Servlet。
HTML代码的一个样例如下:
<html>
<head>
<title>上传文件</title>
</head>
<body>
<!-- 核心的form表单 -->
<form action="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>
</html>
那么,当我们将encType
设置为multipart/form-data
之后,HTTP协议是如何发送数据的呢?参考下面这个图,数据是以二进制流的方式分段发送的。
那么服务端如何接收数据呢?因为客户端是以二进制流的形式发送的,那么服务端就不能再通过req.getParameter("username")
这种形式获得参数了,只能通过二进制输入流来接收。
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取服务端发送过来的输入流
ServletInputStream inputStream = req.getInputStream();
// 定义一个缓冲区
byte[] buf = new buf[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));
}
}
}
运行结果如下,可以发现服务端接收到的数据格式和客户端发过来的一致。
下面需要做的工作就是,如何解析我们得到的数据流的内容?
这个解析的工作,commons-fileupload.jar
和 commons-io.jar
两个包已经帮我们封装好了解析的功能,我们只需要掌握如何使用他们即可。
这里我们使用的版本是 commons-fileupload-1.2.1.jar 和 commons-io-1.4.jar,引入到需要的项目工程中即可使用。
下面介绍一些常用的方法:
// 判断当前上传的数据格式是否是多段的格式
boolean ServletFileUpload.isMultipartContent(HttpServletRequest request);
// 解析上传的数据,每一个FileItem表示每一个表单项,对应每一个input标签
public List<FileItem> parseRequest(HttpServletRequest request);
// 判断当前这个FileItem 是否是普通的表单项
// true表示普通类型的表单项,false表示文件
boolean FileItem.isFormField();
// 获取当前FileItem表单项的name属性值(普通表单项)
String FileItem.getFieldName();
// 获取当前FileItem表单项的值(普通表单项)
String FileItem.getString();
// 获取当前FileItem文件名(文件表单项)
String FileItem.getName();
// 将上传的文件,写到 filePath 指定的磁盘位置
void FileItem.write(String filePath);
我们将这些方法应用到实际项目中去:
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 先判断上传的数据是否是多段数据
if (ServletFileUpload.isMultipartContent(req)) {
// 创建FileItemFactory 工厂实现类
FileItemFactory fileItemFactory = new DiskFileItemFactory();
// 创建用于解析上传数据的实现类ServletFileUpload 类
ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
// 这里解决上传的文件名的中文乱码问题
servletFileUpload.setHeaderEncoding("UTF-8");
try {
// 解析上传的二进制流数据,得到每一个表单项FileItem
List<FileItem> list = servletFileUpload.parseRequest(req);
// 循环判断,每一个表单项是普通类型还是文件类型
for (FileItem fileItem : list) {
if (fileItem.isFormField()) {
// 代表普通表单项
System.out.println("表单项的name 属性值:" + fileItem.getFieldName());
// 参数UFT-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("e:\\" + fileItem.getName()));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
二、文件的下载
实现文件下载的逻辑,如下图。
直接上项目的实战代码:
public class DownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取客户端请求的文件名
String downloadFileName = req.getParameter("downloadFileName");
// 2. 读取要下载的文件内容(通过ServletContext对象实现)
ServletContext servletContext = getServletContext();
// 3. 在回传前,通过响应头告诉客户端返回的文件类型
// 第一个斜杠/ 表示地址为 http://ip:port/工程名/ 映射到代码的web目录
String mimeType = servletContext.getMimeType("/file/" + downloadFileName);
resp.setContentType(mimeType);
// 4. 告诉客户端,通过响应头告诉客户端收到的数据是用于下载的
// Content-Disposition响应头:表示设置收到的数据的处理方式
// attachment:表示附件,告知浏览器用下载的方式接收文件
// filename:表示客户端收到的文件名
// 如果不写下面这行,文件(如图片)会直接写在页面上,而不是以一个下载的方式
resp.setHeader("Content-Disposition", "attachment; filename=" + downloadFileName);
// 5. 获取对应文件二进制流和response的输出流
InputStream resourceAsStream = servletContext.getResourceAsStream("/file/" + downloadFileName);
ServletOutputStream outputStream = resp.getOutputStream();
// 6. IOUtils.copy(InputStream, OutputStream) 将输入流的内容拷贝给输出流
IOUtils.copy(resourceAsStream, outputStream);
}
}
现在,客户端虽然能够获得正确的文件,但是如果文件的名称包含中文,可能会导致中文乱码的问题。我们可以通过下面的方式解决:
public class DownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取客户端请求的文件名
String downloadFileName = req.getParameter("downloadFileName");
// 2. 读取要下载的文件内容(通过ServletContext对象实现)
ServletContext servletContext = getServletContext();
// 3. 在回传前,通过响应头告诉客户端返回的文件类型
// 第一个斜杠/ 表示地址为 http://ip:port/工程名/ 映射到代码的web目录
String mimeType = servletContext.getMimeType("/file/" + downloadFileName);
resp.setContentType(mimeType);
// 4. 告诉客户端,通过响应头告诉客户端收到的数据是用于下载的
// Content-Disposition响应头:表示设置收到的数据的处理方式
// attachment:表示附件,告知浏览器用下载的方式接收文件
// filename:表示客户端收到的文件名
// 下面根据不同浏览器,设置不同的中文乱码处理方式
if (req.getHeader("User-Agent").contains("Firefox")) {
// 处理火狐浏览器中文乱码问题,使用Base64编码
resp.setHeader("Content-Disposition", "attachment; filename==?UTF-8?B?" + new BASE64Encoder().encode(downloadFileName, "UTF-8"));
} else {
// 处理IE 和 Chrome浏览器中文乱码问题,使用URL编码
resp.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("downloadFileName", "UTF-8"));
}
// 5. 获取对应文件二进制流和response的输出流
InputStream resourceAsStream = servletContext.getResourceAsStream("/file/" + downloadFileName);
ServletOutputStream outputStream = resp.getOutputStream();
// 6. IOUtils.copy(InputStream, OutputStream) 将输入流的内容拷贝给输出流
IOUtils.copy(resourceAsStream, outputStream);
}
}