Commons FileUpload

喜你入骨 提交于 2019-11-30 07:19:51

1    概述

Commons FileUpdate包很容易为你的Servlet和web应用程序添加健壮的、高性能的文件上传功能。
FileUpload解析遵循RFC 1876(在HTML中基于表单的文件上传)HTTP请求。即,如果一个HTTP请求使用POST方法提交,并
且使用“multipart/form-data”的内容类型,然后FileUpload解析请求,使结果易于调用者使用。
从1.3开始,FileUpload处理RFC 2047编码头值。

2    用户指南

2.1    使用FileUpload

FileUpload能使用大量不同的方式,依赖于你的应用程序的需求。在简单的情况下,你将调用简单的方法解析Servlet请求,
然后处理item列表作为它们应用到你的应用程序。在天平的另一端,你可能决定自定义FileUpload充分的控制单个item存储
的方式;例如,你可能决定将流的内容写入数据库。
这里,我们将描述FileUpload的基本原则,并阐述一些更简单的——并且更通用的——使用模式。
FileUpload依赖于Commons IO。

2.2    工作原理

一个文件上传请求包含一个根据RFC 1867(在HTML中基于表单的文件上传)编码的有序item列表。FileUpload能解析这么一个请求,并提供给你的应用程序单独的上传item列表。每个item实现FileItem接口,不管它底层实现。
本文描述Commons FileUpload类库的传统API。传统API是便利方式。然而,对于最终性能,你可能喜欢快速的流API。
每个文件item有你的应用程序可能感兴趣的许多属性。例如,每个item有一个名字和内容类型,并提供一个InputStream访问
它的数据。换句话说,你可能需要处理不同的item,依赖于是否item是常规表单——即,数据来自原始文本框或类似于HTML字段——或上传文件。FileItem接口提供方法做出这一决定,并以最适当的方式访问数据。
FileUpload使用FileItemFactory创建新文件item。这给FileUpload更大的灵活性。工厂最终控制每个item如何创建。工厂实
现当前过渡FileUpload存储item的数据在内存或磁盘,依赖于item的大小(例如,数据的字节)。然而,该行为能自定义适合你的应用程序。

2.3    Servlet和Portlet

从1.1开始,FileUpload支持Servlet和Portlet环境的文件上传请求。在两种环境中的使用几乎相同,因此,本文只讲述
Servlet环境。
如果你构建一个Portlet应用程序,以下两点不同你应该阅读API文档:

ServletFileUpload类->PortletFileUpload类
HttpServletRequest类->ActionRequest类

2.4    解析请求

你处理上传item之前,当然,你需要解析请求。确保,请求是一个简单的真实文件上传请求,但FileUpload使其变得简单,
通过提供一个静态方法做到这一点。

// 检查,我们有一个文件上传请求
boolean isMultipart = ServletFileUpload.isMultipartContent(request);

现在,我们准备解析请求为item。

2.5    最简单的情况

以下是最简单的使用情景:

  • 上传item应该保留在内容中,只要它相当小。

  • 大型item应该写入磁盘上的临时文件。

  • 非常大的上传请求应该不被允许。

  • 内置默认的保存在内存中的一个item的最大大小,一个上传请求的最大大小,和可接受的临时文件位置。

在这种情况下,处理请求不应该更简单:

// 创建基于磁盘文件item的工厂
DiskFileItemFactory factory = new DiskFileItemFactory();

// 配置一个仓库(确保一个安全的临时位置被使用)
ServletContext servletContext = this.getServletConfig().getServletContext();
File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
factory.setRepository(repository);

// 创建一个新的文件上传处理器
ServletFileUpload upload = new ServletFileUpload(factory);

// 解析请求
List<FileItem> items = upload.parseRequest(request);

这是我们的所有需要。
解析的结果是文件item List,每个FileItem接口的实现。

2.6    练习更多控制

如果你使用的情景是上面描述的最简单的情况,但是你需要一点更多的控制,你能易于定制上传处理器或文件item工厂的行为。
以下例子显示各种配置选项:

// 创建基于磁盘文件item的工厂
DiskFileItemFactory factory = new DiskFileItemFactory();

