这里主要结合代码说明Java Web中文件上传和下载的机制。
1、文件上传示例说明
这里从一个文件上传的例子,看下文件如何经过http请求上传到服务器端的。
index.jsp
:
<%--
Created by IntelliJ IDEA.
User: chengxia
Date: 2021/10/31
Time: 4:40 PM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="/UploadServletDemo" enctype="multipart/form-data" method="post">
上传用户:<input type="text" name="username"><br/>
上传文件1:<input type="file" name="fileUpload1"><br/>
上传文件2:<input type="file" name="fileUpload2"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
WEB-INF/web.xml
中增加如下的servlet定义和对应的url映射:
<servlet>
<servlet-name>FileUploadServlet</servlet-name>
<servlet-class>com.trial.servlet.FileUploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileUploadServlet</servlet-name>
<url-pattern>/UploadServletDemo</url-pattern>
</servlet-mapping>
com/trial/servlet/FileUploadServlet.java
:
package com.trial.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
/**
* Created by chengxia on 2021/11/27.
*/
public class FileUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
// 获取表单(POST)数据
ServletInputStream in = request.getInputStream();//此方法得到所有的提交信息,不仅仅只有内容
// 转换流
InputStreamReader inReaser = new InputStreamReader(in);
// 缓冲流
BufferedReader reader = new BufferedReader(inReaser);
String str = null;
while ((str=reader.readLine()) != null){
System.out.println(str);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
}
注意:在这个逻辑中,我们不做任何处理,只是把http请求体中的内容读取出来,然后输出到控制台。这样,我们可以看到请求从浏览器提交到服务器之后的原始结构。
这样,启动tomcat服务器之后,访问http://localhost:8080/index.jsp
:

输入如下表单,并上传文件:

f1.txt
内容如下:
File Content of f1.txt:
f1 content.
f2.txt
内容如下:
File Content of f2.txt:
f2 content.
点击提交:

页面变成了白屏,因为这里没有写响应页面。这时看下控制台的输出(这里使用的浏览器是苹果电脑自带的Safari浏览器),如下:
------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="username"
TestUser
------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="fileUpload1"; filename="f1.txt"
Content-Type: text/plain
File Content of f1.txt:
f1 content.
------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="fileUpload2"; filename="f2.txt"
Content-Type: text/plain
File Content of f2.txt:
f2 content.
------WebKitFormBoundary7LVglwc6Wn5QHnN9--
这里,我们通过浏览器控制台,查看下请求的包头和请求体部分如下:
请求头:
POST /UploadServletDemo HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7LVglwc6Wn5QHnN9
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-cn
Host: localhost:8080
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Referer: http://localhost:8080/
Content-Length: 505
Cookie: JSESSIONID=1B3F46A29FA1E69116EDA870088DA1D7
请求体:
------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="username"
TestUser
------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="fileUpload1"; filename="f1.txt"
Content-Type: text/plain
------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="fileUpload2"; filename="f2.txt"
Content-Type: text/plain
------WebKitFormBoundary7LVglwc6Wn5QHnN9--
改用chrome浏览器访问:

之后的控制台输出(看着和上面没有什么区别):
------WebKitFormBoundaryFw25OgiIdbAZLcWN
Content-Disposition: form-data; name="username"
TestUser
------WebKitFormBoundaryFw25OgiIdbAZLcWN
Content-Disposition: form-data; name="fileUpload1"; filename="f1.txt"
Content-Type: text/plain
File Content of f1.txt:
f1 content.
------WebKitFormBoundaryFw25OgiIdbAZLcWN
Content-Disposition: form-data; name="fileUpload2"; filename="f2.txt"
Content-Type: text/plain
File Content of f2.txt:
f2 content.
------WebKitFormBoundaryFw25OgiIdbAZLcWN--
为了用于比较,再写一个直接只有简单输入项,不包含文件的表单,提交后,直接看下http请求的输出。
indexForm.jsp
如下:
<%--
Created by IntelliJ IDEA.
User: chengxia
Date: 2021/10/31
Time: 4:40 PM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="/UploadServletDemo" method="post">
上传用户:<input type="text" name="username"><br/>
用户地址:<input type="text" name="useraddress"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
浏览器访问http://localhost:8080/indexForm.jsp
:

