接着分析了几个小时的SunriseUpload.0.9.1的源码。
终于明白了作者的整体思路。在此就做一个总结。

首先,要想能上传很大的文件,我们就必须编写一个HttpModule来自己处理用户上传的信息。这个模块可以拦截用户所有的请求,因此有必须选择性的做一此判断。如果是mulitypart/form-data请求时,会有这样的一个ContentHeader在请求数据里:
multipart/form-data; boundary=---------------------------7d51a51e25012c
我们可以从m_application.Request.ContentType.ToLower()的方法取得这个数据,注意boundary后面的数据是随机的,它是用来区分上传变量名与数据的分隔符,因此为了能分析后面的数据,必须先把它取出来。
然后就是从用户的请求那里读取数据,第一次读取数据的时候,我们可以用:
m_workRequest.GetPreloadedEntityBody();
其中m_workRequest是HttpWorkerRequest的对象实例。如果用户提交的数据不是很长,那么一次性就可以读取完了。而我们最主要的目的就是为了分析这里的数据。
直接输出里面的数据可以得到类似这样的内容,为了方便说明,我加上了行号:
[01]-----------------------------7d51f321004ec
[02]Content-Disposition: form-data; name="__VIEWSTATE"
[03]
[04]dDwtNTMwNzcxMzI0Ozs+AsSfEXPXvGi5+b7dOBAso7F1wlU=
[05]-----------------------------7d51f321004ec
[06]Content-Disposition: form-data; name="m_file"; filename="D:\WuCountry\Pictures\logo.png"
[07]Content-Type: image/x-png
[08]
[09]?PNG
  
IHDR   ]   &   |??á   gAMA  ˉè7?é(这里是上传的文件二进制数据,我删除了一些)
[10]-----------------------------7d51f321004ec
[11]Content-Disposition: form-data; name="m_file"; filename="D:\WuCountry\Pictures\logo.png"
[12]Content-Type: image/x-png
[13]
[14]?PNG
  
IHDR   ]   &   |??á   gAMA  ˉè7?é   tEXtSof(这里是上传的文件二进制数据,我删除了一些)
[15]-----------------------------7d51f321004ec
[16]Content-Disposition: form-data; name="Button1"
[17]
[18]Button
[19]-----------------------------7d51f321004ec--
这里,乱码是上传的二进制文件(删除了一些,而且假设有两个文件上传)。可以看到,其中有一个__VIEWSTATE(02行)表单变量名,它是ASP.net自己维护的信息,我们就不说了。还有一个Button1(16行),它的值是Button,其它的是两个上传的二进制文件。注意,这里的分隔符比ContentType里的多两个字节,而这两个字节就是"--",数一下就知道了,后面的多两个。不知道为什么?我们可以看到,第09行的数据和第14行的数据就是我们上传的文件,这里我删除了大部分,只留了一点点做例子。看看作者的思想,作者想在这些数据到达页面以前,我们先把它处理一次,作者是这样处理的,他想让用我们的模块处理后的内容变成下面的样子,为了方便说明,我加上了行号:
[01]-----------------------------7d51f321004ec
[02]Content-Disposition: form-data; name="Sunrise_Web_Upload_UploadGUID"
[03]
[04]d922d57d-c1ef-4c02-a3d4-30fae78cb599
[05]-----------------------------7d51f321004ec
[06]Content-Disposition: form-data; name="__VIEWSTATE"
[07]
[08]dDwtNTMwNzcxMzI0Ozs+AsSfEXPXvGi5+b7dOBAso7F1wlU=
[09]-----------------------------7d51f321004ec
[10]Content-Disposition: form-data; name="m_file"
[11]
[12]Content-Type: image/x-png;filename="D:\WuCountry\Pictures\logo.png";filepath="a661ab10-312e-4372-93e6-f37d662ca38f.png"
[13]-----------------------------7d51f321004ec
[14]Content-Disposition: form-data; name="m_file"
[15]
[16]Content-Type: image/x-png;filename="D:\WuCountry\Pictures\logo.png";filepath="sd328d7a-312e-4372-93e6-f37d662ca38f.png"
[17]-----------------------------7d51f321004ec
[18]Content-Disposition: form-data; name="Button1"
[19]
[20]Button
[21]-----------------------------7d51f321004ec--
其中多了一个Upload_GUID,它是用来唯一分一个上传任务的,我们可以不管它,主要是为了处理上传进度条。好了,这样一来,所有的数据都是文本的了,那么二进制的文件到什么地方去了呢?就是filepath="a661ab10-312e-4372-93e6-f37d662ca38f.png"这就记录了文件的位置,也是用GUID生成的唯一文件名,它存放在系统的临时目录里,也就是说,还在我们自己去把它COPY到想存放的目录,这是很容易的,用一个Move就行了。
好了,思路已经很明确了,那么就只用来处理数据了,也就是上一篇文章的核心ReqponseStream类的算法。
这里请注意,因为数据并不是一次提交上来的,上面的数据在任何一个地方都有可能出现断点问题,因此我们不防假设第一次数据断在源请求文件的第09行的某个位置,那么我们用同m_workRequest.GetPreloadedEntityBody();取得的数据可能就是这样的:
[01]-----------------------------7d51f321004ec
[02]Content-Disposition: form-data; name="__VIEWSTATE"
[03]
[04]dDwtNTMwNzcxMzI0Ozs+AsSfEXPXvGi5+b7dOBAso7F1wlU=
[05]-----------------------------7d51f321004ec
[06]Content-Disposition: form-data; name="m_file"; filename="D:\WuCountry\Pictures\logo.png"
[07]Content-Type: image/x-png
[08]
[09]?PNG
  
