天天看點

apache fileupload源碼分析檔案上傳格式檔案上傳注意點:apache fileupload的解析

先來看下含有檔案上傳時的表單送出是怎樣的格式

form表單送出内容如下

apache fileupload源碼分析檔案上傳格式檔案上傳注意點:apache fileupload的解析

從上面可以看到,含有檔案上傳的格式是這樣組織的。

檔案類型字段

其他類型字段

結束

對于上面的檔案内容,chrome浏覽器是不顯示的,換成firefox可以看到,如下圖所示

apache fileupload源碼分析檔案上傳格式檔案上傳注意點:apache fileupload的解析

同時我們還可以注意到,不同的浏覽器,分隔符是不一樣的,在請求頭

中指明了分隔符的内容。

一定是post送出,如果換成get送出,則浏覽器預設僅僅把檔案名作為屬性值來上傳,不會上傳檔案内容,如下

apache fileupload源碼分析檔案上傳格式檔案上傳注意點:apache fileupload的解析

form表單中一定不要忘了添加

否則的話,浏覽器則不是按照上述的格式來來傳遞資料的。

上述兩點才能保證浏覽器正常的進行檔案上傳。

有了上述檔案上傳的組織格式,我們就需要合理的設計背景的解析方式,下面來看下apache fileupload的使用。先來看下整體的流程圖 

apache fileupload源碼分析檔案上傳格式檔案上傳注意點:apache fileupload的解析

apache fileupload分servlets and portlets兩種情形來處理。servlet我們很熟悉,而portlets我也沒用過,可自行去搜尋。

對于httpservletrequest來說,另一個不再說明,自行檢視源碼,判斷規則如下:

是否是post請求

contenttype是否以multipart/開頭

見源碼:

servlet的輸入參數為httpservletrequest,portlets的輸入參數為actionrequest,資料來源不同,為了統一友善後面的資料處理,引入了requestcontext接口,來統一一下目标資料的擷取。

接口requestcontext的實作類:

servletrequestcontext

portletrequestcontext

此時requestcontext就作為了資料源,不再與httpservletrequest和actionrequest打交道。

上述的實作過程是由fileupload的子類servletfileupload和portletfileupload分别完成包裝的。

父類fileupload的子類:

servletfileupload

portletfileupload

源碼展示如下:

servletfileupload類

portletfileupload類

上述的parserequest便完成了整個request的解析過程,内容如下:

分以下兩個大步驟:

根據requestcontext資料源得到解析後的資料集合 fileitemiterator

周遊fileitemiterator中的每個item,類型為fileitemstreamimpl,使用fileitemfactory工廠類來将每個fileitemstreamimpl轉化成最終的fileitem

fileitemiterator内容如下:

這就是一個輪詢器,可以假想成fileitemstream的集合,實際上不是,後面會進行介紹

fileitemstream則是之前上傳檔案格式内容

或者

的封裝,代碼如下

然後我們來具體看下由requestcontext如何解析成一個fileitemiterator的:

new了一個fileitemiteratorimpl,來看下具體的過程:

要點:

contenttype進行判斷,是否以multipart開頭

判斷整個請求流的資料大小是否超過sizemax最大設定

擷取重要的分隔符boundary資訊

封裝了request請求流的資料,包裝為multipartstream類型

也可以設定通知器,來通知流的讀取進度

這裡可以看到fileitemiteratorimpl并不是fileitemstreamimpl的集合,其實是fileitemiteratorimpl内部包含了一個fileitemstreamimpl屬性。fileitemiteratorimpl的一些重要屬性和方法如下:

findnextitem()方法就是建立新的fileitemstreamimpl來替代目前的fileitemstreamimpl,并更新起始位置。

每次調用fileitemiteratorimpl的hasnext()方法,會建立一個新的fileitemstreamimpl指派給fileitemstreamimpl屬性

每次調用fileitemiteratorimpl的next()方法,就會傳回目前fileitemstreamimpl屬性的值

建立的每個fileitemstreamimpl都會共享fileitemiteratorimpl的multipartstream總流,僅僅更新了要讀取的起始位置

其他應用其實就可以周遊fileitemiteratorimpl拿到每一項fileitemstreamimpl的解析資料了。隻是這時候資料

存儲在記憶體中的

每個fileitemstreamimpl都是共享一個總的流,不能被重複讀取

我們想把這些檔案資料存在臨時檔案中,就需要使用使用fileitemfactory來進行下轉化成fileitem。每個fileitem才是互相獨立的,而fileitemstreamimpl則不是,每個fileitem也是對應上傳檔案格式中的每一項,如下

fileitemfactory的實作類diskfileitemfactory即将資料存儲在硬碟上,代碼如下:

我們從上面可以看到,其實fileitemfactory的createitem方法,并沒有為fileitem的流指派。再回顧下上文parserequest方法的源代碼,指派發生在這裡

上述fileitem的openstream()方法如下:

gettempfile()會根據fileitemfactory的臨時檔案目錄配置repository,建立一個臨時檔案,用于上傳檔案。 

這裡又用到了commons-io包中的deferredfileoutputstream類。

當資料數量小于sizethreshold門檻值時,存儲在記憶體中

當資料數量大于sizethreshold門檻值時,存儲到傳入的臨時檔案中

至此,fileitem都被建立出來了,整個過程就結束了。