简介
Commons FileUpload可以轻松地为web应用程序添加强大,高性能的文件上传功能。Servlet3.0之前的web应用程序需要使用Commons FileUpload组件上传文件,但是从Servlet3.0开始,文件上传就成了一个内置的功能。文件上传时,需要使用POST方法提交HTTP请求,并且内容类型(Content-Type)为 multipart/form-data。
enctype
表单的enctype属性表示在发送到服务器之前应该如何对表单数据进行编码,默认值是 application/x-www-form-urlencoded。另一个重要的值就是 multipart/form-data。
application/x-www-form-urlencoded
发送到服务器的HTTP消息的正文本质上是一个大的查询字符串,字符串中的名称/值对被 & 分隔,并且名称与值由 = 分隔。如果值包含非字母数字的字符,那么该字符将被“%HH”取代,即一个百分比符号和两个十六进制数字表示的字符串。HH与字符集有关,如果 Content-Type = application/x-www-form-urlencoded;charset=utf-8,那么该字符将首先被编码为UTF-8字节数组,再将每个字节转换为HH。特别要注意,空格会转换为 +。
其实用 application/x-www-form-urlencoded 来上传文件也未尝不可,只是文件内容必须被序列化为字符串,服务端再将该字符串反序列化。
例如:
key1=%E7%AD%96&key2=abcd1234。
multipart/form-data
不对字符编码,在使用包含文件上传控件的表单时,必须使用该值。
例如:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:755
Content-Type:multipart/form-data; boundary=-----------------------------65982022822840
Cookie:JSESSIONID=wg5h37bt3rcr1dcpes14s42jz
Host:localhost:9443
Origin:https://localhost:9443
Referer:https://localhost:9443/ice-web1/test/test1
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
-----------------------------65982022822840
Content-Disposition: form-data; name="userName"
匿名
-----------------------------65982022822840
Content-Disposition: form-data; name="userAge"
20
-----------------------------65982022822840
Content-Disposition: form-data; name="uploadFile"; filename="测试文件"
Content-Type: application/octet-stream
文件内容xxxxx
-----------------------------65982022822840--
发送到服务器的HTTP消息的正文没有被编码,而是Content-Type请求头中的boundary表示的边界符分隔成几个部分。Content-Disposition 是 MIME 协议的扩展,可以用于文件上传和下载。上传时可以表示参数的名称,如上。下载时表示默认的文件名称,例如 Content-Disposition: attachment; filename=FileName.txt。
文件上传页面
<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<html>
<style>
.progress {
width: 260px;
height: 20px;
border: 1px solid white;
border-radius: 20px;
overflow: hidden;
}
.step {
height: 100%;
width: 0;
background: dodgerblue;
}
</style>
<script>
function upload() {
var file1 = document.getElementById("file1").files[0];
var file2 = document.getElementById("file2").files[0];
var remark = document.getElementById("remark");
if (file1 === undefined || file1 === null) {
alert("请选择文件1");
return;
}
if (file2 === undefined || file2 === null) {
alert("请选择文件2");
return;
}
if (remark.value === null || remark.value === "") {
alert("请输入备注");
return;
}
var data = new FormData(document.getElementById('form'));
var xhr = new XMLHttpRequest();
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
var percent = event.loaded / event.total * 100 + '%';
document.querySelector('.step').style.width = percent;
document.getElementById("progressPercent").innerHTML = percent;
}
}
xhr.onload = function (event) {
alert(xhr.responseText);
};
xhr.open("post", "upload");
xhr.send(data);
}
</script>
<body>
<title>index</title>
<div>
<form id="form" enctype="multipart/form-data">
<fieldset>
<legend>上传文件</legend>
文件1:<input type="file" id="file1" name="file"/> <br/>
文件2:<input type="file" id="file2" name="file"/> <br/>
备注:<input type="text" id="remark" name="remark"><input type="button" onclick="upload()" value="上传">
</fieldset>
</form>
</div>
<div class='progress'>
<div class="step"></div>
</div>
<div id="progressPercent">0%</div>
</body>
</html>
Apache Commons FileUpload 1.3.3
Apache Commons FileUpload提供了两组API,传统API和流式API。传统API假定文件项必须在用户实际可访问之前存储在某个位置,这种方法很方便,但是它占用内存且耗时比较长。
传统API文件在被用户读取前,必须等待被保存在内存或者硬盘中(临时文件),这种方法非常简单,但是另一方面却带来了内存和时间上的额外开销。流式API更佳轻量级,它可以让你牺牲一点便利性以换得理想的性能,文件可以直接从网络输入流中获取。
传统API
web.xml
<web-app>
<display-name>Archetype Created Web Application</display-name>
<listener>
<!--创建临时文件清理跟踪器,用于跟踪临时文件,并且在垃圾回收器回收DiskFileItem时删除-->
<listener-class>
org.apache.commons.fileupload.servlet.FileCleanerCleanup
</listener-class>
</listener>
</web-app>
CommonsFileUploadServlet.java
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.FileCleanerCleanup;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileCleaningTracker;
import org.apache.commons.io.FileUtils;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.List;
@WebServlet(name = "fileUploadServlet", urlPatterns = {"/upload"})
public class CommonsFileUploadServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
HttpServletResponse httpServletResponse = (HttpServletResponse) res;
httpServletResponse.setContentType("text/html; charset=UTF-8");
try {
//进度监听器
ProgressListener progressListener = new ProgressListener() {
private long megaBytes = -1;
/**
* @param pBytesRead 已经读取的字节数
* @param pContentLength 总字节数
* @param pItems 字段编号
*/
@Override
public void update(long pBytesRead, long pContentLength, int pItems) {
//减少通知次数,每接收1M通知一次
long mBytes = pBytesRead / (1024 * 1024);
if (megaBytes == mBytes) {
return;
}
megaBytes = mBytes;
System.out.println("正在读取item:" + pItems);
if (pContentLength == -1) {
System.out.println("到目前为止," + pBytesRead + "字节已读");
} else {
System.out.println("到目前为止," + pBytesRead + "/" + pContentLength
+ "字节已读");
}
}
};
//获取临时文件清理跟踪器
FileCleaningTracker fileCleaningTracker = FileCleanerCleanup.getFileCleaningTracker(httpServletRequest.getServletContext());
//为基于磁盘的文件项创建工厂
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
//设置尺寸阈值,单位字节,超过设定值则文件临时写入磁盘,否则保存在内存
diskFileItemFactory.setSizeThreshold(1024 * 1024);
//设置文件写入磁盘时临时保存的目录
diskFileItemFactory.setRepository(new File("E:/文件上传测试"));
//设置临时文件清理跟踪器,如果设置为null,将不再对临时文件进行跟踪
diskFileItemFactory.setFileCleaningTracker(fileCleaningTracker);
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
//设置允许上传的单个文件最大尺寸,单位字节,-1代表不限制,参数类型是long
servletFileUpload.setFileSizeMax(1024 * 1024 * 10L);
//设置整个请求的大小的最大值,单位字节,-1代表不限制,参数类型是long
servletFileUpload.setSizeMax(1024 * 1024 * 100L);
//设置进度监听器
servletFileUpload.setProgressListener(progressListener);
//设置读取每个部分的请求头的字符集
servletFileUpload.setHeaderEncoding("UTF-8");
boolean isMultipartContent = ServletFileUpload.isMultipartContent(httpServletRequest);
if (isMultipartContent) {
//开始上传文件并解析请求,按顺序获取各个表单项
List<FileItem> fileItems = servletFileUpload.parseRequest(httpServletRequest);
if (fileItems != null) {
for (FileItem fileItem : fileItems) {
//如果当前表单项是字段
if (fileItem.isFormField()) {
//获取字段名
String fieldName = fileItem.getFieldName();
//获取字段值
String value = fileItem.getString("UTF-8");
System.out.println("请求字段:" + fieldName + "=" + value);
}
//如果当前表单项是文件
else {
DiskFileItem diskFileItem = (DiskFileItem) fileItem;
//获取字段名
String fieldName = diskFileItem.getFieldName();
//获取浏览器提供的原始文件名
String fileName = diskFileItem.getName();
//获取文件MIME类型
String contentType = diskFileItem.getContentType();
//获取文件大小,单位字节
long sizeInBytes = diskFileItem.getSize();
//判断文件是否存储在内存中
boolean isInMemory = diskFileItem.isInMemory();
File storeLocation = diskFileItem.getStoreLocation();
System.out.println("请求字段:" + fieldName);
System.out.println("原始文件名:" + fileName);
System.out.println("MIME类型:" + contentType);
System.out.println("文件大小(字节):" + sizeInBytes);
System.out.println("文件临时保存在内存中?:" + isInMemory);
System.out.println("文件临时保存的路径:" + storeLocation.getAbsolutePath());
//等同于diskFileItem.write(new File("E:/文件上传测试/" + fileName))
try (InputStream inputStream = diskFileItem.getInputStream()) {
FileUtils.copyInputStreamToFile(inputStream, new File("E:/文件上传测试/" + fileName));
}
//手动删除临时文件
diskFileItem.delete();
}
}
}
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println("上传成功");
}
}
} catch (Exception e) {
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println(e.getMessage());
}
e.printStackTrace();
}
}
}
需要注意几点:
1.临时文件可以手动删除,也创建临时文件清理跟踪器自动删除,或者同时使用这两者方式。
2.进度监听器可能会降低性能,比如update方法中发送网络数据包等,所以最好还是降低通知频率。
3.如果上传大文件,需要配置tomcat的Connector的maxSwallowSize属性为负数,否则客户端不会接收到响应。
流式API
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
@WebServlet(name = "fileUploadServlet", urlPatterns = {"/upload"})
public class CommonsFileUploadStreamServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
HttpServletResponse httpServletResponse = (HttpServletResponse) res;
httpServletResponse.setContentType("text/html; charset=UTF-8");
try {
//进度监听器
ProgressListener progressListener = new ProgressListener() {
private long megaBytes = -1;
/**
* @param pBytesRead 已经读取的字节数
* @param pContentLength 总字节数
* @param pItems 字段编号
*/
@Override
public void update(long pBytesRead, long pContentLength, int pItems) {
//减少通知次数,每接收1M通知一次
long mBytes = pBytesRead / (1024 * 1024);
if (megaBytes == mBytes) {
return;
}
megaBytes = mBytes;
System.out.println("正在读取item:" + pItems);
if (pContentLength == -1) {
System.out.println("到目前为止," + pBytesRead + "字节已读");
} else {
System.out.println("到目前为止," + pBytesRead + "/" + pContentLength
+ "字节已读");
}
}
};
//为基于磁盘的文件项创建工厂
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
//设置允许上传的单个文件最大尺寸,单位字节,-1代表不限制,参数类型是long
servletFileUpload.setFileSizeMax(1024 * 1024 * 10L);
//设置整个请求的大小的最大值,单位字节,-1代表不限制,参数类型是long
servletFileUpload.setSizeMax(1024 * 1024 * 100L);
//设置进度监听器
servletFileUpload.setProgressListener(progressListener);
//设置读取每个部分的请求头的字符集
servletFileUpload.setHeaderEncoding("UTF-8");
boolean isMultipartContent = ServletFileUpload.isMultipartContent(httpServletRequest);
if (isMultipartContent) {
//开始上传文件并解析请求,按顺序获取各个表单项
FileItemIterator fileItemIterator = servletFileUpload.getItemIterator(httpServletRequest);
while (fileItemIterator.hasNext()) {
FileItemStream fileItemStream = fileItemIterator.next();
String name = fileItemStream.getFieldName();
if (fileItemStream.isFormField()) {
try (InputStream inputStream = fileItemStream.openStream()) {
System.out.println("表单字段:" + name + ",值:"
+ Streams.asString(inputStream, "utf-8"));
}
} else {
System.out.println("请求字段:" + fileItemStream.getFieldName());
System.out.println("原始文件名:" + fileItemStream.getName());
System.out.println("MIME类型:" + fileItemStream.getContentType());
try (
//文件输出流
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
new FileOutputStream("E:/文件上传测试/" + fileItemStream.getName()));
//文件输入流
InputStream inputStream = new BufferedInputStream(fileItemStream.openStream());) {
byte[] bytes = new byte[1024 * 8];
int n;
while (-1 != (n = inputStream.read(bytes))) {
bufferedOutputStream.write(bytes, 0, n);
}
}
}
}
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println("上传成功");
}
}
} catch (Exception e) {
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println(e.getMessage());
}
e.printStackTrace();
}
}
}
Servlet
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
/**
* 必须使用@MultipartConfig注解标注Servlet
* maxFileSize表示允许上传的文件大小的最大值,单位字节,-1表示无限制
* maxRequestSize表示整个请求大小的最大值,单位字节,-1表示无限制
* fileSizeThreshold表示尺寸阈值,单位字节,超过设定值则文件临时写入磁盘,否则保存在内存
* location表示临时文件的目录
*/
@WebServlet(name = "servletFileUploadServlet", urlPatterns = {"/upload"})
@MultipartConfig(maxFileSize = 1024 * 1024 * 10, maxRequestSize = 1024 * 1024 * 100, fileSizeThreshold = 1024 * 1024, location = "E:/文件上传测试/")
public class ServletFileUploadServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
httpServletRequest.setCharacterEncoding("UTF-8");
HttpServletResponse httpServletResponse = (HttpServletResponse) res;
httpServletResponse.setContentType("text/html; charset=UTF-8");
Collection<Part> parts = httpServletRequest.getParts();
try {
parts.forEach(part -> {
//如果part.getContentType() == null,就代表这是个表单字段,否则就是文件
if (part.getContentType() == null) {
//获取字段名
String fieldName = part.getName();
//获取字段值
String value = httpServletRequest.getParameter(fieldName);
System.out.println("请求字段:" + fieldName + "=" + value);
} else {
System.out.println("请求字段:" + part.getName());
System.out.println("原始文件名:" + part.getSubmittedFileName());
System.out.println("MIME类型:" + part.getContentType());
System.out.println("文件大小(字节):" + part.getSize());
try {
part.write("E:/文件上传测试/" + part.getSubmittedFileName());
//手动删除临时文件
part.delete();
} catch (IOException e) {
e.printStackTrace();
}
}
});
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println("上传成功");
}
} catch (Exception e) {
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println(e.getMessage());
}
e.printStackTrace();
}
}
}
- Servlet学习:(三)Servlet3.0 上传文件
转: Servlet学习:(三)Servlet3.0 上传文件 2018年08月03日 11:57:58 iDark_CSDN 阅读数:362 一.注意事项 客户端(浏览器) 表单的提交方法必须是 ...
- Servlet 3.0对上传的支持
Servlet 2.5 进行上传 首先对表单的要求 ->method ="post" ->enctype="multipart/form-d ...
- asp.net FileUpload上传文件夹并检测所有子文件
1.在FileUpload控件添加一个属性 webkitdirectory=""就可以上传文件夹了 <asp:FileUpload ID="FileUpload1& ...
- JSP Servlet学习笔记——使用fileupload上传文件
关键代码如下: index.jsp <body> <center> <h3>文件上传</h3> <font color="red&quo ...
- 上传文件出错:org.apache.commons.fileupload.FileUploadBase$IOFileUploadException: Processing of multipart/form-data request failed. Stream ended unexpectedly
最近做一个web项目中有上传文件的功能,已经写出并在本地和部署到服务器上测试了好几个文件上传都没问题(我用的是tomcat).后来又上传了一个700多K的文件(前边的都是不足600K的,并且这个wor ...
- Servlet异步上传文件
这里需要用到插件ajaxfileupload.js,jar包:commons-fileupload-1.3.2.jar,commons-io-2.5.jar 注意红色部分的字!!!! 1.创建一个we ...
- Servlet 实现上传文件以及同时,写入xml格式文件和上传
package com.isoftstone.eply.servlet; import java.io.BufferedReader; import java.io.BufferedWriter; i ...
- Servlet上传文件
Servlet上传文件 1.准备工作 (1)利用FileUpload组件上传文件,须要到apache上下载commons-fileupload-1.3.1.jar 下载地址:http://common ...
- 一个用于上传文件的servlet
1.jsp页面操作文件: <%@ page language="java" import="java.util.*" pageEncoding=" ...
随机推荐
- 04.简单了解一下Redis企业级数据备份方案
一.企业级的持久化的配置策略 (1)每隔1分钟去检查如果超过10000个可以变更,则生成一个快照.RDB最多丢1分钟的数据. save 60 10000 (2)AOF一定要打开,fsync,every ...
- 注册github时总卡在第一步无法验证的解决办法
从github官网可以看出问题所在,所以造成这一问题的极大可能就是浏览器的问题. 最简单的方法就是换手机浏览器进行注册
- element-ul二次封装table表格
在项目中el的表格使用的地方太多了,若不进行封装,使用的时候页面会显得非常的冗余且难以维护,有时表格样式还不能做到一致:今天分享一个在工作中封装的表格 由于大多代码都在页面有介绍,就不在外面解释了 一 ...
- [ASP.NET Core开发实战]开篇词
前言 本系列课程文章主要是学习官方文档,再输出自己学习心得,希望对你有所帮助. 课程大纲 本系列课程主要分为三个部分:基础篇.实战篇和部署篇. 希望通过本系列课程,能让大家初步掌握使用ASP.NET ...
- 实际项目中遇到EF实体类的操作问题及操作方法
之前一直做ASP,都是直接写数据库操作语句,但是现在使用linq或者EF了,具体数据库操作不会了,遇到几个问题,然后经过查找资料解决了,记录一下. 一.遇到序列化问题 遇到循环引用问题,我的项目是一个 ...
- MySQL索引凭什么能让查询效率提高这么多?
点赞再看,养成习惯,微信搜一搜[三太子敖丙]关注这个喜欢写情怀的程序员. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系 ...
- 如何建立一个完美的 Python 项目
原文地址:How to set up a perfect Python project 原文作者:Brendan Maginnis 译者:HelloGitHub-丫丫 校对者:HelloGitHub- ...
- tcpdump 命令格式
tcpdump 命令格式 tcpdump [选项] [表达式 1. 选项 常用选项: -i : 网卡名: 指定网卡,默认抓取系统第一个网卡 -n : 对地址以数字方式显示 -nn :对地址端口以数字方 ...
- 高可用集群corosync+pacemaker之pcs安装使用
前文我们介绍了高可用集群corosync+pacemaker的集群管理工具crmsh的常用命令的使用,回顾请参考https://www.cnblogs.com/qiuhom-1874/tag/crms ...
- UBer面向领域的微服务体系架构实践
介绍 最近,人们对面向服务的系统架构和微服务系统架构的缺点进行了大量的讨论.尽管仅仅在几年前,由于微服务体系架构提供了许多好处,如独立部署的灵活性.明确的所有权.提高系统稳定性以及更好地分离关注点等, ...