一.概述
1.在
文件上传的基本原理(一)中介绍了HTTP报文的简介以及报文的基本格式
2.本节主要介绍common-Fileupload是如何解析请求报文的实体的
二.common-fileupload的类结构
三.解析大致流程
四.解析实例讲解
4.1 假设一个报文如图
------WebKitFormBoundaryE2KKgliuAAe4H3XB
Content-Disposition: form-data; name="text$9702005251"
C:\fakepath\测试.xlsx
------WebKitFormBoundaryE2KKgliuAAe4H3XB
Content-Disposition: form-data; name="fileName"; filename="测试.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
------WebKitFormBoundaryE2KKgliuAAe4H3XB
Content-Disposition: form-data; name="fileName"; filename="测试1.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
------WebKitFormBoundaryE2KKgliuAAe4H3XB--
对应的报文实体的字节数组
[0 - 19] 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110,
[20 - 39] 100, 97, 114, 121, 69, 50, 75, 75, 103, 108, 105, 117, 65, 65, 101, 52, 72, 51, 88, 66,
[40 - 59] 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 68, 105, 115, 112, 111, 115, 105, 116, 105, 111,
[60 - 79] 110, 58, 32, 102, 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110, 97, 109, 101, 61, 34,
[80 - 99] 116, 101, 120, 116, 36, 57, 55, 48, 50, 48, 48, 53, 50, 53, 49, 34, 13, 10, 13, 10,
[100 - 119] 67, 58, 92, 102, 97, 107, 101, 112, 97, 116, 104, 92, -26, -75,-117, -24, -81,-107, 46, 120,
[120 - 139] 108, 115, 120, 13, 10, 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114,
[140 - 159] 109, 66, 111, 117, 110, 100, 97, 114, 121, 69, 50, 75, 75, 103, 108, 105, 117, 65, 65, 101,
[160 - 179] 52, 72, 51, 88, 66, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 68, 105, 115, 112, 111,
[180 - 199] 115, 105, 116, 105, 111, 110, 58, 32, 102, 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110,
[200 - 219] 97, 109, 101, 61, 34, 102, 105, 108, 101, 78, 97, 109, 101, 34, 59, 32, 102, 105, 108, 101,
[220 - 239] 110, 97, 109, 101, 61, 34, -26, -75,-117, -24, -81,-107, 46, 120, 108, 115, 120, 34, 13, 10,
[240 - 259] 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 32, 97, 112, 112, 108, 105, 99,
[260 - 279] 97, 116, 105, 111, 110, 47, 118, 110, 100, 46, 111, 112, 101, 110, 120, 109, 108, 102, 111, 114,
[280 - 299] 109, 97, 116, 115, 45, 111, 102, 102, 105, 99, 101, 100, 111, 99, 117, 109, 101, 110, 116, 46,
[300 - 319] 115, 112, 114, 101, 97, 100, 115, 104, 101, 101, 116, 109, 108, 46, 115, 104, 101, 101, 116, 13,
[320 - 339] 10, 13, 10, 80, 75, 3, 4, 10, 0, 0, 0, 0, 0,-121, 78, -30, 64, 0, 0, 0,
[340 - 359] 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 100, 111, 99, 80, 114, 111, 112,
[360 - 379] 115, 47, 80, 75, 3, 4, 20, 0, 0, 0, 8, 0,-121, 78, -30, 64, -88, 106, 102, 33,
[380 - 399] 112, 1, 0, 0, -37, 2, 0, 0, 16, 0, 0, 0, 100, 111, 99, 80, 114, 111, 112, 115,
[400 - 419] 47, 97, 112, 112, 46, 120, 109, 108, -99,-110, -49, 75, -61, 48, 28, -59, -17,-126, -1, 67,
[420 - 439] -55, 125, -21, 28, 78, 100, -76, 29,-126,-118, 23, 113,-121, -23, 61, -90, -33, 110,-127, 46,
[440 - 459] 41, 73, 44,-101, 39, 65,-123, 9, 10, 94, 100, 48, 118, 112, -96, -96, -96, -34, -4,-119,
[460 - 479] -2, 55, -21, -74, 63, -61, 116,-123, -39,-119, 78, -16, -10,-110, -9, 120, 124, 94,-120, 85,
[480 - 499] 106, -44, 125, 35, 4, 33, 41, 103, 54, 90, -56, -26,-112, 1,-116, 112,-105, -78, -86,-115,
[500 - 519] -74, 43, -21,-103, 101, 100, 72,-123,-103,-117, 125, -50, -64, 70, 77,-112, -88, -28, -52, -49,
[520 - 539] 89, 101, -63, 3, 16,-118,-126, 52, 116, 5,-109, 54, -86, 41, 21, 20, 77, 83,-110, 26,
[540 - 559] -44, -79, -52, 106, -101, 105, -57,-29, -94,-114,-107, 62,-118, -86, -55, 61,-113, 18, 88, -27,
[560 - 579] ......
4.2 通用首部中分隔符boundary
boundary=----WebKitFormBoundaryE2KKgliuAAe4H3XB
对应的字节数组:
[45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 69, 50, 75, 75, 103, 108, 105, 117, 65, 65, 101, 52, 72, 51, 88, 66]
4.3 MultipartStream实例multi
1.根据输入流input和分隔符boundary创建MultipartStream实例
multi = new MultipartStream(input, boundary, notifier)
head=0;
tail=0;
2.实例multi主要解析实体中的各部分
3.实体中各部分是以分隔符boundary隔开的,this.boundary="CRLF--" + 通用首部中的boundary
boundary = \r\n------WebKitFormBoundaryE2KKgliuAAe4H3XB
对应的字节数组为:
[13, 10, 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 69, 50, 75, 75, 103, 108, 105, 117, 65, 65, 101, 52, 72, 51, 88, 66]
4.multi结构
4.4 缓冲区数据
1.假设缓冲区数据如图
4.5 第一次检索是否有下一个FileItem
4.6 处理FileItemIterator迭代器,创建对应的FileItem
1.第一次findNextItem()时已经检索出一个表单字段,返回当前的FileItemStream实例item
2.根据FileItemStream 实例item创建FileItem实例
FileItem fileItem = fac.createItem(item.getFieldName(),item.getContentType(), item.isFormField(),item.getName());
4.6 第二次检索是否有下一个FileItem
1.由于当前例子中有上传文件,所以还有下一个FileItem,接下来让他如何解析的
2.head表示下一次读取的索引,当前head=123,检索下一个分隔符,返回pos=125,说明去掉CRLF的分隔符从索引125处开始
[120 - 139] 108, 115, 120, 13, 10, 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114,
[140 - 159] 109, 66, 111, 117, 110, 100, 97, 114, 121, 69, 50, 75, 75, 103, 108, 105, 117, 65, 65, 101,
[160 - 179] 52, 72, 51, 88, 66, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 68, 105, 115, 112, 111,
3.discardBodyData():丢弃head=123到125之前的数据,这里丢弃了[13,10],即丢弃了上个部分的\r\n,同时更新head=125
4.由于2中已经检测到存在分隔符,分隔符长40,head=125,所以直接更新head=125+40=165,从索引165检索,检索165、166两个直接
- 如果2个字节为[13, 10],即\r\n,就表示实体中海油下一个部分
- 如果2个直接为[45, 45],即 - - ,就表示实体的结尾,没有下一部分
从下面看出2个直接是[13, 10],即还有下一个字节,更新head=167
[160 - 179] 52, 72, 51, 88, 66, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 68, 105, 115, 112, 111,
------WebKitFormBoundaryElA5s1ivhyHVRZpx
Content-Disposition: form-data; name="fileName"; filename="测试.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
5.确定还有下一个部分,下一部分实体首部和实体主体之间用CRLFCRLF,即\r\n\r\n分隔,检索出下一部分的实体首部
当前head=167, 循环从缓冲区中读取一个字节,读取完后head+1,直到读取到[13,10,13,10],表示找到首部与主体分隔位置
[160 - 179] 52, 72, 51, 88, 66, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 68, 105, 115, 112, 111,
[180 - 199] 115, 105, 116, 105, 111, 110, 58, 32, 102, 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110,
[200 - 219] 97, 109, 101, 61, 34, 102, 105, 108, 101, 78, 97, 109, 101, 34, 59, 32, 102, 105, 108, 101,
[220 - 239] 110, 97, 109, 101, 61, 34, -26, -75,-117, -24, -81,-107, 46, 120, 108, 115, 120, 34, 13, 10,
[240 - 259] 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 32, 97, 112, 112, 108, 105, 99,
[260 - 279] 97, 116, 105, 111, 110, 47, 118, 110, 100, 46, 111, 112, 101, 110, 120, 109, 108, 102, 111, 114,
[280 - 299] 109, 97, 116, 115, 45, 111, 102, 102, 105, 99, 101, 100, 111, 99, 117, 109, 101, 110, 116, 46,
[300 - 319] 115, 112, 114, 101, 97, 100, 115, 104, 101, 101, 116, 109, 108, 46, 115, 104, 101, 101, 116, 13,
[320 - 339] 10, 13, 10, 80, 75, 3, 4, 10, 0, 0, 0, 0, 0,-121, 78, -30, 64, 0, 0, 0,
这里319-322是\r\n\r\n,更新后head=323
读取到的首部为
Content-Disposition: form-data; name="fileName"; filename="测试.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
解析后的FileItemHeaders为:
{
content-type=[application/vnd.openxmlformats-officedocument.spreadsheetml.sheet],
content-disposition=[form-data; name="fileName"; filename="测试.xlsx"]
}
6.根据解析出的实体首部创建一个FileItemStreamImpl实例
currentItem = new FileItemStreamImpl(fileName, fieldName, headers.getHeader(CONTENT_TYPE), fileName == null, getContentLength(headers));
7.流程图
4.7 根据4.6中解析出的FileItemStream实例创建一个FileItem实例
从head=323开始到下一个分隔符之前都是当前上传文件的内容
1.获取当前FileItemStream实例item
2.创建FileItem实例
currentItem = new FileItemStreamImpl(fileName, fieldName, headers.getHeader(CONTENT_TYPE), fileName == null, getContentLength(headers));
3.将上传文件缓存到本地
/**
* item : FileItemStream的实例
* fileItem : FileItem的实例
*/
Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
4.生成临时文件,用于缓存上传文件
5.将上传文件写入临时文件中缓存
6.读取上传文件内容,读到缓存区中
/**
* org.apache.commons.fileupload.MultipartStream.ItemInputStream
* 从缓冲区中读取上传文件内容,读到缓冲区b中
* @param b 目的缓冲区
* @param off 偏移量
* @param len 读取的字节数
* @return 实际读取的字节数,或为-1
*/
public int read(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new FileItemStream.ItemSkippedException();
}
if (len == 0) {
return 0;
}
// 返回缓冲区中的字节数
int res = available();
if (res == 0) {// 缓冲区没有数据了
// 尝试从流中读取更多数据
res = makeAvailable();
if (res == 0) {
return -1;
}
}
res = Math.min(res, len);
System.arraycopy(buffer, head, b, off, res);
head += res;
total += res;
return res;
}
/**
* 返回缓冲区中的字节数
* tail:缓冲区中最大可用索引+1
* head:缓冲区下一个读取的索引
* pad: 缓冲区中保留字节数, 可能是分隔符的一部分
*/
public int available() throws IOException {
if (pos == -1) {// 为-1表示当前缓冲区中无法确定当前上传文件内容的结尾
return tail - head - pad;
}
return pos - head;
}
第一次填充,从head=323开始到下一个分隔符之前都是当前上传文件的内容,有2894个字节西路临时文件
为了防止当前缓冲区结尾中字节是分隔符的一部分,所以保留了42字节,因为分隔符长度为42,所以再次从输入流中读取上传文件内容前,需要将保留的42个字节移动到缓冲区buffer的开头,即0-41处,再将从输入流读取的字节添加到缓冲区buffer中
缓冲区buffer填满了,属性值重置了,head=0,tail=4096
先检索新填满的缓冲区中是否有结尾标志\r\n,本次没有检索到没有检索到结尾标志\r\n,pos=-1,但是tail - head > keepRegion (keepRegion是缓冲区需要保留的字节数),所以当前FileItem对于的ItemInputStream实例需要保留的字节数pad=42
第二次填充缓冲区,从输入流中读取数据,本次填满了缓冲区,读取的数据中没有检索到结尾标志\r\n,所以4054个字节写入临时文件
第二次读取:由于第二次缓冲区中没有分隔符,所以将4054个字节写入临时文件
第三次填充缓冲区,从输入流中读取数据,本次再次填满了缓冲区,读取的数据中没有检索到结尾标志\r\n,所以4054个字节写入临时文件
第三次读取,由于第三次缓冲区中没有检索到结尾标志\r\n,所以将4054个字节写入临时文件
第四次填充缓冲区,从输入流中读取数据,本次读取了84个字节,加上保留的42个字节,缓冲区中有126个字节,检测到126个字节中也没有检索到结尾标志\r\n,所以有84个字节写入临时文件
第四次读取,由于第三次缓冲区中没有检索到结尾标志\r\n,所以将84个字节写入临时文件
第五次填充缓冲区,从输入流中读取数据,本次再次填满了缓冲区,读取的数据中也没有检索到结尾标志\r\n,所以4054个字节写入临时文件
第五次读取,由于第五次缓冲区中没有检索到结尾标志\r\n,所以将4054个字节写入临时文件
第六次填充缓冲区,从输入流中读取数据,本次再次填满了缓冲区,读取的数据中也没有检索到结尾标志\r\n,所以4054个字节写入临时文件
第六次读取,由于第六次缓冲区中没有检索到结尾标志\r\n,所以将4054个字节写入临时文件
第七次填充缓冲区,从输入流中读取数据,本次读取了84个字节,加上保留的42个字节,缓冲区中有126个字节,检测到126个字节中也没有检索到结尾标志\r\n符,所以有84个字节写入临时文件
第七次读取,由于第三次缓冲区中没有检索到结尾标志\r\n,所以将84个字节写入临时文件
第八次填充缓冲区,从输入流中读取数据,本次再次填满了缓冲区,读取的数据中也没有检索到结尾标志\r\n,所以4054个字节写入临时文件
第八次读取,由于第八次缓冲区中没有检索到结尾标志\r\n,所以将4054个字节写入临时文件
第九次填充缓冲区,从输入流中读取数据,本次再次填满了缓冲区,读取的数据中也没有检索到结尾标志\r\n,所以4054个字节写入临时文件
第九次读取,由于第九次缓冲区中没有检索到结尾标志\r\n,所以将4054个字节写入临时文件
第十次填充缓冲区,从输入流中读取数据,本次读取了84个字节,加上保留的42个字节,缓冲区中有126个字节,检测到126个字节中也没有检索到结尾标志\r\n,所以有84个字节写入临时文件
第十次读取,由于第十次缓冲区中没有检索到结尾标志\r\n,所以将84个字节写入临时文件
第十一次填充缓冲区,从输入流中读取数据,本次再次填满了缓冲区,在缓冲区索引1265处检索到结尾标志\r\n的\r,所以有1265个字节写入临时文件
第十一次读取,将1265个字节写入临时文件,上传文件"测试.xlsx"缓存完成
4.8 将创建好的FileItem添加到List中
List items = new ArrayList();
items.add(fileItem);
4.9 处理第三个上传文件"测试1.xlsx"
1.查找结尾标志\r\n,1265、1266检索到就是结尾标志
2.丢弃结尾标志\r\n
discardBodyData();
3.读取分隔符boundary,更新head
更新后head=1308,表示从索引1308开始是这一部分的实体首部
4.获取实体首部并解析
Content-Disposition: form-data; name="fileName"; filename="测试1.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
5.创建FileItemStream实例
6.根据FileItemStream实例创建FileItem实例
7.缓存上传文件"测试1.xlsx"
第一次读取,读取的数据中也没有检索到结尾标志\r\n,所以有2598个直接写入临时文件
第二次填充缓冲区,从输入流中读取数据,本次再次填满了缓冲区,读取的数据中也没有检索到结尾标志\r\n,所以4054个字节写入临时文件
第二次读取,由于第二次缓冲区中没有检索到结尾标志\r\n,所以将4054个字节写入临时文件
第三次填充缓冲区,从输入流中读取数据,本次读取了84个字节,加上保留的42个字节,缓冲区中有126个字节,检测到126个字节中也没有检索到结尾标志\r\n符,所以有84个字节写入临时文件
第三次读取,由于第三次缓冲区中没有检索到结尾标志\r\n,所以将84个字节写入临时文件
第四次填充缓冲区,从输入流中读取数据,读取到了3700个字节,在缓冲区索引3696处检索到结尾标志\r\n的\r,所以3696个字节写入临时文件
第四次读取,将3696个字节写入临时文件
4.10 处理结尾
1.查找结尾标志\r\n,索引3296、3297检索到就是结尾标志\r\n
2.丢弃结尾标志\r\n,head更新到3298
discardBodyData();
3.读取分隔符boundary, 当前分隔符去掉了前缀CRLF,长度为40,更新head = head+ 40 = 3298+40=3738
4.索引3738、3739检索到[45,45],即双破折号
实体结尾是以分隔符+“--”结尾的,说明当前实体已经处理完毕,即上传文件已经缓存,在进行后续处理
5.返回List<FileItem>