zoukankan      html  css  js  c++  java
  • 使用Commons FileUpload 1.3.3和Servlet 3.0上传文件

    简介
    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 {
    260px;
    height: 20px;
    border: 1px solid white;
    border-radius: 20px;
    overflow: hidden;
    }
     
    .step {
    height: 100%;
    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();
    }
    }
    }
     
  • 相关阅读:
    LUA表的引用理解
    UNITY 打包安卓APK
    UNITY打包问题
    U3D 打包时找不到tag的问题
    break prefab instance的原理
    C#调用LUA函数
    LUA 利用#遍历表的问题
    U3D笔记11:47 2016/11/30-15:15 2016/12/19
    Unity3D 预设打包的注意事项
    回调 和 覆盖
  • 原文地址:https://www.cnblogs.com/gjb724332682/p/9445131.html
Copyright © 2011-2022 走看看