// 设置工厂约束
factory.setSizeThreshold(yourMaxMemorySize);
factory.setRepository(yourTempDirectory);

// 创建一个新的文件上传处理器
ServletFileUpload upload = new ServletFileUpload(factory);

// 设置全部请求大小约束
upload.setSizeMax(yourMaxRequestSize);

// 解析请求
List<FileItem> items = upload.parseRequest(request);

当然,每个配置方法独立于其它方法,但如果你想要同时配置工厂,你能使用构造函数,像这样:

// 创建一个基于磁盘的文item工厂
DiskFileItemFactory factory = new DiskFileItemFactory(yourMaxMemorySize, yourTempDirectory);

你应该需要在请求解析上更高级的控制,例如,在其它地方存储item——例如,在数据库中。

2.7    处理上传item

一旦解析完成,你将有一个需要处理的文件item List。在大多数情况下,你将想要处理不同于普通表单字段的文件上传,
因此你可以像这样处理:

// 处理上传item
Iterator<FileItem> iter = items.iterator();
while (iter.hasNext()) {
    FileItem item = iter.next();

    if (item.isFormField()) {
        processFormField(item);
    } else {
        processUploadedFile(item);
    }
}

对于表单字段,你将只对item名称和它的String值感兴趣。正如你料想的,访问这些非常简单。

// 处理常规表单字段
if (item.isFormField()) {
    String name = item.getFieldName();
    String value = item.getString();
    ...
}

对于文件上传,有几种不同的东西,你可能会想知道你处理之前的内容。下面是一些你可能感兴趣的方法示例。

// 处理文件上传
if (!item.isFormField()) {
    String fieldName = item.getFieldName();
    String fileName = item.getName();
    String contentType = item.getContentType();
    boolean isInMemory = item.isInMemory();
    long sizeInBytes = item.getSize();
    ...
}

使用上传文件,你通常不想通过内存要访问它们,除非它们很小,或者你没有其它选择。你宁愿将想要处理的内容作为一个流,或写入整个文件到它的最终位置。FileUpload提供简单的方法完成这两种。

// 处理文件上传
if (writeToFile) {
    File uploadedFile = new File(...);
    item.write(uploadedFile);
} else {
    InputStream uploadedStream = item.getInputStream();
    ...
    uploadedStream.close();
}

注意,在默认的FileUpload实现中,如果数据已经在临时文件中,write()将试图重命名文件到指定目标。
如果你需要访问内存中的上传数据,你需要简单调用get()方法获取数据作为byte数组。

// 处理内存中的文件上传
byte[] data = item.get();
...

2.8    资源清理

如果你使用DiskFileItem,你上传的文件处理之前被写入临时文件。这些临时文件被自动删除,如果他们不再使用,如果相应的java.io.File实例被垃圾回收。通过org.apache.commons.io.FileCleaner类默默开启一个清理线程。这个清理线程应该被停止,如果它不再需要。在Servlet环境中,通过使用一个特定Servlet上下文监听器(FileCleanerCleanup)来完成。为了这么做,在你的web.xml中添加:

<web-app>
  ...
  <listener>
    <listener-class>
      org.apache.commons.fileupload.servlet.FileCleanerCleanup
    </listener-class>
  </listener>
  ...
</web-app>

2.9    创建DiskFileItemFactory

FileCleanerCleanup提供一个org.apache.commons.io.FileCleaningTracker实例。当创建org.apache.commons.fileupload.disk.DiskFileItemFactory时必须使用该实例。

public static DiskFileItemFactory newDiskFileItemFactory(ServletContext context, File repository) {
    FileCleaningTracker fileCleaningTracker = FileCleanerCleanup.getFileCleaningTracker(context);
    DiskFileItemFactory factory = new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, repository);
    factory.setFileCleaningTracker(fileCleaningTracker);
    return factory;
}

2.10    禁用临时文件清理

为了禁用临时文件跟踪,你可以设置FileCleaningTracker为null。因此,创建文件将不再被跟踪。尤其,它们将不再被自动删除。

2.11    集成病毒扫描