控制台输出如下:
username=TestUser&useraddress=TestUserAddress
从这里看出,对于post请求来说,请求中的数据是按照格式存放在请求体中的。只不过如果在表单中指定了enctype="multipart/form-data"
这样的话,请求体中的格式稍微有些复杂。每一个表单项(无论是一般的input,还是文件项)之间,被一个随机的字符串(这个随机的字符串在请求的包头中可以看到,前面的浏览器抓包中也可以看出来,上面例子中是Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7LVglwc6Wn5QHnN9
)分隔开来,每一项中可以看出是简单的输入表单项,还是文件项(如果是文件项,也可以看出文件名)。
2、解析获得上传文件的例子
从上面的例子可以看出上传文件时http请求的格式和结构,实际上,我们可以根据这个结构去解析http请求体,然后得到用户上传的文件。实际中,我们并不用自己去做,有公共的jar包可以解决这个问题,下面是一个演示如何使用的例子。
2.1 需要的jar包
这里需要用到两个jar包:
- commons-io-2.11.0.jar
- commons-fileupload-1.4.jar
下载链接分别如下:
- https://commons.apache.org/proper/commons-io/download_io.cgi
- http://commons.apache.org/proper/commons-fileupload/download_fileupload.cgi
下载对应的jar包之后,放到项目的lib目录下。
2.2 从请求中解析上传的文件
新建一个解析文件的servlet。
com/trial/servlet/FileUploadParseFileServlet.java
:
package com.trial.servlet;
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;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.UUID;
/**
* Created by chengxia on 2021/11/27.
*/
public class FileUploadParseFileServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
DiskFileItemFactory fac = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(fac);
upload.setFileSizeMax(10 * 1024 * 1024);
upload.setSizeMax(20 * 1024 * 1024);
if (upload.isMultipartContent(request)) {
try {
List<FileItem> list = upload.parseRequest(request);
for (FileItem item : list) {
if (item.isFormField()) {
String fileName = item.getFieldName();
String value = item.getString("UTF-8");
System.out.println("普通表单, " + fileName + ":" + value);
} else {
//为了避免上传文件重名,在前面拼接一个随机串
String name = item.getName();
String id = UUID.randomUUID().toString();
name = id + name;
//上传文件放在一个统一的目录
String realPath = getServletContext().getRealPath("/upload");
File uploadDir = new File(realPath);
if (!uploadDir.exists() && !uploadDir.isDirectory()) {
uploadDir.mkdirs();
System.out.println("创建上传文件目录: " + realPath);
} else {
System.out.println("上传文件目录: " + realPath + " 已经存在!");
}
File file = new File(realPath, name);
item.write(file);
System.out.println("文件" + item.getName() + "上传到" +file.getAbsolutePath());
item.delete();
}
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("不处理!");
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
}
WEB-INF/web.xml
中新增servlet定义和url映射:
<servlet>
<servlet-name>FileUploadParseFileServlet</servlet-name>
<servlet-class>com.trial.servlet.FileUploadParseFileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileUploadParseFileServlet</servlet-name>
<url-pattern>/FileUploadParseDemo</url-pattern>
</servlet-mapping>
重新写一个jsp页面将文件上传请求,提交给前面的文件解析servlet处理。
indexParseFile.jsp
:
<%--
Created by IntelliJ IDEA.
User: chengxia
Date: 2021/10/31
Time: 4:40 PM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="/FileUploadParseDemo" enctype="multipart/form-data" method="post">
上传用户:<input type="text" name="username"><br/>
上传文件1:<input type="file" name="fileUpload1"><br/>
上传文件2:<input type="file" name="fileUpload2"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
访问http://localhost:8080/indexParseFile.jsp
:

输入表单域:

点击提交之后,跳转到空白页面:

可以看到控制台输出:
普通表单, username:TestUser
上传文件目录: /Users/chengxia/Developer/Java/JavaWeb/HelloCherry/out/artifacts/HelloCherry_war_exploded/upload 已经存在!
文件f1.txt上传到/Users/chengxia/Developer/Java/JavaWeb/HelloCherry/out/artifacts/HelloCherry_war_exploded/upload/8628411a-5c7e-485f-9318-89a7a00000c3f1.txt
上传文件目录: /Users/chengxia/Developer/Java/JavaWeb/HelloCherry/out/artifacts/HelloCherry_war_exploded/upload 已经存在!
文件f2.txt上传到/Users/chengxia/Developer/Java/JavaWeb/HelloCherry/out/artifacts/HelloCherry_war_exploded/upload/2315e06e-225f-4efa-b575-f45a6d4af830f2.txt
到电脑上对应的目录检查,可以看到文件确实已经上传成功了。
2.3 注意和提示
之前没有引入commons-io-2.11.0.jar
和commons-fileupload-1.4.jarjar
包,upload.parseRequest(req);
报了一个类型不兼容的错,具体提示如下:
The method parseRequest(RequestContext) in the type FileUploadBase is not applicable for the arguments (HttpServletRequest)
原因就在于,默认导入的是:
import org.apache.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
实际要导的是依赖于commons-io-2.11.0.jar
和commons-fileupload-1.4.jarjar
的如下类:
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;
导入之后,报错就解决了。
2.4 补充: 文件上传之后,跳转到结果页面
前面文件上传之后,都是到一个空白页面,因为servlet处理请求之后,没有做页面跳转。这里做下补充,跳到一个文件上传结果页面。
首先,写一个文件上传之后转发到jsp结果页面的servlet。
com/trial/servlet/FileUploadParseFileForwardServlet.java
:
package com.trial.servlet;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Created by chengxia on 2021/11/27.
*/
public class FileUploadParseFileForwardServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
DiskFileItemFactory fac = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(fac);
upload.setFileSizeMax(10 * 1024 * 1024);
upload.setSizeMax(20 * 1024 * 1024);
//记录文件上传信息
String userName = null;
List<String> fileList = new ArrayList<String>();
if (upload.isMultipartContent(request)) {
try {
List<FileItem> list = upload.parseRequest(request);
for (FileItem item : list) {
if (item.isFormField()) {
String fileName = item.getFieldName();
String value = item.getString("UTF-8");
userName = value;
System.out.println("普通表单, " + fileName + ":" + value);
} else {
//为了避免上传文件重名,在前面拼接一个随机串
String name = item.getName();
String id = UUID.randomUUID().toString();
name = id + name;
//上传文件放在一个统一的目录
String realPath = getServletContext().getRealPath("/upload");
File uploadDir = new File(realPath);
if (!uploadDir.exists() && !uploadDir.isDirectory()) {
uploadDir.mkdirs();
System.out.println("创建上传文件目录: " + realPath);
} else {
System.out.println("上传文件目录: " + realPath + " 已经存在!");
}
File file = new File(realPath, name);
item.write(file);
System.out.println("文件" + item.getName() + "上传到" +file.getAbsolutePath());
fileList.add(item.getName());
item.delete();
}
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("不处理!");
}
//跳转到结果页面
request.setAttribute("userName", userName);
request.setAttribute("fileList", fileList);
RequestDispatcher view = request.getRequestDispatcher("indexParseFileResult.jsp");
view.forward(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
}
并在WEB-INF/web.xml
增加servlet定义和url映射:
<servlet>
<servlet-name>FileUploadParseFileForwardServlet</servlet-name>
<servlet-class>com.trial.servlet.FileUploadParseFileForwardServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileUploadParseFileForwardServlet</servlet-name>
<url-pattern>/FileUploadParseForwardDemo</url-pattern>
</servlet-mapping>
再重新写一个提交到这个servlet的文件上传jsp页面。
indexParseFileForward.jsp
:
<%--
Created by IntelliJ IDEA.
User: chengxia
Date: 2021/10/31
Time: 4:40 PM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="/FileUploadParseForwardDemo" enctype="multipart/form-data" method="post">
上传用户:<input type="text" name="username"><br/>
上传文件1:<input type="file" name="fileUpload1"><br/>
上传文件2:<input type="file" name="fileUpload2"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
再写一个文件上传成功后的结果页面。
indexParseFileResult.jsp
:
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %><%--
Created by IntelliJ IDEA.
User: chengxia
Date: 2021/10/31
Time: 4:40 PM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%
String userName = (String)request.getAttribute("userName");
List<String> fileList = (ArrayList<String>)request.getAttribute("fileList");
out.print("<h1>" + "用户: " + userName +" 的如下文件已经上传成功:"+ "</h1>");
for(int i=0; i < fileList.size(); i++){
out.print("<h2>" + fileList.get(i) + "</h2>");
}
%>
<h2></h2>
</body>
</html>
这样,访问http://localhost:8080/indexParseFileForward.jsp
:

填写表单,上传文件:

点击提交
:

到这里,文件上传的例子就演示完毕。
3、文件下载
和文件上传相比,文件下载要容易的多。最简单的,可以直接将文件存储的目录挂到web服务器上,用户直接点击文件路径的链接就可以下载。但是,这样的下载存在两个问题:
- 浏览器会自动解析,比如下载的文件是一个网页,就会字节在浏览器中下载,而不是作为一个文件下载到本地。
- 这种方式所有人都可以下,没法进行权限相关的控制。
因此,还是需要写java代码实现对文件的下载,下面是一个例子。
先写一个提交下载文件名的jsp页面。
indexDownloadFile.jsp
:
<%--
Created by IntelliJ IDEA.
User: chengxia
Date: 2021/10/31
Time: 4:40 PM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="/DownloadServletDemo" method="post">
上传文件名:<input type="text" name="filename" maxlength="500" style="width:400px;"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
然后,写一个接收请求,获取文件名,根据文件名返回文件的servlet。
com/trial/servlet/FileDownloadServlet.java
:
package com.trial.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
/**
* Created by chengxia on 2021/11/27.
*/
public class FileDownloadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String fileName = request.getParameter("filename");//获取要下载的文件名
//第一步:设置响应类型
response.setContentType("application/force-download");//应用程序强制下载
//第二读取文件
String path = getServletContext().getRealPath("/upload/"+fileName);
InputStream in = new FileInputStream(path);
//设置响应头,对文件进行url编码
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename="+fileName);
response.setContentLength(in.available());
//第三步:读文件写入http响应
OutputStream out = response.getOutputStream();
byte[] b = new byte[1024];
int len = 0;
while((len = in.read(b))!=-1){
out.write(b, 0, len);
}
out.flush();
out.close();
in.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
}
在文件WEB-INF/web.xml
中添加servlet定义和url映射:
<servlet>
<servlet-name>FileDownloadServlet</servlet-name>
<servlet-class>com.trial.servlet.FileDownloadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileDownloadServlet</servlet-name>
<url-pattern>/DownloadServletDemo</url-pattern>
</servlet-mapping>
访问http://localhost:8080/indexDownloadFile.jsp
:

输入要下载的文件名(这里用的是前面文件上传例子中的生成的文件),比如af52bc39-06bc-49f3-b511-eab747e3943bf1.txt
。

点击提交
,文件就会作为附件从浏览器中下载。
3.1 文件下载说明
resp.setContentType("application/force-download");
,设置应用程序强制下载。
Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。Content-disposition其实可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,文件直接在浏览器上显示或者在访问时弹出文件下载对话框。
格式说明:
content-disposition = "Content-Disposition" ":" disposition-type *( ";" disposition-parm )
字段说明:
Content-Disposition为属性名
disposition-type是以什么方式下载,如attachment为以附件方式下载
disposition-parm为默认保存时的文件名
服务端向客户端游览器发送文件时,如果是浏览器支持的文件类型,一般会默认使用浏览器打开,比如txt、jpg等,会直接在浏览器中显示,如果需要提示用户保存,就要利用Content-Disposition进行一下处理,关键在于一定要加上attachment
。
网友评论