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();
    }
    }
    }
     
  • 相关阅读:
    堆栈学习
    需要阅读的书籍
    Rust Book Lang Ch.19 Fully Qualified Syntax, Supertraits, Newtype Pattern, type aliases, never type, dynamic sized type
    Rust Lang Book Ch.19 Placeholder type, Default generic type parameter, operator overloading
    Rust Lang Book Ch.19 Unsafe
    Rust Lang Book Ch.18 Patterns and Matching
    Rust Lang Book Ch.17 OOP
    Rust Lang Book Ch.16 Concurrency
    Rust Lang Book Ch.15 Smart Pointers
    HDU3966-Aragorn's Story-树链剖分-点权
  • 原文地址:https://www.cnblogs.com/gjb724332682/p/9445131.html
Copyright © 2011-2022 走看看