文件上传的基本原理(二)

来源:互联网 发布:ubuntu安装在哪个区 编辑:程序博客网 时间:2024/05/29 16:00

一.概述

1.在文件上传的基本原理(一)中介绍了HTTP报文的简介以及报文的基本格式
2.本节主要介绍common-Fileupload是如何解析请求报文的实体的

二.common-fileupload的类结构


三.解析大致流程


四.解析实例讲解

4.1 假设一个报文如图


------WebKitFormBoundaryE2KKgliuAAe4H3XBContent-Disposition: form-data; name="text$9702005251"C:\fakepath\测试.xlsx------WebKitFormBoundaryE2KKgliuAAe4H3XBContent-Disposition: form-data; name="fileName"; filename="测试.xlsx"Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet------WebKitFormBoundaryE2KKgliuAAe4H3XBContent-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, 
------WebKitFormBoundaryElA5s1ivhyHVRZpxContent-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>

4.11 解析完成,继续后续处理