文件上传的基本原理(二)
来源:互联网 发布: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 解析完成,继续后续处理
阅读全文
0 0
- 文件上传的基本原理(二)
- 文件上传的基本原理(一)
- HTTP 文件上传的基本原理
- HTTP 文件上传的基本原理
- HTTP 文件上传的基本原理
- nant的使用(二)-基本原理之Build文件
- 文件的上传(二)
- VPN基本原理之二(SSL VPN的基本原理分析)
- VPN基本原理之二(SSL VPN的基本原理分析)
- 文件上传(二)
- 文件的上传与下载(二)
- 0024网络爬虫的基本原理(二)
- Hibernate (二)基本原理
- 下载文件的基本原理
- 上传文件中应当注意的细节(二)
- ASP.NET网站 文件的上传与下载(二)
- 文件、图片的上传并实现图片预览(二)
- 二层交换机的基本原理
- BZOJ1568: [JSOI2008]Blue Mary开公司
- centos下使用zlib库支持gzip流解压
- DataTable属性详解
- 让你爱不释手的几款智能居家设备
- 以pdf转cad为例,所有格式之间任意转换
- 文件上传的基本原理(二)
- window.onload=function(){}与$(document).ready(function (){})区别
- JavaEE开发之SpringMVC中的静态资源映射及服务器推送技术
- phpcms安装教程
- Linux下搭建自己的Git服务器
- 同步队列
- js+csss实现的一个带复选框的下拉框
- Segmentation fault到底是何方妖孽
- VC++文件路径和文件名处理函数