IHDR   ]
而后我们用m_workRequest.ReadEntityBody(m_readBuffer,m_bufferSize);取得的数据可能是这样的,我们再假设第二个断点在14行
[09] ]   &   |??á   gAMA  ˉè7?é(这里是上传的文件二进制数据,我删除了一些)
[10]-----------------------------7d51f321004ec
[11]Content-Disposition: form-data; name="m_file"; filename="D:\WuCountry\Pictures\logo.png"
[12]Content-Type: image/x-png
[13]
[14]?PNG
  
IHDR   ]   &
更多可能的是取得的数据全部是文件数据,也就是全部是二进制。那么应该怎样处理这个问题呢?通过研究作者的算法,作者是这样做的:
RequestStream类有一个m_contentTextBody(这是我自己取的名字),它用来处理完读取的数据后记录回文本信息,也就是说最后这里面的内容就是我们想生成的所有的文本内容。然后把文件存在在临时文件夹里,但由于文件可能没有传完,所以在RequestStream类里还必须记录上传文件的一些信息。作者想在第二次提交的数据处理的时候,再把前面的m_contentTextBody再传回RequestStream类来处理,也就是说第二次处理数据时,原本应该全部都是二进制数据了,但作者把数据改为这样的:
[01]-----------------------------7d51f321004ec
[02]Content-Disposition: form-data; name="__VIEWSTATE"
[03]
[04]dDwtNTMwNzcxMzI0Ozs+AsSfEXPXvGi5+b7dOBAso7F1wlU=
[05]-----------------------------7d51f321004ec
[06]Content-Disposition: form-data; name="m_file"; filename="D:\WuCountry\Pictures\logo.png"
[07]Content-Type: image/x-png
[08]
[09]?PNG
  
IHDR   ]   & (这一行是第二次读取时真正的数据,前面的8行都是添加上去的)
这样一来,就感觉又是一个新的请求了,可以当成是第一次请求那样处理数据。但这次不能重新打开新的文件流写入数据,而应该还用上一次的文件流来写入上一次的文件中。当遇到一个文件结束的时候,关闭前一个文件。如果遇到第二个文件就再来打开一个文件流来填写数据。
我觉得这是很不好的,因为每次把数据当成是第一次的样子来处理,这样很浪费资源,可以看的出来,每次都要多处理好多字节的数据。而在大文件上传的时候更是不能忽略这些数据了,而在算法里,把字节数组的复制也搞的很复杂。这是我觉得作者最不可取的地方。因为每次处理读取的数据,都会NEW一个RequestStream对象,这样太浪费资源了。(我决定重新改写这一算法。)
好了,最后一个任务就是把m_contentTextBody添加到RequestContent里去,否则我们不能在后来的处理中得到数据,因此还有一点麻烦,作者用到了C#的反射功能,得到IIS的应用程序域(我还不知道是不是),然后添加进数据,这是核心代码:
private byte[] InjectTextParts(HttpWorkerRequest request, byte[] textParts)
{
 Type type;
 BindingFlags flags = (BindingFlags.NonPublic | BindingFlags.Instance);
 //Is there application host IIS6.0?
 if (Utils.GetContext().Request.ServerVariables["SERVER_SOFTWARE"].Equals("Microsoft-IIS/6.0"))
 {
  type = request.GetType().BaseType.BaseType;
 }
 else
 {
  type = request.GetType().BaseType;
 }
 int dataLength = textParts.Length;
 //Set values of working request
 type.GetField("_contentAvailLength", flags).SetValue(request, dataLength);
 type.GetField("_contentTotalLength", flags).SetValue(request, dataLength);
 type.GetField("_preloadedContent", flags).SetValue(request, textParts);
 type.GetField("_preloadedContentRead", flags).SetValue(request, true);
 return textParts;
}
好了,还只剩下最后一个,就是上传进度条的处理问题了。在后面的文章里会继续分析吧,其实上我也开始在写自己的上传组件了,当然一些技术上的方法还得用作者的思想,但一些算法或者一些我觉得自己有突破的地方,我会做一些修改的。