病毒扫描和web容器运行在相同的系统中对于使用FileUpload的应用程序会导致一些意想不到的行为。本文描述一些你可能遇到的行为,并且提供一些如何处理它们的方式。
FileUpload的默认实现将导致上传的item超过某一阀值被写入磁盘。就这样一个文件关闭,系统上的任何病毒扫描程序会来检查它,并且可能隔离文件——即,把它移动到一个不能导致文件的地方。当然,这将给应用程序开发人员一个惊喜,因为上传文件item不再有效。话句话说,上传item在相同阀值下将保存在内容中,因此将不会被病毒扫描器发觉。这允许一个病毒可能以某种形式被保留(尽管从不写入磁盘,病毒扫描程序应该定位和检查它)。
一个常用的解决办法是将所有上传的文件放置系统的一个目录中,并配置病毒扫描器忽略该目录。这将确保应用程序不会丢掉文件,然而脱离病毒扫描程序的职责范围,对上传的文件进行病毒扫描可以由外部处理,移动干净或以清理了文件到“批准”位置,或在应用程序中集成病毒扫描程序。

2.12    查看进程

如果你期望真实的大文件上传,那么它将很好的报告给你的用户已经接收了多少。每个HTML页面允许实现一个进度条,通过返回一个multipart/replace响应或这样的东西。

查看上传进度可以通过提供一个进度监听器完成:

// 创建一个进度监听器
ProgressListener progressListener = new ProgressListener(){
   public void update(long pBytesRead, long pContentLength, int pItems) {
       System.out.println("We are currently reading item " + pItems);
       if (pContentLength == -1) {
           System.out.println("So far, " + pBytesRead + " bytes have been read.");
       } else {
           System.out.println("So far, " + pBytesRead + " of " + pContentLength
                              + " bytes have been read.");
       }
   }
};
upload.setProgressListener(progressListener);

帮自己一个忙,实现你的第一个进度监听器,像上面这样,因为它显示了一个陷阱:进度监听器被频繁调用。依赖于Servlet引擎和其它环境工厂,它可能调用任何网络包!换句话说,你的进程监听器可能成为性能问题!一个典型的解决方法是减少进度监听器的活跃度。例如,你可以只在兆字节数量改变时发出消息:

// 创建进度监听器
ProgressListener progressListener = new ProgressListener(){
   private long megaBytes = -1;
   public void update(long pBytesRead, long pContentLength, int pItems) {
       long mBytes = pBytesRead / 1000000;
       if (megaBytes == mBytes) {
           return;
       }
       megaBytes = mBytes;
       System.out.println("We are currently reading item " + pItems);
       if (pContentLength == -1) {
           System.out.println("So far, " + pBytesRead + " bytes have been read.");
       } else {
           System.out.println("So far, " + pBytesRead + " of " + pContentLength
                              + " bytes have been read.");
       }
   }
};

3    流API

3.1    为什么使用流?

假设,文件item实际被用户访问之前必须存储在某一地方。这种方式很方便,因为它允许易于访问item内容。话句话说,
它消耗内存和时间。
流API允许你牺牲一点点便利,优化性能和较低的内存配置。然而,流API更轻量级,因此易于理解。

3.2    工作原理

FileUpload类用于访问表单字段和字段顺序,它们已经通过客户端发送。然而,FileItemFactory完全被忽略。

3.3    解析请求

首先,不要忘记确保,请求实际是一个文件上传请求。这通常是通过使用相同的静态方法。

// 检查我们有一个文件上传请求
boolean isMultipart = ServletFileUpload.isMultipartContent(request);

现在我们已经准备好解析请求:

// 创建新文件上传处理器
ServletFileUpload upload = new ServletFileUpload();

// 解析请求
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext()) {
    FileItemStream item = iter.next();
    String name = item.getFieldName();
    InputStream stream = item.openStream();
    if (item.isFormField()) {
        System.out.println("Form field " + name + " with value "
            + Streams.asString(stream) + " detected.");
    } else {
        System.out.println("File field " + name + " with file name "
            + item.getName() + " detected.");
        // 处理输入流
        ...
    }
}






易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!