zoukankan      html  css  js  c++  java
  • [导入]SunriseUpload.0.9.1的源码分析(七)

    接着分析了几个小时的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
    ================================
      /\_/\                        
     (=^o^=)  Wu.Country@侠缘      
     (~)@(~)  一辈子,用心做一件事!
    --------------------------------
      学而不思则罔,思而不学则怠!  
    ================================
  • 相关阅读:
    接口的故事
    Bash CookBook(一)--基础
    Spring学习笔记(四)--MVC概述
    Spring学习笔记(三)--Convert System设计
    java web框架发展的新趋势--跨界轻型App
    由Strurts2漏洞引开谈谈web代码安全问题
    Java线程同步之一--AQS
    Android Studio 0.4 + PhoneGap 3.3 开发环境的搭建
    redis的多线程
    原电商设计框架
  • 原文地址:https://www.cnblogs.com/WuCountry/p/305653.html
Copyright © 2011-2022 走看看