文章来源:http://computer.mblogger.cn/wucountry/posts/48662.aspx

[导入]SunriseUpload.0.9.1的源码分析(七)相关推荐

  1. TeamTalk客户端源码分析七

    TeamTalk客户端源码分析七 一,CBaseSocket类 二,select模型 三,样例分析:登录功能 上篇文章我们分析了network模块中的引用计数,智能锁,异步回调机制以及数据的序列化和反 ...

  2. Spring Core Container 源码分析七:注册 Bean Definitions

    前言 原本以为,Spring 通过解析 bean 的配置,生成并注册 bean defintions 的过程不太复杂,比较简单,不用单独开辟一篇博文来讲述:但是当在分析前面两个章节有关 @Autowi ...

  3. Spring Security源码分析七:Spring Security 记住我

    有这样一个场景--有个用户初访并登录了你的网站,然而第二天他又来了,却必须再次登录.于是就有了"记住我"这样的功能来方便用户使用,然而有一件不言自明的事情,那就是这种认证状态的&q ...

  4. 【转】ABP源码分析七:Setting 以及 Mail

    本文主要说明Setting的实现以及Mail这个功能模块如何使用Setting. 首先区分一下ABP中的Setting和Configuration. Setting一般用于需要通过外部配置文件(或数据 ...

  5. spring boot 源码分析(七) 事件机制 之 SpringApplicationEvent

    2019独角兽企业重金招聘Python工程师标准>>> 一.前言 前面的文章我们讲解了一下spring boot配置文件加载的相关源码分析,下面我们将从源码角度讲解一下spring  ...

  6. WinCE6.0学习之EBoot源码分析----startup.s(三)

    下面将详细叙述MMU的设置,也是本人花费时间最多的一部分内容,无论是2410.6410甚至是Cortex-A8核的ARM,MMU的设置基本都一样,所以移植时这部分可以直接搬过来,只需要更改全局内存映射 ...

  7. tensorflow 2.0 Layer定义的源码分析

    一直不太懂tensorflow 2.0的层的操作,所以跑去看了下源码,其实也不难,如果对python比较了解的话,自己去看下源码也很快就能理解了. tensorflow 2.0中的api都是使用了ke ...

  8. rnnlm源码分析(七)

    系列前言 参考文献: RNNLM - Recurrent Neural Network  Language Modeling Toolkit(点此阅读) Recurrent neural networ ...

  9. springfox源码_springfox 源码分析(七) 文档初始化

    时间:2019-5-23 20:12:04 地点:家中 通过前面几篇文章对springfox的介绍,以及我们的学习准备工作,这篇我们将正式来探索springfox是如何初始化的 我们在学算法的时候,其 ...

最新文章

  1. Struts2标签库常用标签(转)
  2. java的课后作业咋写_写的简单的java第三季的课后作业
  3. poj 3373 Changing Digits
  4. Linux服务器之间传输文件 scp命令
  5. 微软若“无故”解雇暴雪 CEO,将付 1500 万美元“分手费”
  6. 2021年高压电工考试APP及高压电工模拟考试题库
  7. html 设置打印纸张大小怎么设置,打印机纸张大小怎么设置 打印机纸张大小设置方法【详细介绍】...
  8. Python打印二叉树的左视图、右视图
  9. 中国百货业重磅报告!新零售玩得好的已经尝到甜头了
  10. matlab中signal pulses,MATLAB信号处理仿真-基带脉冲成形的数字滤波器
  11. 钱生钱最好的办法是什么?
  12. Leetcode1905. 统计子岛屿(medium)
  13. pytorch教程(1.5)——梯度自动计算
  14. Win10 升级安装全攻略
  15. cadence学习笔记 从ultra librarain网站下载封装并生成封装文件
  16. Q4财报一如既往增长稳健 陌陌为何能逆势增长?
  17. Wpf应用程序进入全屏和退出全屏
  18. m序列产生器(FPGA学习)
  19. 电商物流一分四分拣机的开发和源码
  20. 小米运动蓝牙耳机重新配对_小米运动蓝牙耳机怎么重置

热门文章

  1. OpenCV将现有算法移植到G-API的实例(附完整代码)
  2. OpenCV图像中的人脸界标检测
  3. C语言判断树是否为求和树(附完整源码)
  4. OpenGL Shadow Mapping阴影贴图的实例
  5. C++用stack实现深度优先搜索DFS(附完整源码)
  6. c语言实现堆Stack(附完整源码)
  7. C语言十进制数转换为八进制(附完整源码)
  8. C++STL的queue容器
  9. 经典C语言程序100例之四六
  10. 3.商品可视化展示与文本处理