一.概述
1.位置 : org.apache.commons.fileupload.FileUploadBase.java
2.此类parseRequest(RequestContext ctx)方法用于解析上传文件,返回一个List<FileItem>
二.解析请求报文流程
三.内部类 - FileItemStreamImpl
3.1 接口FileItemHeadersSupport
package org.apache.commons.fileupload;
public interface FileItemHeadersSupport {
FileItemHeaders getHeaders();
void setHeaders(FileItemHeaders headers);
}
3.2 接口
package org.apache.commons.fileupload;
import java.io.IOException;
import java.io.InputStream;
public interface FileItemStream extends FileItemHeadersSupport {
public static class ItemSkippedException extends IOException {
private static final long serialVersionUID = -7280778431581963740L;
}
InputStream openStream() throws IOException;
String getContentType();
String getName();
String getFieldName();
boolean isFormField();
}
3.3 实现类FileItemStreamImpl
private class FileItemStreamImpl implements FileItemStream {
// 当前FileItem的内容类型,FileItem是表单字段时为null
private final String contentType;
// FileItem的字段名
private final String fieldName;
// 上传文件名,当FileItem是表单字段时为null
private final String name;
// 当前FileItem是否是表单字段
private final boolean formField;
// 当前FileItem的输入流
private final InputStream stream;
// 当前FileItem是否已经打开
private boolean opened;
// 当前FileItem的header
private FileItemHeaders headers;
/**
* @param pName 文件名,或为null
* @param pFieldName item的字段名,如text$983112430
* @param pContentType item的内容类型,或为null(是表单字段时)
* @param pFormField 是否是表单字段,文件名为null就是表单字段
* @param pContentLength item的内容长度,或为-1(是表单字段时)
*/
FileItemStreamImpl(String pName, String pFieldName, String pContentType, boolean pFormField, long pContentLength) throws IOException {
name = pName;
fieldName = pFieldName;
contentType = pContentType;
formField = pFormField;
final ItemInputStream itemStream = multi.newInputStream();
InputStream istream = itemStream;
if (fileSizeMax != -1) {// 单个上传文件大小有限制
if (pContentLength != -1 && pContentLength > fileSizeMax) {// 上传文件过大
FileUploadException e = new FileSizeLimitExceededException("The field " + fieldName + " exceeds its maximum permitted " + " size of " + fileSizeMax + " characters.", pContentLength, fileSizeMax);
throw new FileUploadIOException(e);
}
istream = new LimitedInputStream(istream, fileSizeMax) {
protected void raiseError(long pSizeMax, long pCount) throws IOException {
itemStream.close(true);
FileUploadException e = new FileSizeLimitExceededException("The field " + fieldName + " exceeds its maximum permitted " + " size of " + pSizeMax + " characters.", pCount, pSizeMax);
throw new FileUploadIOException(e);
}
};
}
stream = istream;
}
// 打开FileItem的文件流,用于读取FileItem的内容
public InputStream openStream() throws IOException {
if (opened) {
throw new IllegalStateException("The stream was already opened.");
}
if (((Closeable) stream).isClosed()) {
throw new FileItemStream.ItemSkippedException();
}
return stream;
}
// 关闭FileItem的流
void close() throws IOException {
stream.close();
}
public FileItemHeaders getHeaders() {
return headers;
}
public void setHeaders(FileItemHeaders pHeaders) {
headers = pHeaders;
}
// 返回当前FileItem的内容类型,当FileItem为表单字段时返回null
public String getContentType() {
return contentType;
}
// 返回字段名
public String getFieldName() {
return fieldName;
}
// 返回上传文件名,当FileItem为表单字段时返回null
public String getName() {
return name;
}
// 返回当前FileItem是否是表单字段
public boolean isFormField() {
return formField;
}
}
四.内部类 - FileItemIteratorImpl
一个FileItem的迭代器,迭代器遍历可以解析出实体中的每个部分,创建对应的FileItem实例
private class FileItemIteratorImpl implements FileItemIterator {
// 处理multi part stream
private final MultipartStream multi;
// 通知器,用于触发监听器
private final MultipartStream.ProgressNotifier notifier;
// 边界,分割各部分
private final byte[] boundary;
// 当前处理的FileItem
private FileItemStreamImpl currentItem;
// 当前item字段名
private String currentFieldName;
// 是否跳过序言
private boolean skipPreamble;
// 当前FileItem是否有效,是否可以继续读取
private boolean itemValid;
// 是否是文件的结尾
private boolean eof;
// 构造函数
FileItemIteratorImpl(RequestContext ctx) throws FileUploadException, IOException {
if (ctx == null) {
throw new NullPointerException("ctx parameter");
}
// 如:multipart/form-data; boundary=----WebKitFormBoundarySvo5DUiFsRuugu9A
String contentType = ctx.getContentType();
if ((null == contentType) || (!contentType.toLowerCase().startsWith(MULTIPART))) {
throw new InvalidContentTypeException("the request doesn't contain a " + MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED + " stream, content type header is " + contentType);
}
// 获取请求流
InputStream input = ctx.getInputStream();
if (sizeMax >= 0) {// 设置了整个上传文件的最大大小
// 上传文件的长度
int requestSize = ctx.getContentLength();
if (requestSize == -1) {
input = new LimitedInputStream(input, sizeMax) {
protected void raiseError(long pSizeMax, long pCount) throws IOException {
FileUploadException ex = new SizeLimitExceededException("the request was rejected because" + " its size (" + pCount + ") exceeds the configured maximum" + " (" + pSizeMax + ")", pCount, pSizeMax);
throw new FileUploadIOException(ex);
}
};
} else {
if (sizeMax >= 0 && requestSize > sizeMax) {// 上传文件过大
throw new SizeLimitExceededException("the request was rejected because its size (" + requestSize + ") exceeds the configured maximum (" + sizeMax + ")", requestSize, sizeMax);
}
}
}
// 如:UTF-8
String charEncoding = headerEncoding;
if (charEncoding == null) {
charEncoding = ctx.getCharacterEncoding();
}
/**
* contentType:multipart/form-data; boundary=----WebKitFormBoundarySvo5DUiFsRuugu9A
* boundaryStr : ----WebKitFormBoundarySvo5DUiFsRuugu9A
* byte[]:[45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 83, 118, 111, 53, 68, 85, 105, 70, 115, 82, 117, 117, 103, 117, 57, 65]
*/
boundary = getBoundary(contentType);
if (boundary == null) {// 没有找到边界
throw new FileUploadException("the request was rejected because " + "no multipart boundary was found");
}
notifier = new MultipartStream.ProgressNotifier(listener, ctx.getContentLength());
// 处理多部分流
multi = new MultipartStream(input, boundary, notifier);
// UTF-8
multi.setHeaderEncoding(charEncoding);
// 跳过序言
skipPreamble = true;
findNextItem();
}
/**
* 是否找得到下一个item
* @return true-表示下一个item被找到
*/
private boolean findNextItem() throws IOException {
if (eof) {// 文件末尾
return false;
}
if (currentItem != null) {// 关闭当前的item
currentItem.close();
currentItem = null;
}
for (;;) {
// 是否还有下一个item
boolean nextPart;
if (skipPreamble) {// 跳过序言
nextPart = multi.skipPreamble();
} else {
nextPart = multi.readBoundary();
}
if (!nextPart) {//
if (currentFieldName == null) {// 没有字段名
// 外部multipart终止 - >没有更多的数据
eof = true;
return false;
}
// 内部multipart已终止 - >返回解析外部
multi.setBoundary(boundary);
currentFieldName = null;
continue;
}
/**
* 解析当前encapsulation的header-part
* 如header:Content-Disposition: form-data; name="text$6822755630"
*/
FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
if (currentFieldName == null) {// 当前字段名为null
// 解析出的字段名,如text$983112430
String fieldName = getFieldName(headers);
if (fieldName != null) {// 有字段名
String subContentType = headers.getHeader(CONTENT_TYPE);
if (subContentType != null && subContentType.toLowerCase().startsWith(MULTIPART_MIXED)) {
currentFieldName = fieldName;
// Multiple files associated with this field name
byte[] subBoundary = getBoundary(subContentType);
multi.setBoundary(subBoundary);
skipPreamble = true;
continue;
}
// 获取字段名对应的文件名,没有就为null
String fileName = getFileName(headers);
currentItem = new FileItemStreamImpl(fileName, fieldName, headers.getHeader(CONTENT_TYPE), fileName == null, getContentLength(headers));
notifier.noteItem();
// item是有效的
itemValid = true;
return true;
}
} else {// 有上传文件
String fileName = getFileName(headers);
if (fileName != null) {
currentItem = new FileItemStreamImpl(fileName, currentFieldName, headers.getHeader(CONTENT_TYPE), false, getContentLength(headers));
notifier.noteItem();
itemValid = true;
return true;
}
}
multi.discardBodyData();
}
}
// 从pHeaders获取内容长度
private long getContentLength(FileItemHeaders pHeaders) {
try {
return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));//"Content-length"
} catch (Exception e) {
return -1;
}
}
// 是否还有下一个FileItem
public boolean hasNext() throws FileUploadException, IOException {
if (eof) {// 文件末尾,表示文件读完
return false;
}
if (itemValid) {// 当前的item是可以读取的
return true;
}
return findNextItem();
}
/**
* 返回下一个可用的FileItemStream实例
*/
public FileItemStream next() throws FileUploadException, IOException {
if (eof || (!itemValid && !hasNext())) {
throw new NoSuchElementException();
}
itemValid = false;
return currentItem;
}
}
四.完整类 - FileUploadBase
package org.apache.commons.fileupload;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.MultipartStream.ItemInputStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.apache.commons.fileupload.util.Closeable;
import org.apache.commons.fileupload.util.FileItemHeadersImpl;
import org.apache.commons.fileupload.util.LimitedInputStream;
import org.apache.commons.fileupload.util.Streams;
/**
* <p>用于处理文件上传的高级API</p>
*
* 本类处理由html组件发送的multipart/mixed编码类型、RFC 1867(http://www.ietf.org/rfc/rfc1867.txt)指定的多个文件
* 使用parseRequest(HttpServletRequest)方法获取一个org.apache.commons.fileupload.FileItem列表
*
* 上传文件是保存在内存,缓存到本地,还是其他地方,由DiskFileItemFactory决定
* 已弃用的代码删除了
*/
public abstract class FileUploadBase {
/**
* 确定请求是否包含多部分内容。可用来判断是否是文件上传请求
* @param request servlet请求对象,必须非空
* @return true:文件上传请求
*/
public static boolean isMultipartContent(HttpServletRequest req) {
return ServletFileUpload.isMultipartContent(req);
}
// 不再使用此方法,使用ServletFileUpload中对于方法替代
public static final boolean isMultipartContent(RequestContext ctx) {
String contentType = ctx.getContentType();
if (contentType == null) {
return false;
}
if (contentType.toLowerCase().startsWith(MULTIPART)) {
return true;
}
return false;
}
// 如:Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
public static final String CONTENT_TYPE = "Content-type";
// 如:Content-Disposition: form-data; name="fileName"; filename="123.xlsx"
public static final String CONTENT_DISPOSITION = "Content-disposition";
public static final String CONTENT_LENGTH = "Content-length";
/**
* Content-disposition值中的form-data
* 如:Content-Disposition: form-data; name="fileName"; filename="123.xlsx"
*/
public static final String FORM_DATA = "form-data";
// Content-disposition值中的attachment
public static final String ATTACHMENT = "attachment";
public static final String MULTIPART = "multipart/";
public static final String MULTIPART_FORM_DATA = "multipart/form-data";
public static final String MULTIPART_MIXED = "multipart/mixed";
// 不再使用
public static final int MAX_HEADER_SIZE = 1024;
// 完整请求的最大值,-1表示没有最大值
private long sizeMax = -1;
// 单个上传文件的最大值,-1表示没有最大值
private long fileSizeMax = -1;
// 读取header时的内容编码
private String headerEncoding;
// 进度监听器
private ProgressListener listener;
// 返回用于创建FileItem的FileItemFactory
public abstract FileItemFactory getFileItemFactory();
public abstract void setFileItemFactory(FileItemFactory factory);
public FileItemIterator getItemIterator(RequestContext ctx) throws FileUploadException, IOException {
return new FileItemIteratorImpl(ctx);
}
/**
* 解析请求流,返回一个FileItem List
*/
public List /* FileItem */ parseRequest(RequestContext ctx) throws FileUploadException {
try {
FileItemIterator iter = getItemIterator(ctx);
List items = new ArrayList();
// 获取创建FileItem的工厂FileItemFactory
FileItemFactory fac = getFileItemFactory();
if (fac == null) {
throw new NullPointerException("No FileItemFactory has been set.");
}
while (iter.hasNext()) {
// 返回一个item
FileItemStream item = iter.next();
// 根据item创建对于的FileItem
FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(), item.isFormField(), item.getName());
try {
//
Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new IOFileUploadException("Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage(), e);
}
if (fileItem instanceof FileItemHeadersSupport) {
final FileItemHeaders fih = item.getHeaders();
((FileItemHeadersSupport) fileItem).setHeaders(fih);
}
// 将创建的FileItem添加到List<ListItem>中
items.add(fileItem);
}
return items;
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new FileUploadException(e.getMessage(), e);
}
}
/**
* 从contentType中检索出边界boundary
* 如:
* Content-Type = multipart/form-data; boundary=----WebKitFormBoundarypv0XnanNgIkhNEDD
* 返回的boundary:----WebKitFormBoundarypv0XnanNgIkhNEDD
*/
protected byte[] getBoundary(String contentType) {
ParameterParser parser = new ParameterParser();
parser.setLowerCaseNames(true);
// Parameter parser can handle null input
Map params = parser.parse(contentType, new char[] {';', ','});
String boundaryStr = (String) params.get("boundary");
if (boundaryStr == null) {
return null;
}
byte[] boundary;
try {
boundary = boundaryStr.getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException e) {
boundary = boundaryStr.getBytes();
}
return boundary;
}
/**
* 从headers的key=Content-disposition对应值中返回文件名
* @param headers FileItem的header, {content-disposition=[form-data; name="text$5234303740"]}
* @return 返回当前encapsulation的文件名
*/
protected String getFileName(FileItemHeaders headers) {
// headers.getHeader(CONTENT_DISPOSITION) - form-data; name="text$5234303740"
return getFileName(headers.getHeader(CONTENT_DISPOSITION));// "Content-disposition"
}
/**
* 从pContentDisposition中获取File文件名
* @param pContentDisposition 如:form-data; name="text$5234303740"
* @return 返回text$5234303740
*/
private String getFileName(String pContentDisposition) {
String fileName = null;
if (pContentDisposition != null) {// 存在文件名
String cdl = pContentDisposition.toLowerCase();
if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
ParameterParser parser = new ParameterParser();
parser.setLowerCaseNames(true);
// Parameter parser can handle null input
Map params = parser.parse(pContentDisposition, ';');
if (params.containsKey("filename")) {
fileName = (String) params.get("filename");
if (fileName != null) {
fileName = fileName.trim();
} else {
// 即使没有值,参数也是存在的,所以我们返回一个空的文件名而不是文件名。
fileName = "";
}
}
}
}
return fileName;
}
/**
* 从FileItemHeaders的key=Content-disposition的值中检索字段名
* @param headers 包含HTTP请求头的一个Map,有一个键值对,key="Content-disposition"
* @return 返回当前encapsulation的字段名
*/
protected String getFieldName(FileItemHeaders headers) {
// CONTENT_DISPOSITION = "Content-disposition"
return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
}
/**
* 根据所给的content-disposition值返回字段名
* @param pContentDisposition content-dispositions值,如-form-data; name="text$983112430"
* @return 返回字段,如text$983112430
*/
private String getFieldName(String pContentDisposition) {
String fieldName = null;
// FORM_DATA = "form-data"
if (pContentDisposition != null && pContentDisposition.toLowerCase().startsWith(FORM_DATA)) {
ParameterParser parser = new ParameterParser();
parser.setLowerCaseNames(true);
// 参数解析器可以处理空输入
Map params = parser.parse(pContentDisposition, ';');
// 字段名
fieldName = (String) params.get("name");
if (fieldName != null) {
fieldName = fieldName.trim();
}
}
return fieldName;
}
/**
* 解析header-part,并返回键值对
* 如:
* Content-Disposition: form-data; name="text$5234303740"
*
* @param headerPart 当前encapsulation的header-part
* @return 返回一个包含解析出的HTTP请求头的Map
*/
protected FileItemHeaders getParsedHeaders(String headerPart) {
final int len = headerPart.length();
FileItemHeadersImpl headers = newFileItemHeaders();
int start = 0;
for (;;) {
// headerPart的/r/n的索引,此例返回54
int end = parseEndOfLine(headerPart, start);
if (start == end) {
break;
}
// 当前header:此例-Content-Disposition: form-data; name="text$983112430"
String header = headerPart.substring(start, end);
start = end + 2;
while (start < len) {
int nonWs = start;
while (nonWs < len) {
char c = headerPart.charAt(nonWs);
if (c != ' ' && c != '\t') {
break;
}
++nonWs;
}
if (nonWs == start) {
break;
}
// Continuation line found
end = parseEndOfLine(headerPart, nonWs);
header += " " + headerPart.substring(nonWs, end);
start = end + 2;
}
// 解析当前header,记录在headers中,此例header=Content-Disposition: form-data; name="text$983112430"
parseHeaderLine(headers, header);
}
return headers;
}
/**
* 解析header中的一行,将解析的结果保存到headers中
* 如:header=Content-Disposition: form-data; name="text$5234303740"
* 结果:content-disposition=[form-data; name="text$5234303740"]
* @param headers 记录解析header的键值对
* @param header 待解析的header行
*/
private void parseHeaderLine(FileItemHeadersImpl headers, String header) {
final int colonOffset = header.indexOf(':');
if (colonOffset == -1) {
// 这个标题行格式错误,跳过它。
return;
}
String headerName = header.substring(0, colonOffset).trim();
String headerValue = header.substring(header.indexOf(':') + 1).trim();
headers.addHeader(headerName, headerValue);
}
/**
* 从end处开始查找\r\n的位置,即返回行字符串的结尾索引
* 如:headerPart=Content-Disposition: form-data; name="text$5234303740",结尾返回54
* @param headerPart 正在被解析headers
* @param end 从索引end处开始搜索
* @return \r\n序列的索引,表示行尾。
*/
private int parseEndOfLine(String headerPart, int end) {
int index = end;
for (;;) {
// 回车
int offset = headerPart.indexOf('\r', index);
if (offset == -1 || offset + 1 >= headerPart.length()) {
throw new IllegalStateException("Expected headers to be terminated by an empty line.");
}
// /r/n 说明找到
if (headerPart.charAt(offset + 1) == '\n') {
return offset;
}
index = offset + 1;
}
}
protected FileItemHeadersImpl newFileItemHeaders() {
return new FileItemHeadersImpl();
}
/**
* 由FileUploadBase#getItemIterator(RequestContext)调用获取此迭代器
*/
private class FileItemIteratorImpl implements FileItemIterator {
/**
* Default implementation of {@link FileItemStream}.
*/
private class FileItemStreamImpl implements FileItemStream {
// 当前FileItem的内容类型,FileItem是表单字段时为null
private final String contentType;
// FileItem的字段名
private final String fieldName;
// 上传文件名,当FileItem是表单字段时为null
private final String name;
// 当前FileItem是否是表单字段
private final boolean formField;
// 当前FileItem的输入流
private final InputStream stream;
// 当前FileItem是否已经打开
private boolean opened;
// 当前FileItem的header
private FileItemHeaders headers;
/**
* Creates a new instance.
* @param pName 文件名,或为null
* @param pFieldName item的字段名,如text$983112430
* @param pContentType item的内容类型,或为null(是表单字段时)
* @param pFormField 是否是表单字段,文件名为null就是表单字段
* @param pContentLength item的内容长度,或为-1(是表单字段时)
*/
FileItemStreamImpl(String pName, String pFieldName, String pContentType, boolean pFormField, long pContentLength) throws IOException {
name = pName;
fieldName = pFieldName;
contentType = pContentType;
formField = pFormField;
final ItemInputStream itemStream = multi.newInputStream();
InputStream istream = itemStream;
if (fileSizeMax != -1) {// 单个上传文件大小有限制
if (pContentLength != -1 && pContentLength > fileSizeMax) {// 上传文件过大
FileUploadException e = new FileSizeLimitExceededException("The field " + fieldName + " exceeds its maximum permitted " + " size of " + fileSizeMax + " characters.", pContentLength, fileSizeMax);
throw new FileUploadIOException(e);
}
istream = new LimitedInputStream(istream, fileSizeMax) {
protected void raiseError(long pSizeMax, long pCount) throws IOException {
itemStream.close(true);
FileUploadException e = new FileSizeLimitExceededException("The field " + fieldName + " exceeds its maximum permitted " + " size of " + pSizeMax + " characters.", pCount, pSizeMax);
throw new FileUploadIOException(e);
}
};
}
stream = istream;
}
// 打开FileItem的文件流,用于读取FileItem的内容
public InputStream openStream() throws IOException {
if (opened) {
throw new IllegalStateException("The stream was already opened.");
}
if (((Closeable) stream).isClosed()) {
throw new FileItemStream.ItemSkippedException();
}
return stream;
}
// 关闭FileItem的流
void close() throws IOException {
stream.close();
}
public FileItemHeaders getHeaders() {
return headers;
}
public void setHeaders(FileItemHeaders pHeaders) {
headers = pHeaders;
}
// 返回当前FileItem的内容类型,当FileItem为表单字段时返回null
public String getContentType() {
return contentType;
}
// 返回字段名
public String getFieldName() {
return fieldName;
}
// 返回上传文件名,当FileItem为表单字段时返回null
public String getName() {
return name;
}
// 返回当前FileItem是否是表单字段
public boolean isFormField() {
return formField;
}
}
// 处理multi part stream
private final MultipartStream multi;
// 通知器,用于触发监听器
private final MultipartStream.ProgressNotifier notifier;
// 边界,分割各部分
private final byte[] boundary;
// 当前处理的FileItem
private FileItemStreamImpl currentItem;
// 当前item字段名
private String currentFieldName;
// 是否跳过序言
private boolean skipPreamble;
// 当前FileItem是否有效,是否可以继续读取
private boolean itemValid;
// 是否是文件的结尾
private boolean eof;
// 构造函数
FileItemIteratorImpl(RequestContext ctx) throws FileUploadException, IOException {
if (ctx == null) {
throw new NullPointerException("ctx parameter");
}
// 如:multipart/form-data; boundary=----WebKitFormBoundarySvo5DUiFsRuugu9A
String contentType = ctx.getContentType();
if ((null == contentType) || (!contentType.toLowerCase().startsWith(MULTIPART))) {
throw new InvalidContentTypeException("the request doesn't contain a " + MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED + " stream, content type header is " + contentType);
}
// 获取请求流
InputStream input = ctx.getInputStream();
if (sizeMax >= 0) {// 设置了整个上传文件的最大大小
// 上传文件的长度
int requestSize = ctx.getContentLength();
if (requestSize == -1) {
input = new LimitedInputStream(input, sizeMax) {
protected void raiseError(long pSizeMax, long pCount) throws IOException {
FileUploadException ex = new SizeLimitExceededException("the request was rejected because" + " its size (" + pCount + ") exceeds the configured maximum" + " (" + pSizeMax + ")", pCount, pSizeMax);
throw new FileUploadIOException(ex);
}
};
} else {
if (sizeMax >= 0 && requestSize > sizeMax) {// 上传文件过大
throw new SizeLimitExceededException("the request was rejected because its size (" + requestSize + ") exceeds the configured maximum (" + sizeMax + ")", requestSize, sizeMax);
}
}
}
// 如:UTF-8
String charEncoding = headerEncoding;
if (charEncoding == null) {
charEncoding = ctx.getCharacterEncoding();
}
/**
* contentType:multipart/form-data; boundary=----WebKitFormBoundarySvo5DUiFsRuugu9A
* boundaryStr : ----WebKitFormBoundarySvo5DUiFsRuugu9A
* byte[]:[45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 83, 118, 111, 53, 68, 85, 105, 70, 115, 82, 117, 117, 103, 117, 57, 65]
*/
boundary = getBoundary(contentType);
if (boundary == null) {// 没有找到边界
throw new FileUploadException("the request was rejected because " + "no multipart boundary was found");
}
notifier = new MultipartStream.ProgressNotifier(listener, ctx.getContentLength());
// 处理多部分流
multi = new MultipartStream(input, boundary, notifier);
// UTF-8
multi.setHeaderEncoding(charEncoding);
// 跳过序言
skipPreamble = true;
findNextItem();
}
/**
* 是否找得到下一个item
* @return true-表示下一个item被找到
*/
private boolean findNextItem() throws IOException {
if (eof) {// 文件末尾
return false;
}
if (currentItem != null) {// 关闭当前的item
currentItem.close();
currentItem = null;
}
for (;;) {
// 是否还有下一个item
boolean nextPart;
if (skipPreamble) {// 跳过序言
nextPart = multi.skipPreamble();
} else {
nextPart = multi.readBoundary();
}
if (!nextPart) {//
if (currentFieldName == null) {// 没有字段名
// 外部multipart终止 - >没有更多的数据
eof = true;
return false;
}
// 内部multipart已终止 - >返回解析外部
multi.setBoundary(boundary);
currentFieldName = null;
continue;
}
/**
* 解析当前encapsulation的header-part
* 如header:Content-Disposition: form-data; name="text$6822755630"
*/
FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
if (currentFieldName == null) {// 当前字段名为null
// 解析出的字段名,如text$983112430
String fieldName = getFieldName(headers);
if (fieldName != null) {// 有字段名
String subContentType = headers.getHeader(CONTENT_TYPE);
if (subContentType != null && subContentType.toLowerCase().startsWith(MULTIPART_MIXED)) {
currentFieldName = fieldName;
// Multiple files associated with this field name
byte[] subBoundary = getBoundary(subContentType);
multi.setBoundary(subBoundary);
skipPreamble = true;
continue;
}
// 获取字段名对应的文件名,没有就为null
String fileName = getFileName(headers);
currentItem = new FileItemStreamImpl(fileName, fieldName, headers.getHeader(CONTENT_TYPE), fileName == null, getContentLength(headers));
notifier.noteItem();
// item是有效的
itemValid = true;
return true;
}
} else {// 有上传文件
String fileName = getFileName(headers);
if (fileName != null) {
currentItem = new FileItemStreamImpl(fileName, currentFieldName, headers.getHeader(CONTENT_TYPE), false, getContentLength(headers));
notifier.noteItem();
itemValid = true;
return true;
}
}
multi.discardBodyData();
}
}
// 从pHeaders获取内容长度
private long getContentLength(FileItemHeaders pHeaders) {
try {
return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));//"Content-length"
} catch (Exception e) {
return -1;
}
}
// 是否还有下一个FileItem
public boolean hasNext() throws FileUploadException, IOException {
if (eof) {// 文件末尾,表示文件读完
return false;
}
if (itemValid) {// 当前的item是可以读取的
return true;
}
return findNextItem();
}
/**
* 返回下一个可用的FileItemStream实例
*/
public FileItemStream next() throws FileUploadException, IOException {
if (eof || (!itemValid && !hasNext())) {
throw new NoSuchElementException();
}
itemValid = false;
return currentItem;
}
}
/**
* This exception is thrown for hiding an inner
* {@link FileUploadException} in an {@link IOException}.
*/
public static class FileUploadIOException extends IOException {
// The exceptions UID, for serializing an instance.
private static final long serialVersionUID = -7047616958165584154L;
// The exceptions cause; we overwrite the parent classes field, which is available since Java 1.4 only.
private final FileUploadException cause;
/**
* Creates a <code>FileUploadIOException</code> with the given cause.
* @param pCause The exceptions cause, if any, or null.
*/
public FileUploadIOException(FileUploadException pCause) {
// We're not doing super(pCause) cause of 1.3 compatibility.
cause = pCause;
}
/**
* Returns the exceptions cause.
* @return The exceptions cause, if any, or null.
*/
public Throwable getCause() {
return cause;
}
}
/**
* Thrown to indicate that the request is not a multipart request.
*/
public static class InvalidContentTypeException extends FileUploadException {
private static final long serialVersionUID = -9073026332015646668L;
/**
* Constructs a <code>InvalidContentTypeException</code> with no detail message.
*/
public InvalidContentTypeException() {
// Nothing to do.
}
/**
* Constructs an <code>InvalidContentTypeException</code> with the specified detail message.
* @param message The detail message.
*/
public InvalidContentTypeException(String message) {
super(message);
}
}
/**
* Thrown to indicate an IOException.
*/
public static class IOFileUploadException extends FileUploadException {
private static final long serialVersionUID = 1749796615868477269L;
// The exceptions cause; we overwrite the parent classes field, which is available since Java 1.4 only.
private final IOException cause;
/**
* Creates a new instance with the given cause.
* @param pMsg The detail message.
* @param pException The exceptions cause.
*/
public IOFileUploadException(String pMsg, IOException pException) {
super(pMsg);
cause = pException;
}
/**
* Returns the exceptions cause.
* @return The exceptions cause, if any, or null.
*/
public Throwable getCause() {
return cause;
}
}
/** This exception is thrown, if a requests permitted size
* is exceeded.
*/
protected abstract static class SizeException extends FileUploadException {
/**
* The actual size of the request.
*/
private final long actual;
/**
* The maximum permitted size of the request.
*/
private final long permitted;
/**
* Creates a new instance.
* @param message The detail message.
* @param actual The actual number of bytes in the request.
* @param permitted The requests size limit, in bytes.
*/
protected SizeException(String message, long actual, long permitted) {
super(message);
this.actual = actual;
this.permitted = permitted;
}
/**
* Retrieves the actual size of the request.
*
* @return The actual size of the request.
*/
public long getActualSize() {
return actual;
}
/**
* Retrieves the permitted size of the request.
*
* @return The permitted size of the request.
*/
public long getPermittedSize() {
return permitted;
}
}
/**
* Thrown to indicate that the request size is not specified. In other
* words, it is thrown, if the content-length header is missing or
* contains the value -1.
* @deprecated As of commons-fileupload 1.2, the presence of a
* content-length header is no longer required.
*/
public static class UnknownSizeException extends FileUploadException {
/** The exceptions UID, for serializing an instance.
*/
private static final long serialVersionUID = 7062279004812015273L;
/**
* Constructs a <code>UnknownSizeException</code> with no
* detail message.
*/
public UnknownSizeException() {
super();
}
/**
* Constructs an <code>UnknownSizeException</code> with
* the specified detail message.
*
* @param message The detail message.
*/
public UnknownSizeException(String message) {
super(message);
}
}
/**
* Thrown to indicate that the request size exceeds the configured maximum.
*/
public static class SizeLimitExceededException extends SizeException {
/** The exceptions UID, for serializing an instance.
*/
private static final long serialVersionUID = -2474893167098052828L;
/**
* @deprecated Replaced by
* {@link #SizeLimitExceededException(String, long, long)}
*/
public SizeLimitExceededException() {
this(null, 0, 0);
}
/**
* @deprecated Replaced by
* {@link #SizeLimitExceededException(String, long, long)}
* @param message The exceptions detail message.
*/
public SizeLimitExceededException(String message) {
this(message, 0, 0);
}
/**
* Constructs a <code>SizeExceededException</code> with
* the specified detail message, and actual and permitted sizes.
*
* @param message The detail message.
* @param actual The actual request size.
* @param permitted The maximum permitted request size.
*/
public SizeLimitExceededException(String message, long actual,
long permitted) {
super(message, actual, permitted);
}
}
/**
* Thrown to indicate that A files size exceeds the configured maximum.
*/
public static class FileSizeLimitExceededException extends SizeException {
/** The exceptions UID, for serializing an instance.
*/
private static final long serialVersionUID = 8150776562029630058L;
/**
* Constructs a <code>SizeExceededException</code> with
* the specified detail message, and actual and permitted sizes.
*
* @param message The detail message.
* @param actual The actual request size.
* @param permitted The maximum permitted request size.
*/
public FileSizeLimitExceededException(String message, long actual,
long permitted) {
super(message, actual, permitted);
}
}
// 返回完整请求的最大值,单位字节, -1表示没有限制
public long getSizeMax() {
return sizeMax;
}
public void setSizeMax(long sizeMax) {
this.sizeMax = sizeMax;
}
public long getFileSizeMax() {
return fileSizeMax;
}
public void setFileSizeMax(long fileSizeMax) {
this.fileSizeMax = fileSizeMax;
}
/**
* 返回读取单个部分header时使用的字符编码
* 如果未指定或为null,就使用请求的编码
* 如果请求编码也未指定或为null,就使用平台的默认编码
*/
public String getHeaderEncoding() {
return headerEncoding;
}
public void setHeaderEncoding(String encoding) {
headerEncoding = encoding;
}
public ProgressListener getProgressListener() {
return listener;
}
public void setProgressListener(ProgressListener pListener) {
listener = pListener;
}
}