[导入]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
總結
以上是生活随笔為你收集整理的[导入]SunriseUpload.0.9.1的源码分析(七)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 白话设计模式——Builder
- 下一篇: 父亲姓胡,母亲姓金取个网名