
2007-12-2
本视频讲解了如何使用最新版本开源的Apache Commons FileUpload 来上传文件以及如何编写文件下载代码.
视频部分代码屏幕出现闪烁, 错位, 不便之处请参考本文中的源码和文档中绿色部分的注释:
// Set factory constraints
factory.setSizeThreshold(yourMaxMemorySize); // 设置最多只允许在内存中存储的数据,单位:字节
factory.setRepository(yourTempDirectory); // 设置一旦文件大小超过getSizeThreshold()的值时数据存放在硬盘的目录(默认可以不用设置)
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// Set overall request size constraint
// 设置允许用户上传文件大小,单位:字节
upload.setSizeMax(yourMaxRequestSize);
友情提示: 下载微软网盘文件时关闭下载工具, 否则你将得到错误的文件, 双击 EXE 会出来 DOS 窗口. 正确操作是点击文件名后能看到显示下载链接和文件大小等信息.
代码: http://cid-519b3f7aa2172030.skydrive.live.com/self.aspx/Public/MyEclipse6Videos/10_JSPFileUploadDownload.zip 132 KB
视频: http://cid-519b3f7aa2172030.skydrive.live.com/self.aspx/Public/MyEclipse6Videos/myeclipse6_10.exe 16分31秒 6.0 MB
内容包括:
1. Apache Commons FileUpload 项目介绍
2. 下载并增加必要的类库
3. 编写文件上传表单 HTML
4. 编写文件上传处理 JSP
5. 编写文件下载JSP
6. 发布并测试
视频截图:
代码:
upload.htm
<%@ page language="java" import="java.util.*" pageEncoding="GBK"%>
<%@page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%>
<%@page import="org.apache.commons.fileupload.disk.DiskFileItemFactory"%>
<%!/**
* 得到文件的短路径, 不包括目录.
* @date 2005-10-18
*
* @param fileName
* 需要处理的文件的名字.
* @return the short version of the file's name.
*/
public static String getShortFileName(String fileName) {
if (fileName != null) {
String oldFileName = new String(fileName);
fileName = fileName.replace('\\\\', '/');
// Handle dir
if (fileName.endsWith("/")) {
int idx = fileName.indexOf('/');
if (idx == -1 || idx == fileName.length() - 1) {
return oldFileName;
} else {
return oldFileName
.substring(idx + 1, fileName.length() - 1);
}
}
if (fileName.lastIndexOf("/") > 0) {
fileName = fileName.substring(fileName.lastIndexOf("/") + 1,
fileName.length());
}
return fileName;
}
return "";
}%>
<%
// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (isMultipart) {
// Create a factory for disk-based file items
org.apache.commons.fileupload.FileItemFactory factory = new DiskFileItemFactory();
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// Parse the request
List /* FileItem */items = upload.parseRequest(request);
// Process the uploaded items
Iterator iter = items.iterator();
while (iter.hasNext()) {
org.apache.commons.fileupload.FileItem item = (org.apache.commons.fileupload.FileItem) iter
.next();
if (item.isFormField()) {
String name = item.getFieldName();
String value = item.getString("GBK");
out.println(name + "=" + value);
} else {
String fieldName = item.getFieldName();//file
String fileName = item.getName();
String contentType = item.getContentType();
boolean isInMemory = item.isInMemory();
long sizeInBytes = item.getSize();
out.println("上传的文件名是:" + fileName);
if (fileName == null || fileName.length() == 0) {
out.println("请选择一个文件来上传");
} else {
| java.io.FileOutputStream fout = new java.io.FileOutputStream( application.getRealPath("upload/" + getShortFileName(fileName))); fout.write(item.get()); fout.close(); } } } } else { out.println("请用文件上传表单来访问这个页面"); } %> |
下载地址
http://commons.apache.org/fileupload/
http://commons.apache.org/io/
用法文档:
http://commons.apache.org/fileupload/using.html
Using FileUpload
FileUpload can be used in a number of different ways, depending upon the requirements of your application. In the simplest case, you will call a single method to parse the servlet request, and then process the list of items as they apply to your application. At the other end of the scale, you might decide to customize FileUpload to take full control of the way in which individual items are stored; for example, you might decide to stream the content into a database.
Here, we will describe the basic principles of FileUpload, and illustrate some of the simpler - and most common - usage patterns. Customization of FileUpload is described elsewhere.
FileUpload depends on Commons IO, so make sure you have the version mentioned on the dependencies page in your classpath before continuing.
How it works
A file upload request comprises an ordered list of items that are encoded according to RFC 1867, "Form-based File Upload in HTML". FileUpload can parse such a request and provide your application with a list of the individual uploaded items. Each such item implements the FileItem interface, regardless of its underlying implementation.
This page describes the traditional API of the commons fileupload library. The traditional API is a convenient approach. However, for ultimate performance, you might prefer the faster Streaming API.
Each file item has a number of properties that might be of interest for your application. For example, every item has a name and a content type, and can provide an InputStream to access its data. On the other hand, you may need to process items differently, depending upon whether the item is a regular form field - that is, the data came from an ordinary text box or similar HTML field - or an uploaded file. The FileItem interface provides the methods to make such a determination, and to access the data in the most appropriate manner.
FileUpload creates new file items using a FileItemFactory. This is what gives FileUpload most of its flexibility. The factory has ultimate control over how each item is created. The factory implementation that currently ships with FileUpload stores the item's data in memory or on disk, depending on the size of the item (i.e. bytes of data). However, this behavior can be customized to suit your application.
Servlets and Portlets
Starting with version 1.1, FileUpload supports file upload requests in both servlet and portlet environments. The usage is almost identical in the two environments, so the remainder of this document refers only to the servlet environment.
If you are building a portlet application, the following are the two distinctions you should make as you read this document:
∙Where you see references to the ServletFileUpload class, substitute the PortletFileUpload class.
∙Where you see references to the HttpServletRequest class, substitute the ActionRequest class.
Parsing the request
Before you can work with the uploaded items, of course, you need to parse the request itself. Ensuring that the request is actually a file upload request is straightforward, but FileUpload makes it simplicity itself, by providing a static method to do just that.
// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
Now we are ready to parse the request into its constituent items.
The simplest case
The simplest usage scenario is the following:
∙Uploaded items should be retained in memory as long as they are reasonably small.
∙Larger items should be written to a temporary file on disk.
∙Very large upload requests should not be permitted.
∙The built-in defaults for the maximum size of an item to be retained in memory, the maximum permitted size of an upload request, and the location of temporary files are acceptable.
Handling a request in this scenario couldn't be much simpler:
// Create a factory for disk-based file items
FileItemFactory factory = new DiskFileItemFactory();
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// Parse the request
List /* FileItem */ items = upload.parseRequest(request);
That's all that's needed. Really!
The result of the parse is a List of file items, each of which implements the FileItem interface. Processing these items is discussed below.
Exercising more control
If your usage scenario is close to the simplest case, described above, but you need a little more control, you can easily customize the behavior of the upload handler or the file item factory or both. The following example shows several configuration options:
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory();
// Set factory constraints
factory.setSizeThreshold(yourMaxMemorySize); // 设置最多只允许在内存中存储的数据,单位:字节
factory.setRepository(yourTempDirectory); // 设置一旦文件大小超过getSizeThreshold()的值时数据存放在硬盘的目录(默认可以不用设置)
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// Set overall request size constraint
// 设置允许用户上传文件大小,单位:字节
upload.setSizeMax(yourMaxRequestSize);
// Parse the request
List /* FileItem */ items = upload.parseRequest(request);
Of course, each of the configuration methods is independent of the others, but if you want to configure the factory all at once, you can do that with an alternative constructor, like this:
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory(
yourMaxMemorySize, yourTempDirectory);
Should you need further control over the parsing of the request, such as storing the items elsewhere - for example, in a database - you will need to look into customizing FileUpload.
Processing the uploaded items
Once the parse has completed, you will have a List of file items that you need to process. In most cases, you will want to handle file uploads differently from regular form fields, so you might process the list like this:
// Process the uploaded items
Iterator iter = items.iterator();
while (iter.hasNext()) {
FileItem item = (FileItem) iter.next();
if (item.isFormField()) {
processFormField(item);
} else {
processUploadedFile(item);
}
}
For a regular form field, you will most likely be interested only in the name of the item, and its String value. As you might expect, accessing these is very simple.
// Process a regular form field
if (item.isFormField()) {
String name = item.getFieldName();
String value = item.getString();
...
}
For a file upload, there are several different things you might want to know before you process the content. Here is an example of some of the methods you might be interested in.
// Process a file upload
if (!item.isFormField()) {
String fieldName = item.getFieldName();
String fileName = item.getName();
String contentType = item.getContentType();
boolean isInMemory = item.isInMemory();
long sizeInBytes = item.getSize();
...
}
With uploaded files, you generally will not want to access them via memory, unless they are small, or unless you have no other alternative. Rather, you will want to process the content as a stream, or write the entire file to its ultimate location. FileUpload provides simple means of accomplishing both of these.
// Process a file upload
if (writeToFile) {
File uploadedFile = new File(...);
item.write(uploadedFile);
} else {
InputStream uploadedStream = item.getInputStream();
...
uploadedStream.close();
}
Note that, in the default implementation of FileUpload, write() will attempt to rename the file to the specified destination, if the data is already in a temporary file. Actually copying the data is only done if the the rename fails, for some reason, or if the data was in memory.
If you do need to access the uploaded data in memory, you need simply call the get() method to obtain the data as an array of bytes.
// Process a file upload in memory
byte[] data = item.get();
...
Resource cleanup
This section applies only, if you are using the DiskFileItem. In other words, it applies, if your uploaded files are written to temporary files before processing them.
Such temporary files are deleted automatically, if they are no longer used (more precisely, if the corresponding instance of java.io.File is garbage collected. This is done silently by an instance of org.apache.commons.io.FileCleaningTracker, which starts a reaper thread.
In what follows, we assume that you are writing a web application. In a web application, resource cleanup is controlled by an instance of javax.servlet.ServletContextListener. In other environments, similar ideas must be applied.
The FileCleanerCleanup
Your web application should use an instance of org.apache.commons.fileupload.FileCleanerCleanup. That's very easy, you've simply got to add it to your web.xml:
... org.apache.commons.fileupload.servlet.FileCleanerCleanup ...
Creating a DiskFileItemFactory
The FileCleanerCleanup provides an instance of org.apache.commons.io.FileCleaningTracker. This instance must be used when creating a org.apache.commons.fileupload.disk.DiskFileItemFactory. This should be done by calling a method like the following:
public static DiskFileItemFactory newDiskFileItemFactory(ServletContext context,
File repository) {
FileCleaningTracker fileCleaningTracker
= FileCleanerCleanup.getFileCleaningTracker(context);
return new DiskFileItemFactory(fileCleaningTracker,
DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD,
repository);
}
Disabling cleanup of temporary files
To disable tracking of temporary files, you may set the FileCleaningTracker to null. Consequently, created files will no longer be tracked. In particular, they will no longer be deleted automatically.
Interaction with virus scanners
Virus scanners running on the same system as the web container can cause some unexpected behaviours for applications using FileUpload. This section describes some of the behaviours that you might encounter, and provides some ideas for how to handle them.
The default implementation of FileUpload will cause uploaded items above a certain size threshold to be written to disk. As soon as such a file is closed, any virus scanner on the system will wake up and inspect it, and potentially quarantine the file - that is, move it to a special location where it will not cause problems. This, of course, will be a surprise to the application developer, since the uploaded file item will no longer be available for processing. On the other hand, uploaded items below that same threshold will be held in memory, and therefore will not be seen by virus scanners. This allows for the possibility of a virus being retained in some form (although if it is ever written to disk, the virus scanner would locate and inspect it).
One commonly used solution is to set aside one directory on the system into which all uploaded files will be placed, and to configure the virus scanner to ignore that directory. This ensures that files will not be ripped out from under the application, but then leaves responsibility for virus scanning up to the application developer. Scanning the uploaded files for viruses can then be performed by an external process, which might move clean or cleaned files to an "approved" location, or by integrating a virus scanner within the application itself. The details of configuring an external process or integrating virus scanning into an application are outside the scope of this document.
Watching progress
If you expect really large file uploads, then it would be nice to report to your users, how much is already received. Even HTML pages allow to implement a progress bar by returning a multipart/replace response, or something like that.
Watching the upload progress may be done by supplying a progress listener:
//Create a progress listener
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);
Do yourself a favour and implement your first progress listener just like the above, because it shows you a pitfall: The progress listener is called quite frequently. Depending on the servlet engine and other environment factory, it may be called for any network packet! In other words, your progress listener may become a performance problem! A typical solution might be, to reduce the progress listeners activity. For example, you might emit a message only, if the number of megabytes has changed:
//Create a progress listener
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.");
}
}
};
What's next
Hopefully this page has provided you with a good idea of how to use FileUpload in your own applications. For more detail on the methods introduced here, as well as other available methods, you should refer to the JavaDocs.
The usage described here should satisfy a large majority of file upload needs. However, should you have more complex requirements, FileUpload should still be able to help you, with it's flexible customization capabilities.
RFC1867协议介绍
RFC1867协议主要是在HTTP协议的基础上为INPUT标签增加了file属性,同时限定了Form的method必须为POST,ENCTYPE必须为multipart/form-data。当然还增加了一些与此相关属性,但都不是很重要,我们在此不作讨论。
在一般的基于Web的程序中,我们往往使用标签,该标签在被浏览器解析后会产生一个文本框和一个浏览按钮,单击浏览按钮会出现系统的文件选择框。
2. 执行上传及标签的一些特性
在上图选择相应的文件,按Upload按钮即可把选择的文件上传到服务器(服务器端可用JspSmartUpload等组件接受文件)。归根结底上传的所有操作都是由浏览器作的,用户所做的只是简单地选择了一下文件而已,接下来的问题是,如何能把一个目录中所有的文件实现一次性上传?
(1) 因为目录下的文件数量是不定的,因此我们基本不可能通过增加多个标签的方式来解决问题。
(2) 如果在Jsp中我们可以考虑以下方式来解决:通过Jsp动态创建标签,并使所创建的标签不可见。把每个标签的Value属性设置为每个文件的路径。在按Upload时再实行一次性上传。在我们试验了之后就会发现,对的Value属性赋值是徒劳的行为,因为RFC1867协议并没有要求浏览器的实现者一定实现Value属性,而IE恰好忽略了Value属性。
即以下代码将是徒劳的(IE中)
