<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Apache Common fileUpload API 詳解(轉(zhuǎn))

    轉(zhuǎn)自:http://m.tkk7.com/gembin/archive/2008/05/08/199240.html

    文件上傳組件的應(yīng)用與編寫 
    在許多Web站點(diǎn)應(yīng)用中都需要為用戶提供通過瀏覽器上傳文檔資料的功能,例如,上傳郵件附件、個(gè)人相片、共享資料等。對(duì)文件上傳功能,在 

    瀏覽器端提供了較好的支持,只要將FORM表單的enctype屬性設(shè)置為“multipart/form-data”即可;但在Web服務(wù)器端如何獲取瀏覽器上傳的文 

    件,需要進(jìn)行復(fù)雜的編程處理。為了簡(jiǎn)化和幫助Web開發(fā)人員接收瀏覽器上傳的文件,一些公司和組織專門開發(fā)了文件上傳組件。本章將詳細(xì)介 

    紹如何使用Apache文件上傳組件,以及分析該組件源程序的設(shè)計(jì)思路和實(shí)現(xiàn)方法。 
    1.1 準(zhǔn)備實(shí)驗(yàn)環(huán)境 
    按下面的步驟為本章的例子程序建立運(yùn)行環(huán)境: 
    (1)在Tomcat 5.5.12的<tomcat的安裝目錄>\webapps目錄中創(chuàng)建一個(gè)名為fileupload的子目錄,并在fileupload目錄中創(chuàng)建一個(gè)名為test.html 

    的網(wǎng)頁(yè)文件,在該文件中寫上“這是test.html頁(yè)面的原始內(nèi)容!”這幾個(gè)字符。 
    (2)在<tomcat的安裝目錄>\webapps\fileupload目錄中創(chuàng)建一個(gè)名為WEB-INF的子目錄,在WEB-INF目錄中創(chuàng)建一個(gè)名為classes的子目錄和一個(gè) 

    web.xml文件,web.xml文件內(nèi)容如下: 
    <web-app> 
    </web-app> 
    (3)要使用Apache文件上傳組件,首先需要安裝Apache文件上傳組件包。在<tomcat的安裝目錄>\webapps\fileupload\WEB-INF目錄中創(chuàng)建一個(gè) 

    名為lib的子目錄,然后從網(wǎng)址http://jakarta.apache.org/commons/fileupload下載到Apache組件的二進(jìn)制發(fā)行包,在本書的附帶帶光盤中也 

    提供了該組件的二進(jìn)制發(fā)行包,文件名為commons-fileupload-1.0.zip。從commons-fileupload-1.0.zip壓縮包中解壓出commons-fileupload 

    -1.0.jar文件,將它放置進(jìn)<tomcat的安裝目錄>\webapps\fileupload\WEB-INF\lib目錄中,就完成了Apache文件上傳組件的安裝。 
    (4)在<tomcat的安裝目錄>\webapps\fileupload目錄中創(chuàng)建一個(gè)名為src的子目錄,src目錄用于放置本章編寫的Java源程序。為了便于對(duì) 

    Servlet源文件進(jìn)行編譯,在src目錄中編寫一個(gè)compile.bat批處理文件,如例程1-1所示。 
    例程1-1 compile.bat 



    set PATH=C:\jdk1.5.0_01\bin;%path% 
    set CLASSPATH=C:\tomcat-5.5.12\common\lib\servlet-api.jar;C:\tomcat-5.5.12\\webapps\ 
    fileupload\WEB-INF\lib\commons-fileupload-1.0.jar;%CLASSPATH% 
    javac -d ..\WEB-INF\classes %1 
    pause 



    在compile.bat批處理文件中要注意將commons-fileupload-1.0.jar文件的路徑加入到CLASSPATH環(huán)境變量中和確保編譯后生成的class文件存放 

    到<tomcat安裝目錄>\webapps\fileupload\WEB-INF\classes目錄中,上面的CLASSPATH環(huán)境變量的設(shè)置值由于排版原因進(jìn)行了換行,實(shí)際上不 

    應(yīng)該有換行。接著在src目錄中為compile.bat文件創(chuàng)建一個(gè)快捷方式,以后只要在Windows資源管理器窗口中將Java源文件拖動(dòng)到compile.bat 

    文件的快捷方式上,就可以完成Java源程序的編譯了。之所以要?jiǎng)?chuàng)建compile.bat文件的快捷方式,是因?yàn)橹苯訉ava源程序拖動(dòng)到 

    compile.bat批處理文件時(shí),compile.bat批處理文件內(nèi)編寫的相對(duì)路徑不被支持。創(chuàng)建完的fileupload目錄中的文件結(jié)構(gòu)如圖1.1所示。 
    圖1.1 
    (4)啟動(dòng)Tomcat,在本地計(jì)算機(jī)的瀏覽器地址欄中輸入如下地址: 
    http://localhost:8080/fileupload/test.html 
    驗(yàn)證瀏覽器能夠成功到該網(wǎng)頁(yè)文檔。如果瀏覽器無(wú)法訪問到該網(wǎng)頁(yè)文檔,請(qǐng)檢查前面的操作步驟和改正問題,直到瀏覽器能夠成功到該網(wǎng)頁(yè)文 

    檔為止。 
    (5)為了讓/fileupload這個(gè)WEB應(yīng)用程序能自動(dòng)重新裝載發(fā)生了修改的Servlet程序,需要修改Tomcat的server.xml文件,在該文件的<Host>元 

    素中增加如下一個(gè)<Context>子元素: 
    <Context path="/fileupload" docBase="fileupload" reloadable="true"/> 
    保存server.xml文件后,重新啟動(dòng)Tomcat。 

    1.2 Apache文件上傳組件的應(yīng)用 
    Java Web開發(fā)人員可以使用Apache文件上傳組件來接收瀏覽器上傳的文件,該組件由多個(gè)類共同組成,但是,對(duì)于使用該組件來編寫文件上傳 

    功能的Java Web開發(fā)人員來說,只需要了解和使用其中的三個(gè)類:DiskFileUpload、FileItem和FileUploadException。這三個(gè)類全部位于 

    org.apache.commons.fileupload包中。 
    1.2.1查看API文檔 
    在準(zhǔn)備實(shí)驗(yàn)環(huán)境時(shí)獲得的commons-fileupload-1.0.zip文件的解壓縮目錄中可以看到一個(gè)docs的子目錄,其中包含了Apache文件上傳組件中的 

    各個(gè)API類的幫助文檔,從這個(gè)文檔中可以了解到各個(gè)API類的使用幫助信息。打開文件上傳組件API幫助文檔中的index.html頁(yè)面,在左側(cè)分欄 

    窗口頁(yè)面中列出了文件上傳組件中的各個(gè)API類的名稱,在右側(cè)分欄窗口頁(yè)面的底部列出了一段示例代碼,如圖1.2所示。 
    圖1.2 
    讀者不需要逐個(gè)去閱讀圖1.2中列出的各個(gè)API類的幫助文檔,而應(yīng)該以圖1.2中的示例代碼為線索,以其中所使用到的類為入口點(diǎn),按圖索驥地 

    進(jìn)行閱讀,對(duì)于示例代碼中調(diào)用到的各個(gè)API類的方法則應(yīng)重點(diǎn)掌握。 
    1.2.2 DiskFileUpload類 
    DiskFileUpload類是Apache文件上傳組件的核心類,應(yīng)用程序開發(fā)人員通過這個(gè)類來與Apache文件上傳組件進(jìn)行交互。下面介紹 

    DiskFileUpload類中的幾個(gè)常用的重要方法。 
    1.setSizeMax方法 
    setSizeMax方法用于設(shè)置請(qǐng)求消息實(shí)體內(nèi)容的最大允許大小,以防止客戶端故意通過上傳特大的文件來塞滿服務(wù)器端的存儲(chǔ)空間,單位為字節(jié) 

    。其完整語(yǔ)法定義如下: 
    public void setSizeMax(long sizeMax) 
    如果請(qǐng)求消息中的實(shí)體內(nèi)容的大小超過了setSizeMax方法的設(shè)置值,該方法將會(huì)拋出FileUploadException異常。 
    2.setSizeThreshold方法 
    Apache文件上傳組件在解析和處理上傳數(shù)據(jù)中的每個(gè)字段內(nèi)容時(shí),需要臨時(shí)保存解析出的數(shù)據(jù)。因?yàn)镴ava虛擬機(jī)默認(rèn)可以使用的內(nèi)存空間是有 

    限的(筆者測(cè)試不大于100M),超出限制時(shí)將會(huì)發(fā)生“java.lang.OutOfMemoryError”錯(cuò)誤,如果上傳的文件很大,例如上傳800M的文件,在 

    內(nèi)存中將無(wú)法保存該文件內(nèi)容,Apache文件上傳組件將用臨時(shí)文件來保存這些數(shù)據(jù);但如果上傳的文件很小,例如上傳600個(gè)字節(jié)的文件,顯然 

    將其直接保存在內(nèi)存中更加有效。setSizeThreshold方法用于設(shè)置是否使用臨時(shí)文件保存解析出的數(shù)據(jù)的那個(gè)臨界值,該方法傳入的參數(shù)的單 

    位是字節(jié)。其完整語(yǔ)法定義如下: 
    public void setSizeThreshold(int sizeThreshold) 
    3. setRepositoryPath方法 
    setRepositoryPath方法用于設(shè)置setSizeThreshold方法中提到的臨時(shí)文件的存放目錄,這里要求使用絕對(duì)路徑。其完整語(yǔ)法定義如下: 
    public void setRepositoryPath(String repositoryPath) 
    如果不設(shè)置存放路徑,那么臨時(shí)文件將被儲(chǔ)存在"java.io.tmpdir"這個(gè)JVM環(huán)境屬性所指定的目錄中,tomcat 5.5.9將這個(gè)屬性設(shè)置為了 

    “<tomcat安裝目錄>/temp/”目錄。 
    4. parseRequest方法 
    parseRequest 方法是DiskFileUpload類的重要方法,它是對(duì)HTTP請(qǐng)求消息進(jìn)行解析的入口方法,如果請(qǐng)求消息中的實(shí)體內(nèi)容的類型不是 

    “multipart/form-data”,該方法將拋出FileUploadException異常。parseRequest 方法解析出FORM表單中的每個(gè)字段的數(shù)據(jù),并將它們分別 

    包裝成獨(dú)立的FileItem對(duì)象,然后將這些FileItem對(duì)象加入進(jìn)一個(gè)List類型的集合對(duì)象中返回。parseRequest 方法的完整語(yǔ)法定義如下: 
    public List parseRequest(HttpServletRequest req) 
    parseRequest 方法還有一個(gè)重載方法,該方法集中處理上述所有方法的功能,其完整語(yǔ)法定義如下: 
    parseRequest(HttpServletRequest req,int sizeThreshold,long sizeMax, 
    String path) 
    這兩個(gè)parseRequest方法都會(huì)拋出FileUploadException異常。 
    5. isMultipartContent方法 
    isMultipartContent方法方法用于判斷請(qǐng)求消息中的內(nèi)容是否是“multipart/form-data”類型,是則返回true,否則返回false。 

    isMultipartContent方法是一個(gè)靜態(tài)方法,不用創(chuàng)建DiskFileUpload類的實(shí)例對(duì)象即可被調(diào)用,其完整語(yǔ)法定義如下: 
    public static final boolean isMultipartContent(HttpServletRequest req) 
    6. setHeaderEncoding方法 
    由于瀏覽器在提交FORM表單時(shí),會(huì)將普通表單中填寫的文本內(nèi)容傳遞給服務(wù)器,對(duì)于文件上傳字段,除了傳遞原始的文件內(nèi)容外,還要傳遞其 

    文件路徑名等信息,如后面的圖1.3所示。不管FORM表單采用的是“application/x-www-form-urlencoded”編碼,還是“multipart/form-data 

    ”編碼,它們僅僅是將各個(gè)FORM表單字段元素內(nèi)容組織到一起的一種格式,而這些內(nèi)容又是由某種字符集編碼來表示的。關(guān)于瀏覽器采用何種 

    字符集來編碼FORM表單字段中的內(nèi)容,請(qǐng)參看筆者編著的《深入體驗(yàn)java Web開發(fā)內(nèi)幕——核心基礎(chǔ)》一書中的第6.9.2的講解, 

    “multipart/form-data”類型的表單為表單字段內(nèi)容選擇字符集編碼的原理和方式與“application/x-www-form-urlencoded”類型的表單是 

    相同的。FORM表單中填寫的文本內(nèi)容和文件上傳字段中的文件路徑名在內(nèi)存中就是它們的某種字符集編碼的字節(jié)數(shù)組形式,Apache文件上傳組 

    件在讀取這些內(nèi)容時(shí),必須知道它們所采用的字符集編碼,才能將它們轉(zhuǎn)換成正確的字符文本返回。 
    對(duì)于瀏覽器上傳給WEB服務(wù)器的各個(gè)表單字段的描述頭內(nèi)容,Apache文件上傳組件都需要將它們轉(zhuǎn)換成字符串形式返回,setHeaderEncoding 方 

    法用于設(shè)置轉(zhuǎn)換時(shí)所使用的字符集編碼,其原理與筆者編著的《深入體驗(yàn)java Web開發(fā)內(nèi)幕——核心基礎(chǔ)》一書中的第6.9.4節(jié)講解的 

    ServletRequest.setCharacterEncoding方法相同。setHeaderEncoding 方法的完整語(yǔ)法定義如下: 
    public void setHeaderEncoding(String encoding) 
    其中,encoding參數(shù)用于指定將各個(gè)表單字段的描述頭內(nèi)容轉(zhuǎn)換成字符串時(shí)所使用的字符集編碼。 
    注意:如果讀者在使用Apache文件上傳組件時(shí)遇到了中文字符的亂碼問題,一般都是沒有正確調(diào)用setHeaderEncoding方法的原因。 
    1.2.3 FileItem類 
    FileItem類用來封裝單個(gè)表單字段元素的數(shù)據(jù),一個(gè)表單字段元素對(duì)應(yīng)一個(gè)FileItem對(duì)象,通過調(diào)用FileItem對(duì)象的方法可以獲得相關(guān)表單字 

    段元素的數(shù)據(jù)。FileItem是一個(gè)接口,在應(yīng)用程序中使用的實(shí)際上是該接口一個(gè)實(shí)現(xiàn)類,該實(shí)現(xiàn)類的名稱并不重要,程序可以采用FileItem接 

    口類型來對(duì)它進(jìn)行引用和訪問,為了便于講解,這里將FileItem實(shí)現(xiàn)類稱之為FileItem類。FileItem類還實(shí)現(xiàn)了Serializable接口,以支持序 

    列化操作。 
    對(duì)于“multipart/form-data”類型的FORM表單,瀏覽器上傳的實(shí)體內(nèi)容中的每個(gè)表單字段元素的數(shù)據(jù)之間用字段分隔界線進(jìn)行分割,兩個(gè)分隔 

    界線間的內(nèi)容稱為一個(gè)分區(qū),每個(gè)分區(qū)中的內(nèi)容可以被看作兩部分,一部分是對(duì)表單字段元素進(jìn)行描述的描述頭,另外一部是表單字段元素的 

    主體內(nèi)容,如圖1.3所示。 
    圖 1.3 
    主體部分有兩種可能性,要么是用戶填寫的表單內(nèi)容,要么是文件內(nèi)容。FileItem類對(duì)象實(shí)際上就是對(duì)圖1.3中的一個(gè)分區(qū)的數(shù)據(jù)進(jìn)行封裝的對(duì) 

    象,它內(nèi)部用了兩個(gè)成員變量來分別存儲(chǔ)描述頭和主體內(nèi)容,其中保存主體內(nèi)容的變量是一個(gè)輸出流類型的對(duì)象。當(dāng)主體內(nèi)容的大小小于 

    DiskFileUpload.setSizeThreshold方法設(shè)置的臨界值大小時(shí),這個(gè)流對(duì)象關(guān)聯(lián)到一片內(nèi)存,主體內(nèi)容將會(huì)被保存在內(nèi)存中。當(dāng)主體內(nèi)容的數(shù)據(jù) 

    超過DiskFileUpload.setSizeThreshold方法設(shè)置的臨界值大小時(shí),這個(gè)流對(duì)象關(guān)聯(lián)到硬盤上的一個(gè)臨時(shí)文件,主體內(nèi)容將被保存到該臨時(shí)文件 

    中。臨時(shí)文件的存儲(chǔ)目錄由DiskFileUpload.setRepositoryPath方法設(shè)置,臨時(shí)文件名的格式為“upload_00000005(八位或八位以上的數(shù)字) 

    .tmp”這種形式,F(xiàn)ileItem類內(nèi)部提供了維護(hù)臨時(shí)文件名中的數(shù)值不重復(fù)的機(jī)制,以保證了臨時(shí)文件名的唯一性。當(dāng)應(yīng)用程序?qū)⒅黧w內(nèi)容保存 

    到一個(gè)指定的文件中時(shí),或者在FileItem對(duì)象被垃圾回收器回收時(shí),或者Java虛擬機(jī)結(jié)束時(shí),Apache文件上傳組件都會(huì)嘗試刪除臨時(shí)文件,以 

    盡量保證臨時(shí)文件能被及時(shí)清除。 
    下面介紹FileItem類中的幾個(gè)常用的方法: 
    1. isFormField方法 
    isFormField方法用于判斷FileItem類對(duì)象封裝的數(shù)據(jù)是否屬于一個(gè)普通表單字段,還是屬于一個(gè)文件表單字段,如果是普通表單字段則返回 

    true,否則返回false。該方法的完整語(yǔ)法定義如下: 
    public boolean isFormField() 
    2. getName方法 
    getName方法用于獲得文件上傳字段中的文件名,對(duì)于圖1.3中的第三個(gè)分區(qū)所示的描述頭,getName方法返回的結(jié)果為字符串“C:\bg.gif”。 

    如果FileItem類對(duì)象對(duì)應(yīng)的是普通表單字段,getName方法將返回null。即使用戶沒有通過網(wǎng)頁(yè)表單中的文件字段傳遞任何文件,但只要設(shè)置了 

    文件表單字段的name屬性,瀏覽器也會(huì)將文件字段的信息傳遞給服務(wù)器,只是文件名和文件內(nèi)容部分都為空,但這個(gè)表單字段仍然對(duì)應(yīng)一個(gè) 

    FileItem對(duì)象,此時(shí),getName方法返回結(jié)果為空字符串"",讀者在調(diào)用Apache文件上傳組件時(shí)要注意考慮這個(gè)情況。getName方法的完整語(yǔ)法 

    定義如下: 
    public String getName() 
    注意:如果用戶使用Windows系統(tǒng)上傳文件,瀏覽器將傳遞該文件的完整路徑,如果用戶使用Linux或者Unix系統(tǒng)上傳文件,瀏覽器將只傳遞該 

    文件的名稱部分。 
    3.getFieldName方法 
    getFieldName方法用于返回表單字段元素的name屬性值,也就是返回圖1.3中的各個(gè)描述頭部分中的name屬性值,例如“name=p1”中的“p1” 

    。getFieldName方法的完整語(yǔ)法定義如下: 
    public String getFieldName() 
    4. write方法 
    write方法用于將FileItem對(duì)象中保存的主體內(nèi)容保存到某個(gè)指定的文件中。如果FileItem對(duì)象中的主體內(nèi)容是保存在某個(gè)臨時(shí)文件中,該方法 

    順利完成后,臨時(shí)文件有可能會(huì)被清除。該方法也可將普通表單字段內(nèi)容寫入到一個(gè)文件中,但它主要用途是將上傳的文件內(nèi)容保存在本地文 

    件系統(tǒng)中。其完整語(yǔ)法定義如下: 
    public void write(File file) 
    5.getString方法 
    getString方法用于將FileItem對(duì)象中保存的主體內(nèi)容作為一個(gè)字符串返回,它有兩個(gè)重載的定義形式: 
    public java.lang.String getString() 
    public java.lang.String getString(java.lang.String encoding) 
    throws java.io.UnsupportedEncodingException 
    前者使用缺省的字符集編碼將主體內(nèi)容轉(zhuǎn)換成字符串,后者使用參數(shù)指定的字符集編碼將主體內(nèi)容轉(zhuǎn)換成字符串。如果在讀取普通表單字段元 

    素的內(nèi)容時(shí)出現(xiàn)了中文亂碼現(xiàn)象,請(qǐng)調(diào)用第二個(gè)getString方法,并為之傳遞正確的字符集編碼名稱。 
    6. getContentType方法 
    getContentType 方法用于獲得上傳文件的類型,對(duì)于圖1.3中的第三個(gè)分區(qū)所示的描述頭,getContentType方法返回的結(jié)果為字符串 

    “image/gif”,即“Content-Type”字段的值部分。如果FileItem類對(duì)象對(duì)應(yīng)的是普通表單字段,該方法將返回null。getContentType 方法 

    的完整語(yǔ)法定義如下: 
    public String getContentType() 
    7. isInMemory方法 
    isInMemory方法用來判斷FileItem類對(duì)象封裝的主體內(nèi)容是存儲(chǔ)在內(nèi)存中,還是存儲(chǔ)在臨時(shí)文件中,如果存儲(chǔ)在內(nèi)存中則返回true,否則返回 

    false。其完整語(yǔ)法定義如下: 
    public boolean isInMemory() 
    8. delete方法 
    delete方法用來清空FileItem類對(duì)象中存放的主體內(nèi)容,如果主體內(nèi)容被保存在臨時(shí)文件中,delete方法將刪除該臨時(shí)文件。盡管Apache組件 

    使用了多種方式來盡量及時(shí)清理臨時(shí)文件,但系統(tǒng)出現(xiàn)異常時(shí),仍有可能造成有的臨時(shí)文件被永久保存在了硬盤中。在有些情況下,可以調(diào)用 

    這個(gè)方法來及時(shí)刪除臨時(shí)文件。其完整語(yǔ)法定義如下: 
    public void delete() 
    1.2.4 FileUploadException類 
    在文件上傳過程中,可能發(fā)生各種各樣的異常,例如網(wǎng)絡(luò)中斷、數(shù)據(jù)丟失等等。為了對(duì)不同異常進(jìn)行合適的處理,Apache文件上傳組件還開發(fā) 

    了四個(gè)異常類,其中FileUploadException是其他異常類的父類,其他幾個(gè)類只是被間接調(diào)用的底層類,對(duì)于Apache組件調(diào)用人員來說,只需對(duì) 

    FileUploadException異常類進(jìn)行捕獲和處理即可。

    1.2.5 文件上傳編程實(shí)例 
    下面參考圖1.2中看到的示例代碼編寫一個(gè)使用Apache文件上傳組件來上傳文件的例子程序。 
    :動(dòng)手體驗(yàn):使用Apache文件上傳組件 
    (1)在<tomcat安裝目錄>\webapps\fileupload目錄中按例程1-1編寫一個(gè)名為FileUpload.html的HTML頁(yè)面,該頁(yè)面用于提供文件上傳的FORM 

    表單,表單的enctype屬性設(shè)置值為“multipart/form-data”,表單的action屬性設(shè)置為“servlet/UploadServlet”。 
    例程1-1 FileUpload.html 



    <html> 
    <head> 
    <title>upload experiment</title> 
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> 
    </head> 
    <body> 
    <h3>測(cè)試文件上傳組件的頁(yè)面</h3> 
    <form action="servlet/UploadServlet" 
    enctype="multipart/form-data" method="post"> 
    作者:<input type="text" name="author"><br> 
    來自:<input type="text" name="company"><br> 
    文件1:<input type="file" name="file1"><br> 
    文件2:<input type="file" name="file2"><br> 
    <input type="submit" value="上載"> 
    </form> 
    </body> 
    </html> 



    (2)在<tomcat的安裝目錄>\webapps\fileupload\src目錄中按例程1-2創(chuàng)建一個(gè)名為UploadServlet.java的Servlet程序,UploadServlet.java 

    調(diào)用Apache文件上傳組件來處理FORM表單提交的文件內(nèi)容和普通字段數(shù)據(jù)。 
    例程1-2 UploadServlet.java 



    import java.io.*; 
    import javax.servlet.*; 
    import javax.servlet.http.*; 
    import org.apache.commons.fileupload.*; 
    import java.util.*; 

    public class UploadServlet extends HttpServlet 

    public void doPost(HttpServletRequest request, 
    HttpServletResponse response) throws ServletException,IOException 

    response.setContentType("text/html;charset=gb2312"); 
    PrintWriter out = response.getWriter(); 

    //設(shè)置保存上傳文件的目錄 
    String uploadDir = getServletContext().getRealPath("/upload"); 
    if (uploadDir == null) 

    out.println("無(wú)法訪問存儲(chǔ)目錄!"); 
    return; 

    File fUploadDir = new File(uploadDir); 
    if(!fUploadDir.exists()) 

    if(!fUploadDir.mkdir()) 

    out.println("無(wú)法創(chuàng)建存儲(chǔ)目錄!"); 
    return; 



    if (!DiskFileUpload.isMultipartContent(request)) 

    out.println("只能處理multipart/form-data類型的數(shù)據(jù)!"); 
    return ; 


    DiskFileUpload fu = new DiskFileUpload(); 
    //最多上傳200M數(shù)據(jù) 
    fu.setSizeMax(1024 * 1024 * 200); 
    //超過1M的字段數(shù)據(jù)采用臨時(shí)文件緩存 
    fu.setSizeThreshold(1024 * 1024); 
    //采用默認(rèn)的臨時(shí)文件存儲(chǔ)位置 
    //fu.setRepositoryPath(...); 
    //設(shè)置上傳的普通字段的名稱和文件字段的文件名所采用的字符集編碼 
    fu.setHeaderEncoding("gb2312"); 

    //得到所有表單字段對(duì)象的集合 
    List fileItems = null; 
    try 

    fileItems = fu.parseRequest(request); 

    catch (FileUploadException e) 

    out.println("解析數(shù)據(jù)時(shí)出現(xiàn)如下問題:"); 
    e.printStackTrace(out); 
    return; 


    //處理每個(gè)表單字段 
    Iterator i = fileItems.iterator(); 
    while (i.hasNext()) 

    FileItem fi = (FileItem) i.next(); 
    if (fi.isFormField()) 

    String content = fi.getString("GB2312"); 
    String fieldName = fi.getFieldName(); 
    request.setAttribute(fieldName,content); 

    else 

    try 

    String pathSrc = fi.getName(); 
    /*如果用戶沒有在FORM表單的文件字段中選擇任何文件, 
    那么忽略對(duì)該字段項(xiàng)的處理*/ 
    if(pathSrc.trim().equals("")) 

    continue; 

    int start = pathSrc.lastIndexOf('\\'); 
    String fileName = pathSrc.substring(start + 1); 
    File pathDest = new File(uploadDir, fileName); 

    fi.write(pathDest); 
    String fieldName = fi.getFieldName(); 
    request.setAttribute(fieldName, fileName); 

    catch (Exception e) 

    out.println("存儲(chǔ)文件時(shí)出現(xiàn)如下問題:"); 
    e.printStackTrace(out); 
    return; 

    finally //總是立即刪除保存表單字段內(nèi)容的臨時(shí)文件 

    fi.delete(); 





    //顯示處理結(jié)果 
    out.println("用戶:" + request.getAttribute("author") + "<br>"); 
    out.println("來自:" + request.getAttribute("company") + "<br>"); 

    /*將上傳的文件名組合成"file1,file2"這種形式顯示出來,如果沒有上傳 
    *任何文件,則顯示為"無(wú)",如果只上傳了第二個(gè)文件,顯示為"file2"。*/ 
    StringBuffer filelist = new StringBuffer(); 
    String file1 = (String)request.getAttribute("file1"); 
    makeUpList(filelist,file1); 
    String file2 = (String)request.getAttribute("file2"); 
    makeUpList(filelist,file2); 
    out.println("成功上傳的文件:" + 
    (filelist.length()==0 ? "無(wú)" : filelist.toString())); 



    /** 
    *將一段字符串追加到一個(gè)結(jié)果字符串中。如果結(jié)果字符串的初始內(nèi)容不為空, 
    *在追加當(dāng)前這段字符串之前先最加一個(gè)逗號(hào)(,)。在組合sql語(yǔ)句的查詢條件時(shí), 
    *經(jīng)常要用到類似的方法,第一條件前沒有"and",而后面的條件前都需要用"and" 
    *作連詞,如果沒有選擇第一個(gè)條件,第二個(gè)條件就變成第一個(gè),依此類推。 

    *@param result 要將當(dāng)前字符串追加進(jìn)去的結(jié)果字符串 
    *@param fragment 當(dāng)前要追加的字符串 
    */ 
    private void makeUpList(StringBuffer result,String fragment) 

    if(fragment != null) 

    if(result.length() != 0) 

    result.append(","); 

    result.append(fragment); 






    在Windows資源管理器窗口中將UploadServlet.java源文件拖動(dòng)到compile.bat文件的快捷方式上進(jìn)行編譯,修改Javac編譯程序報(bào)告的錯(cuò)誤,直 

    到編譯成功通過為止。 
    (3)修改<tomcat的安裝目錄>\webapps\fileupload\WEB-INF\classes\web.xml文件,在其中注冊(cè)和映射UploadServlet的訪問路徑,如例程1-3 

    所示。 
    例程1-3 web.xml 



    <web-app> 
    <servlet> 
    <servlet-name>UploadServlet</servlet-name> 
    <servlet-class>UploadServlet</servlet-class> 
    </servlet> 

    <servlet-mapping> 
    <servlet-name>UploadServlet</servlet-name> 
    <url-pattern>/servlet/UploadServlet</url-pattern> 
    </servlet-mapping> 
    </web-app> 
    (4)重新啟動(dòng)Tomcat,并在瀏覽器地址欄中輸入如下地址: 
    http://localhost:8080/fileupload/FileUpload.html 
    填寫返回頁(yè)面中的FORM表單,如圖1.4所示,單擊“上載”按鈕后,瀏覽器返回的頁(yè)面信息如圖1.5所示。 
    圖1.4 
    圖1.5(這些圖的標(biāo)題欄中的it315改為fileupload) 
    查看<tomcat安裝目錄>\webapps\it315\upload目錄,可以看到剛才上傳的兩個(gè)文件。 
    (4)單擊瀏覽器工具欄上的“后退”按鈕回到表單填寫頁(yè)面,只在第二個(gè)文件字段中選擇一個(gè)文件,單擊“上載”按鈕,瀏覽器返回的顯示結(jié)果 

    如圖1.6所示。 
    圖1.6 
    M腳下留心: 
    上面編寫的Servlet程序?qū)⑸蟼鞯奈募4嬖诹水?dāng)前WEB應(yīng)用程序下面的upload目錄中,這個(gè)目錄是客戶端瀏覽器可以訪問到的目錄。如果用戶 

    通過瀏覽器上傳了一個(gè)名稱為test.jsp的文件,那么用戶接著就可以在瀏覽器中訪問這個(gè)test.jsp文件了,對(duì)于本地瀏覽器來說,這個(gè)jsp文件 

    的訪問URL地址如下所示: 
    http://localhost:8080/fileupload/upload/test.jsp 
    對(duì)于遠(yuǎn)程客戶端瀏覽器而言,只需要將上面的url地址中的localhost改寫為Tomcat服務(wù)器的主機(jī)名或IP地址即可。用戶可以通過上面的Servlet 

    程序來上傳自己編寫的jsp文件,然后又可以通過瀏覽器來訪問這個(gè)jsp文件,如果用戶在jsp文件中編寫一些有害的程序代碼,例如,查看服務(wù) 

    器上的所有目錄結(jié)構(gòu),調(diào)用服務(wù)器上的操作系統(tǒng)進(jìn)程等等,這將是一個(gè)非常致命的安全漏洞和隱患,這臺(tái)服務(wù)器對(duì)外就沒有任何安全性可言了 

    。 

    1.3 Apache文件上傳組件的源碼賞析 
    經(jīng)常閱讀一些知名的開源項(xiàng)目的源代碼,可以幫助我們開闊眼界和快速提高編程能力。Apache文件上傳組件是Apache組織開發(fā)的一個(gè)開源項(xiàng)目 

    ,從網(wǎng)址http://jakarta.apache.org/commons/fileupload可以下載到Apache組件的源程序包,在本書的附帶帶光盤中也提供了該組件的源程 

    序包,文件名為commons-fileupload-1.0-src.zip。該組件的設(shè)計(jì)思想和程序編碼細(xì)節(jié)包含有許多值得借鑒的技巧,為了便于有興趣的讀者學(xué) 

    習(xí)和研究該組件的源碼,本節(jié)將分析Apache文件上傳組件的源代碼實(shí)現(xiàn)。對(duì)于只想了解如何使用Apache文件上傳組件來上傳文件的讀者來說, 

    不必學(xué)習(xí)本節(jié)的內(nèi)容。在學(xué)習(xí)本節(jié)內(nèi)容之前,讀者需要仔細(xì)學(xué)習(xí)了筆者編著的《深入體驗(yàn)java Web開發(fā)內(nèi)幕——核心基礎(chǔ)》一書中的第6.7.2節(jié) 

    中講解的“分析文件上傳的請(qǐng)求消息結(jié)構(gòu)”的知識(shí)。 
    1.3.1 Apache文件上傳組的類工作關(guān)系 
    Apache文件上傳組件總共由兩個(gè)接口,十二個(gè)類組成。在Apache文件上傳組件的十二個(gè)類中,有兩個(gè)抽象類,四個(gè)的異常類,六個(gè)主要類,其 

    中FileUpLoad類用暫時(shí)沒有應(yīng)用,是為了以后擴(kuò)展而保留的。Apache文件上傳組件中的各個(gè)類的關(guān)系如圖1.7所示,圖中省略了異常類。 
    圖 1.7 

    DiskFileUpload類是文件上傳組件的核心類,它是一個(gè)總的控制類,首先由Apache文件上傳組件的使用者直接調(diào)用DiskFileUpload類的方法, 

    DiskFileUpload類再調(diào)用和協(xié)調(diào)更底層的類來完成具體的功能。解析類MultipartStream和工廠類DefaultFileItemFactory就是DiskFileUpload 

    類調(diào)用的兩個(gè)的底層類。MultipartStream類用于對(duì)請(qǐng)求消息中的實(shí)體數(shù)據(jù)進(jìn)行具體解析,DefaultFileItemFactory類對(duì)MultipartStream類解 

    析出來的數(shù)據(jù)進(jìn)行封裝,它將每個(gè)表單字段數(shù)據(jù)封裝成一個(gè)個(gè)的FileItem類對(duì)象,用戶通過FileItem類對(duì)象來獲得相關(guān)表單字段的數(shù)據(jù)。 
    DefaultFileItem是FileItem接口的實(shí)現(xiàn)類,實(shí)現(xiàn)了FileItem接口中定義的功能,用戶只需關(guān)心FileItem接口,通過FileItem接口來使用 

    DefaultFileItem類實(shí)現(xiàn)的功能。DefaultFileItem類使用了兩個(gè)成員變量來分別存儲(chǔ)表單字段數(shù)據(jù)的描述頭和主體內(nèi)容,其中保存主體內(nèi)容的 

    變量類型為DeferredFileOutputStream類。DeferredFileOutputStream類是一個(gè)輸出流類型,在開始時(shí),DeferredFileOutputStream類內(nèi)部使 

    用一個(gè)ByteArrayOutputStream類對(duì)象來存儲(chǔ)數(shù)據(jù),當(dāng)寫入它里面的主體內(nèi)容的大小大于DiskFileUpload.setSizeThreshold方法設(shè)置的臨界值 

    時(shí),DeferredFileOutputStream類內(nèi)部創(chuàng)建一個(gè)文件輸出流對(duì)象來存儲(chǔ)數(shù)據(jù),并將前面寫入到ByteArrayOutputStream類對(duì)象中的數(shù)據(jù)轉(zhuǎn)移到文 

    件輸出流對(duì)象中。這個(gè)文件輸出流對(duì)象關(guān)聯(lián)的文件是一個(gè)臨時(shí)文件,它的保存路徑由DiskFileUpload.setRepositoryPath方法指定。 
    Apache文件上傳組件的處理流程如圖1.8所示。 
    圖1.8 
    圖1.8中的每一步驟的詳細(xì)解釋如下: 
    (1)Web容器接收用戶的HTTP請(qǐng)求消息,創(chuàng)建request請(qǐng)求對(duì)象。 
    (2)調(diào)用DiskFileUpload類對(duì)象的parseRequest方法對(duì)request請(qǐng)求對(duì)象進(jìn)行解析。該方法首先檢查request請(qǐng)求對(duì)象中的數(shù)據(jù)內(nèi)容是否是 

    “multipart/form-data”類型,如果是,該方法則創(chuàng)建MultipartStream類對(duì)象對(duì)request請(qǐng)求對(duì)象中的請(qǐng)求體 進(jìn)行解析。 
    (3)MultipartStream類對(duì)象對(duì)request請(qǐng)求體進(jìn)行解析,并返回解析出的各個(gè)表單字段元素對(duì)應(yīng)的內(nèi)容。 
    (4)DiskFileUpload類對(duì)象的parseRequest方法接著創(chuàng)建DefaultFileItemFactory類對(duì)象,用來將MultipartStream類對(duì)象解析出的每個(gè)表單 

    字段元素的數(shù)據(jù)封裝成FileItem類對(duì)象。 
    (5)DefaultFileItemFactory工廠類對(duì)象把MultipartStream類對(duì)象解析出的各個(gè)表單字段元素的數(shù)據(jù)封裝成若干DefaultFileItem類對(duì)象,然 

    后加入到一個(gè)List類型的集合對(duì)象中,parseRequest方法返回該List集合對(duì)象。 
    實(shí)際上,步驟(3)和步驟(5)是交替同步進(jìn)行的,即在MultipartStream類對(duì)象解析每個(gè)表單字段元素時(shí),都會(huì)調(diào)用DefaultFileItemFactory 

    工廠類把該表單字段元素封裝成對(duì)應(yīng)的FileItem類對(duì)象。 

    1.3.2 Apache文件上傳組件的核心編程問題 
    WEB服務(wù)器端程序接收到“multipart/form-data”類型的HTTP請(qǐng)求消息后,其核心和基本的編程工作就是讀取請(qǐng)求消息中的實(shí)體內(nèi)容,然后解 

    析出每個(gè)分區(qū)的數(shù)據(jù),接著再?gòu)拿總€(gè)分區(qū)中解析出描述頭和主體內(nèi)容部分。 
    在讀取HTTP請(qǐng)求消息中的實(shí)體內(nèi)容時(shí),只能調(diào)用HttpServletRequest.getInputStream方法返回的字節(jié)輸入流,而不能調(diào)用 

    HttpServletRequest.getReader方法返回的字符輸入流,因?yàn)椴还苌蟼鞯奈募愋褪俏谋镜?、還是其他各種格式的二進(jìn)制內(nèi)容,WEB服務(wù)器程序 

    要做的工作就是將屬于文件內(nèi)容的那部分?jǐn)?shù)據(jù)原封不動(dòng)地提取出來,然后原封不動(dòng)地存儲(chǔ)到本地文件系統(tǒng)中。如果使用 

    HttpServletRequest.getReader方法返回的字符輸入流對(duì)象來讀取HTTP請(qǐng)求消息中的實(shí)體內(nèi)容,它將HTTP請(qǐng)求消息中的字節(jié)數(shù)據(jù)轉(zhuǎn)換成字符后 

    再返回,這主要是為了方便要以文本方式來處理本來就全是文本內(nèi)容的請(qǐng)求消息的應(yīng)用,但本程序要求的是“原封不動(dòng)”,顯然不能使用 

    HttpServletRequest.getReader方法返回的字符輸入流對(duì)象來進(jìn)行讀取。 
    另外,不能期望用一個(gè)很大的字節(jié)數(shù)組就可以裝進(jìn)HTTP請(qǐng)求消息中的所有實(shí)體內(nèi)容,因?yàn)槌绦蛑卸x的字節(jié)數(shù)組大小總是有限制的,但應(yīng)該允 

    許客戶端上傳超過這個(gè)字節(jié)數(shù)組大小的實(shí)體內(nèi)容。所以,只能創(chuàng)建一個(gè)一般大小的字節(jié)數(shù)組緩沖區(qū)來逐段讀取請(qǐng)求消息中的實(shí)體內(nèi)容,讀取一 

    段就處理一段,處理完上一段以后,再讀取下一段,如此循環(huán),直到處理完所有的實(shí)體內(nèi)容,如圖1.9所示。 
    圖 1.9 
    在圖1.9中,buffer即為用來逐段讀取請(qǐng)求消息中的實(shí)體內(nèi)容的字節(jié)數(shù)組緩沖區(qū)。因?yàn)樽x取到緩沖區(qū)中的數(shù)據(jù)處理完后就會(huì)被拋棄,確切地說, 

    是被下一段數(shù)據(jù)覆蓋,所以,解析和封裝過程必須同步進(jìn)行,程序一旦識(shí)別出圖1.3中的一個(gè)分區(qū)的開始后,就要開始將它封裝到一個(gè)FileItem 

    對(duì)象中。 
    程序要識(shí)別出圖1.3中的每一個(gè)分區(qū),需要在圖1.9所示的字節(jié)數(shù)組緩沖區(qū)buffer中尋找分區(qū)的字段分隔界線,當(dāng)找到一個(gè)字段分隔界線后,就 

    等于找到了一個(gè)分區(qū)的開始。筆者在《深入體驗(yàn)java Web開發(fā)內(nèi)幕——核心基礎(chǔ)》一書中的第6.7.2節(jié)中已經(jīng)講過,上傳文件的請(qǐng)求消息的 

    Content-Type頭字段中包含有用作字段分隔界線的字符序列,如下所示: 
    content-type : multipart/form-data; boundary=---------------------------7d51383203e8 
    顯然,我們可以通過調(diào)用HttpServletRequest.getHeader方法讀取Content-Type頭字段的內(nèi)容,從中分離出分隔界線的字符序列,然后在字節(jié) 

    數(shù)組緩沖區(qū)buffer中尋找分區(qū)的字段分隔界線。content-type頭字段的boundary參數(shù)中指定的字段分隔界線是瀏覽器隨機(jī)產(chǎn)生的,瀏覽器保證 

    它不會(huì)與用戶上傳的所有數(shù)據(jù)中的任何部分出現(xiàn)相同。在這里有一點(diǎn)需要注意,圖1.3中的實(shí)體內(nèi)容內(nèi)部的字段分隔界線與content-type頭中指 

    定的字段分隔界線有一點(diǎn)細(xì)微的差別,前者是在后者前面增加了兩個(gè)減號(hào)(-)字符而形成的,這倒不是什么編程難點(diǎn)。真正的編程難點(diǎn)在于在 

    字節(jié)數(shù)組緩沖區(qū)buffer中尋找分隔界線時(shí),可能會(huì)遇到字節(jié)數(shù)組緩沖區(qū)buffer中只裝入了分隔界線字符序列的部分內(nèi)容的情況,如圖1.10所示 

    。 
    圖1.10 

    要解決這個(gè)問題的方法之一就是在查找字段分隔界線時(shí),如果發(fā)現(xiàn)字節(jié)數(shù)組緩沖區(qū)buffer中只裝入了分隔界線字符序列的部分內(nèi)容,那么就將 

    這一部分內(nèi)容留給字節(jié)數(shù)組緩沖區(qū)buffer的下一次讀取,如圖1.11所示。 
    圖1.11 
    這種方式讓字節(jié)數(shù)組緩沖區(qū)buffer下一次讀取的內(nèi)容不是緊接著上一次讀取內(nèi)容的后面,而是重疊上一次讀取的一部分內(nèi)容,即從上一次讀取 

    內(nèi)容中的分隔界線字符序列的第一個(gè)字節(jié)處開始讀取。這種方式在實(shí)際的編程處理上存在著相當(dāng)大的難度,程序首先必須確定字節(jié)數(shù)組緩沖區(qū) 

    buffer上一次讀取的數(shù)據(jù)的后一部分內(nèi)容正好是分隔界線字符序列的前面一部分內(nèi)容,而這一部分內(nèi)容的長(zhǎng)度是不確定的,可能只是分隔界線 

    字符序列的第一個(gè)字符,也可能是分隔界線字符序列的前面n-1個(gè)字符,其中n為分隔界線字符序列的整個(gè)長(zhǎng)度。另外,即使確定字節(jié)數(shù)組緩沖 

    區(qū)buffer上一次讀取的數(shù)據(jù)的后一部分內(nèi)容正好是分隔界線字符序列的前面一部分內(nèi)容,但它們?cè)谡麄€(gè)輸入字節(jié)流中的后續(xù)內(nèi)容不一定就整個(gè) 

    分隔界線字符序列的后一部分內(nèi)容,出現(xiàn)這種情況的可能性是完全存在,程序必須進(jìn)行全面和嚴(yán)謹(jǐn)?shù)目紤]。 
    Apache文件上傳組件的解決方法比較巧妙,它在查找字段分隔界線時(shí),如果搜索到最后第n個(gè)字符時(shí),n為分隔界線字符序列的長(zhǎng)度,發(fā)現(xiàn)最后n 

    個(gè)字符不能與分隔界線字符序列匹配,則將最后的n-1個(gè)字符留給字節(jié)數(shù)組緩沖區(qū)buffer的下一次讀取,程序再對(duì)buffer的下一次讀取的整個(gè)內(nèi) 

    容從頭開始查找字段分隔界線,如圖1.12所示。 
    圖1.12 

    Apache文件上傳組件查找字段分隔界線的具體方法,讀者可以請(qǐng)參見MultipartStream類的findSeparator()方法中的源代碼。 
    當(dāng)找到一個(gè)分區(qū)的開始位置后,程序還需要分辨出分區(qū)中的描述頭和主體內(nèi)容,并對(duì)這兩部分內(nèi)容分開存儲(chǔ)。如何分辨出一個(gè)分區(qū)的描述頭和 

    主體部分呢?從圖1.3中可以看到,每個(gè)分區(qū)中的描述頭和主體內(nèi)容之間有一空行,再加上描述頭后面的換行,這就說明描述頭和主體部分之間 

    是使用“\n”、“\r”、“\n”、“\r”這四個(gè)連續(xù)的字節(jié)內(nèi)容進(jìn)行分隔。因此,程序需要把“\n”、“\r”、“\n”、“\r”這四個(gè)連續(xù)的 

    字節(jié)內(nèi)容作為描述頭和主體部分之間的分隔界線,并在字節(jié)數(shù)組緩沖區(qū)buffer中尋找這個(gè)特殊的分隔界線來識(shí)別描述頭和主體部分。 
    當(dāng)識(shí)別出一個(gè)分區(qū)中的描述頭和主體部分后,程序需要解決的下一個(gè)問題就是如何將描述頭和主體部分的數(shù)據(jù)保存到FileItem對(duì)象中,以便用 

    戶以后可以調(diào)用FileItem類的方法來獲得這些數(shù)據(jù)。主體部分的數(shù)據(jù)需要能夠根據(jù)用戶上傳的文件大小有伸縮性地進(jìn)行存儲(chǔ),因此,程序要求 

    編寫一個(gè)特殊的類來封裝主體部分的數(shù)據(jù),對(duì)于這個(gè)問題的具體實(shí)現(xiàn)細(xì)節(jié),讀者可參見1.2.4小節(jié)中講解的DeferredFileOutputStream類來了解 

    1.3.3 MultipartStream類 
    MultipartStream類用來對(duì)上傳的請(qǐng)求輸入流進(jìn)行解析,它是整個(gè)Apache上傳組件中最復(fù)雜的類。 
    1.設(shè)計(jì)思想 
    MultipartStream類中定義了一個(gè)byte[]類型的boundary成員變量,這個(gè)成員變量用于保存圖1.3中的各個(gè)數(shù)據(jù)分區(qū)之間的分隔界線,每個(gè)分區(qū) 

    分別代表一個(gè)表單字段的信息。圖1.3中的每個(gè)分區(qū)又可以分為描述頭部分和主體部分,MultipartStream類中定義了一個(gè)readHeaders()方法來 

    讀取描述頭部分的內(nèi)容,MultipartStream類中定義了一個(gè)readBodyData(OutputStream output)方法來讀取主體部分的內(nèi)容,并將這些內(nèi)容寫 

    入到一個(gè)作為參數(shù)傳入進(jìn)來的輸出流對(duì)象中。readBodyData方法接收的參數(shù)output對(duì)象在應(yīng)用中的實(shí)際類型是DeferredFileOutputStream,這 

    個(gè)對(duì)象又是保存在DefaultFileItem類對(duì)象中的一個(gè)成員變量,這樣,readBodyData方法就可以將一個(gè)分區(qū)的主體部分的數(shù)據(jù)寫入到 

    DefaultFileItem類對(duì)象中。 
    因?yàn)閳D1.3中的實(shí)體內(nèi)容內(nèi)部的字段分隔界線是在content-type頭中指定的字段分隔界線前面增加了兩個(gè)減號(hào)(-)字符而形成的,而每個(gè)字段 

    分隔界線與它前面內(nèi)容之間還進(jìn)行了換行,這個(gè)換行并不屬于表單字段元素的內(nèi)容。所以,MultipartStream類中的成員變量boundary中存儲(chǔ)的 

    字節(jié)數(shù)組并不是直接從content-type頭的boundary參數(shù)中獲得的字符序列,而是在boundary參數(shù)中指定的字符序列前面增加了四個(gè)字節(jié),依次 

    是‘\n’、‘\r’、‘-’和‘-’。MultipartStream類中定義了一個(gè)readBoundary()方法來讀取和識(shí)別各個(gè)字段之間分隔界線,有一點(diǎn)特殊的 

    是,圖1.3中的第一個(gè)分隔界線前面沒有回車換行符,它是無(wú)法與成員變量boundary中的數(shù)據(jù)相匹配的,所以無(wú)法調(diào)用readBoundary()方法進(jìn)行 

    讀取,而是需要進(jìn)行特殊處理,其后的每個(gè)分隔界線都與boundary中的數(shù)據(jù)相匹配,可以直接調(diào)用readBoundary()方法進(jìn)行讀取。在本章的后 

    面部分,如果沒有特別說明,所說的分隔界線都是指成員變量boundary中的數(shù)據(jù)內(nèi)容。 
    RFC 1867格式規(guī)范規(guī)定了描述頭和主體部分必須用一個(gè)空行進(jìn)行分隔,如圖1.3所示,也就是描述頭和主體部分使用“\n”、“\r”、“\n”、 

    “\r”這四個(gè)連續(xù)的字節(jié)內(nèi)容進(jìn)行分隔。MultipartStream類的設(shè)計(jì)者為了簡(jiǎn)化編程,在readHeaders()方法中將“\n”、“\r”、“\n”、 

    “\r”這四個(gè)連續(xù)的字節(jié)內(nèi)容連同描述頭一起進(jìn)行讀取。readHeaders()方法在讀取數(shù)據(jù)的過程中,當(dāng)它發(fā)現(xiàn)第一個(gè)‘\n’、‘\r’、‘\n’、 

    ‘\r’ 連續(xù)的字節(jié)序列時(shí)就會(huì)返回,即使主體部分正好也包含了“\n”、“\r”、“\n”、“\r”這四個(gè)連續(xù)的字節(jié)內(nèi)容,但是,它們只會(huì)被 

    隨后調(diào)用的readBodyData方法作為主體內(nèi)容讀取,永遠(yuǎn)不會(huì)被readHeaders()方法讀取到,所以,它們不會(huì)與作為描述頭和主體部分的分隔字符 

    序列發(fā)生沖突。 
    由于readHeaders()方法讀取了一個(gè)分區(qū)中的主體部分前面的所有內(nèi)容(包括它前面的換行),而它與下一個(gè)分區(qū)之間的分隔界線前面的換行又 

    包含在了成員變量boundary中,這個(gè)換行將被readBoundary()方法讀取,所以,夾在readheaders()方法讀取的內(nèi)容和readBoundary()方法讀取 

    的內(nèi)容之間的數(shù)據(jù)全部都屬于表單字段元素的內(nèi)容了,因此,讀取分區(qū)中的主體部分的readBodyData(OutputStream output)方法不需要進(jìn)行特 

    別的處理,它直接將讀取的數(shù)據(jù)寫入到DefaultFileItem類對(duì)象中封裝的DeferredFileOutputStream屬性對(duì)象中即可。 
    2. 構(gòu)造方法 
    MultipartStream類中的一個(gè)主要的構(gòu)造方法的語(yǔ)法定義如下: 
    public (InputStream input, byte[] boundary, int bufSize) 
    其中,參數(shù)input是指從HttpServetRequest請(qǐng)求對(duì)象中獲得的字節(jié)輸入流對(duì)象,參數(shù)boundary是從請(qǐng)求消息頭中獲得的未經(jīng)處理的分隔界線, 

    bufSize指定圖1.10中的buffer緩沖區(qū)字節(jié)數(shù)組的長(zhǎng)度,默認(rèn)值是4096個(gè)字節(jié)。這個(gè)構(gòu)造方法的源代碼如下: 
    public MultipartStream(InputStream input, byte[] boundary, int bufSize) 

    // 初始化成員變量 
    this.input = input; 
    this.bufSize = bufSize; 
    this.buffer = new byte[bufSize]; 
    this.boundary = new byte[boundary.length + 4]; 
    this.boundaryLength = boundary.length + 4; 
    //buffer緩沖區(qū)中保留給下次讀取的最大字節(jié)個(gè)數(shù) 
    this.keepRegion = boundary.length + 3; 
    this.boundary[0] = 0x0D; //‘\n’的16進(jìn)制形式 
    this.boundary[1] = 0x0A; //‘\r’的16進(jìn)制形式 
    this.boundary[2] = 0x2D; //‘-’的16進(jìn)制形式 
    this.boundary[3] = 0x2D; 
    //在成員變量boundary中生成最終的分隔界線 
    System.arraycopy (boundary, 0, this.boundary, 4, boundary.length); 

    head = 0; // 成員變量,表示正在處理的這個(gè)字節(jié)在buffer中的位置指針 
    tail = 0; // 成員變量,表示實(shí)際讀入到buffer中的字節(jié)個(gè)數(shù) 


    3. readByte方法 
    MultipartStream類中的readByte()方法從字節(jié)數(shù)組緩沖區(qū)buffer中讀一個(gè)字節(jié),當(dāng)buffer緩沖區(qū)中沒有更多的數(shù)據(jù)可讀時(shí),該方法會(huì)自動(dòng)從輸 

    入流中讀取一批新的字節(jié)數(shù)據(jù)來重新填充buffer緩沖區(qū)。readByte()方法的源代碼如下: 
    public byte readByte () throws IOException 

    // 判斷是否已經(jīng)讀完了buffer緩沖區(qū)中的所有數(shù)據(jù) 
    if (head == tail) 

    head = 0; 
    //讀入新的數(shù)據(jù)內(nèi)容來填充buffer緩沖區(qū) 
    tail = input.read(buffer, head, bufSize); 
    if (tail == -1) 

    throw new IOException("No more data is available "); 


    return buffer[head++];// 返回當(dāng)前字節(jié),head++ 

    其中,head變量是MultipartStream類中定義的一個(gè)int類型的成員變量,它用于表示正在讀取的字節(jié)在buffer數(shù)組緩沖區(qū)中的位置;tail變量 

    也是MultipartStream類中定義的一個(gè)int類型的成員變量,它用于表示當(dāng)前buffer數(shù)組緩沖區(qū)裝入的實(shí)際字節(jié)內(nèi)容的長(zhǎng)度。在MultipartStream 

    類中主要是通過控制成員變量head的值來控制對(duì)buffer緩沖區(qū)中的數(shù)據(jù)的讀取和直接跳過某段數(shù)據(jù),通過比較head與tail變量的值了解是否需 

    要向buffer緩沖區(qū)中裝入新的數(shù)據(jù)內(nèi)容。當(dāng)每次向buffer緩沖區(qū)中裝入新的數(shù)據(jù)內(nèi)容后,都應(yīng)該調(diào)整成員變量head和tail的值。 

    4. arrayequals靜態(tài)方法 
    MultipartStream類中定義了一個(gè)的arrayequals靜態(tài)方法,用于比較兩個(gè)字節(jié)數(shù)組中的前面一部分內(nèi)容是否相等,相等返回true,否則返回 

    false。arrayequals方法的源代碼如下,參數(shù)count指定了對(duì)字節(jié)數(shù)組中的前面幾個(gè)字節(jié)內(nèi)容進(jìn)行比較: 
    public static boolean arrayequals(byte[] a, byte[] b,int count) 

    for (int i = 0; i < count; i++) 

    if (a[i] != b[i]) 

    return false; 


    return true; 


    5. findByte方法 
    MultipartStream類中的findByte()方法從字節(jié)數(shù)組緩沖區(qū)buffer中的某個(gè)位置開始搜索一個(gè)特定的字節(jié)數(shù)據(jù),如果找到了,則返回該字節(jié)在 

    buffer緩沖區(qū)中的位置,不再繼續(xù)搜索,如果沒有找到,則返回-1。findByte方法的源代碼如下,參數(shù)pos制定了不搜索的起始位置值,value 

    是要搜索的字節(jié)數(shù)據(jù): 
    protected int findByte(byte value,int pos) 

    for (int i = pos; i < tail; i++) 

    if (buffer[i] == value) 

    return i; // 找到該值,findByte方法返回 


    return - 1; 

    如果程序需要在buffer緩沖區(qū)中多次搜索某個(gè)特定的字節(jié)數(shù)據(jù),那就可以循環(huán)調(diào)用findByte方法,只是在每次調(diào)用findByte方法時(shí),必須不斷 

    地改變參數(shù)pos的值,讓pos的值等于上次調(diào)用findByte的返回值,直到findByte方法返回-1時(shí)為止,如圖1.13所示。 
    圖1.13 
    6. findSeparator方法 
    MultipartStream類中的findSeparator方法用于從字節(jié)數(shù)組緩沖區(qū)buffer中查找成員變量boundary中定義的分隔界線,并返回分隔界線的第一 

    個(gè)字節(jié)在buffer緩沖區(qū)中的位置,如果在buffer緩沖區(qū)中沒有找到分隔界線,則返回-1。 
    findSeparator方法內(nèi)部首先調(diào)用findByte方法在buffer緩沖區(qū)中搜索分隔界線boundary的第一個(gè)字節(jié)內(nèi)容,如果沒有找到,則說明buffer緩沖 

    區(qū)中沒有包含分隔界線;如果findByte方法在buffer緩沖區(qū)中找到了分隔界線boundary的第一個(gè)字節(jié)內(nèi)容,findSeparator方法內(nèi)部接著確定該 

    字節(jié)及隨后的字節(jié)序列是否確實(shí)是分隔界線。findSeparator方法內(nèi)部循環(huán)調(diào)用findByte方法,直到找到分隔界線或者findByte方法已經(jīng)查找到 

    了buffer緩沖區(qū)中的最后boundaryLength -1個(gè)字節(jié)。findSeparator方法內(nèi)部為什么調(diào)用findByte方法查找到buffer緩沖區(qū)中的最后 

    boundaryLength-1個(gè)字節(jié)時(shí)就停止查找呢?這是為了解決如圖1.10所示的buffer緩沖區(qū)中裝入了分隔界線的部分內(nèi)容的特殊情況,所以在 

    findSeparator()方法中不要搜索buffer緩沖區(qū)中的最后的boundaryLength -1個(gè)字節(jié),而是把buffer緩沖區(qū)中的最后這boundaryLength -1個(gè)字 

    節(jié)作為保留區(qū),在下次讀取buffer緩沖區(qū)時(shí)將這些保留的字節(jié)數(shù)據(jù)重新填充到buffer緩沖區(qū)的開始部分。findSeparator方法的源代碼如下: 
    protected int findSeparator() 

    int first; 
    int match = 0; 
    int maxpos = tail - boundaryLength;//在buffer中搜索的最大位置 

    for (first = head;(first <= maxpos) && (match != boundaryLength); 
    first++) 

    //在buffer緩沖區(qū)中尋找boundary的第一個(gè)字節(jié) 
    first = findByte(boundary[0], first); 
    /*buffer中找不到boundary[0]或者boundary[0]位于保留區(qū)中, 
    則可以判斷buffer中不存在分隔界線*/ 
    if (first == -1 || (first > maxpos)) 

    return -1; 

    //確定隨后的字節(jié)序列是否確實(shí)是分隔界線的其他字節(jié)內(nèi)容 
    for (match = 1; match < boundaryLength; match++) 

    if (buffer[first + match] != boundary[match]) 

    break; 



    // 當(dāng)前buffer中找到boundary,返回第一個(gè)字節(jié)所在位置值 
    if (match == boundaryLength) 

    return first - 1; 

    return -1; // 當(dāng)前buffer中沒找到boundary,返回-1 

    圖1.14中描述了findSeparator方法內(nèi)部定義的各個(gè)變量的示意圖。 
    圖1.14 
    findSeparator方法內(nèi)部的代碼主要包括如下三個(gè)步驟: 
    (1)循環(huán)調(diào)用findByte(boundary[0], first)找到buffer緩沖區(qū)中的與boundary[0]相同的字節(jié)的位置,并將位置記錄在first變量中。 
    (2)比較buffer緩沖區(qū)中的first后的boundaryLength-1個(gè)字節(jié)序列是否與boundary中的其他字節(jié)序列相同。如果不同,說明這個(gè)first變量指 

    向的字節(jié)不是分隔界線的開始字節(jié),跳出內(nèi)循環(huán),將first變量加1后繼續(xù)外循環(huán)調(diào)用findByte方法;如果相同,說明在當(dāng)前緩沖區(qū)buffer中找 

    到了分隔界線,內(nèi)循環(huán)正常結(jié)束,此時(shí)match變量的值為boundaryLength,接著執(zhí)行外循環(huán)將first變量加1,然后執(zhí)行外循環(huán)的條件判斷,由于 

    match != boundaryLength條件不成立,外循環(huán)也隨之結(jié)束。 
    (3)判斷match是否等于boundaryLength,如果等于則說明找到了分隔界線,此時(shí)返回成員變量boundary的第一個(gè)字節(jié)在緩沖區(qū)buffer中位置 

    ,由于第(2)中將first加1了,所以這里的返回值應(yīng)該是first-1;如果不等,說明當(dāng)前緩沖區(qū)huffer中沒有分隔界線,返回-1。 

    7. readHeaders方法 
    MultipartStream類中的readHeaders方法用于讀取一個(gè)分區(qū)的描述頭部分,并根據(jù)DiskFileUpload類的setHeaderEncoding方法設(shè)定的字符集編 

    碼將描述頭部分轉(zhuǎn)換成一個(gè)字符串返回。 
    在調(diào)用readHeaders方法之前時(shí),程序已經(jīng)調(diào)用了findSeparator方法找到了分隔界線和讀取了分隔界線前面的內(nèi)容,此時(shí)MultipartStream類中 

    的成員變量head指向了buffer緩沖區(qū)中的分隔界線boundary的第一個(gè)字節(jié),程序接著應(yīng)調(diào)用readBoundary方法跳過分隔界線及其隨后的回車換 

    行兩個(gè)字節(jié),以保證在調(diào)用readHeaders方法時(shí),成員變量head已經(jīng)指向了分區(qū)的描述頭的第一個(gè)字節(jié)。在readHeaders方法內(nèi)部,直接循環(huán)調(diào) 

    用readByte方法讀取字節(jié)數(shù)據(jù),并把讀到的數(shù)據(jù)存儲(chǔ)在一個(gè)字節(jié)數(shù)組輸出流中,直到讀取到了連續(xù)的兩次回車換行字符,就認(rèn)為已經(jīng)讀取完了 

    描述頭的全部?jī)?nèi)容,此時(shí)成員變量head將指向分區(qū)中的主體內(nèi)容的第一個(gè)字節(jié)。readHeaders()方法的源代碼如下: 
    public String readHeaders()throws MalformedStreamException 

    int i = 0; 
    //從下面的代碼看來,這里定義成一個(gè)byte即可,不用定義成byte數(shù)組 
    byte b[] = new byte[1]; 
    //用于臨時(shí)保存描述頭信息的字節(jié)數(shù)組輸出流 
    ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
    //對(duì)描述頭部分的數(shù)據(jù)內(nèi)容過大進(jìn)行限制處理 
    int sizeMax = HEADER_PART_SIZE_MAX; 
    int size = 0; 
    while (i < 4) 

    try 

    b[0] = readByte(); } 
    catch (IOException e) 

    throw new MalformedStreamException("Stream ended unexpectedly " ); 

    size++; 
    //靜態(tài)常量HEADER_SEPARATOR的值為:{0x0D, 0x0A, 0x0D, 0x0A} 
    if (b[0] == HEADER_SEPARATOR[i]) 

    i++; 

    else 

    i = 0; 

    if (size <= sizeMax) 

    baos.write(b[0]); // 將當(dāng)前字節(jié)存入緩沖流 


    String headers = null; // 找到HEADER_SEPARATOR后,獲取描述頭 
    if (headerEncoding != null) 

    try 

    headers = baos.toString(headerEncoding); 

    catch (UnsupportedEncodingException e) 

    headers = baos.toString(); 


    else 

    headers = baos.toString(); 

    return headers; 

    readHeaders方法循環(huán)調(diào)用readByte()方法逐個(gè)讀取buffer緩沖區(qū)中的字節(jié),并將讀取的字節(jié)與HEADER_SEPARATOR ={‘\n’,‘\r’,‘\n’ 

    ,‘\r’}的第一個(gè)字節(jié)進(jìn)行比較,如果這個(gè)字節(jié)等于HEADER_SEPARATOR的首字節(jié)‘\n’,則循環(huán)控制因子i加1,這樣,下次調(diào)用readByte()方 

    法讀取的字節(jié)將與HEADER_SEPARATOR中的第二字節(jié)比較,如果相等,則依照這種方式比較后面的字節(jié)內(nèi)容,如果連續(xù)讀取到了 

    HEADER_SEPARATOR字節(jié)序列,則循環(huán)語(yǔ)句結(jié)束。readHeaders方法將讀取到的每個(gè)正常字節(jié)寫入到了一個(gè)字節(jié)數(shù)組輸出流中,其中也包括作為描 

    述頭與主體內(nèi)容之間的分隔序列HEADER_SEPARATOR中的字節(jié)數(shù)據(jù)。由于readByte()方法會(huì)自動(dòng)移動(dòng)head變量的值和自動(dòng)向緩沖區(qū)buffer中載入 

    數(shù)據(jù),所以,readHeaders方法執(zhí)行完以后,成員變量head指向分區(qū)主體部分的首字節(jié)。readHeaders方法最后將把存入字節(jié)數(shù)組輸出流中的字 

    節(jié)數(shù)據(jù)按指定字符集編碼轉(zhuǎn)換成字符串并返回,該字符串就是描述頭字符串。 

    8. readBodyData方法 
    MultipartStream類中的readBodyData方法用于把主體部分的數(shù)據(jù)寫入到一個(gè)輸出流對(duì)象中,并返回寫入到輸出流中的字節(jié)總數(shù)。當(dāng)調(diào)用 

    readBodyData方法前,成員變量head已經(jīng)指向了分區(qū)的主體部分的首字節(jié),readBodyData方法調(diào)用完成后,成員變量head指向分區(qū)分隔界線的 

    首字節(jié)。readBodyData方法中需要調(diào)用findSeparator方法找出下一個(gè)分區(qū)分隔界線的首字節(jié)位置,才能知道這次讀取的分區(qū)主體內(nèi)容的結(jié)束位 

    置。從分區(qū)主體部分的首字節(jié)開始,直到在findSeparator方法找到的下一個(gè)分區(qū)分隔界線前的所有數(shù)據(jù)都是這個(gè)分區(qū)的主體部分的數(shù)據(jù), 

    readBodyData方法需要把這些數(shù)據(jù)都寫到輸出流output對(duì)象中。如果findSeparator方法在buffer緩沖區(qū)中沒有找到分區(qū)分隔界線, 

    readBodyData方法還必須向buffer緩沖區(qū)中裝入新的數(shù)據(jù)內(nèi)容后繼續(xù)調(diào)用findSeparator方法進(jìn)行處理。在向buffer緩沖區(qū)中裝入新的數(shù)據(jù)內(nèi)容 

    時(shí),必須先將上次保留在buffer緩沖區(qū)中的內(nèi)容轉(zhuǎn)移進(jìn)新buffer緩沖區(qū)的開始處。readBodyData方法的源代碼如下,傳遞給readBodyData方法 

    的參數(shù)實(shí)際上是一個(gè)DeferredFileOutputStream類對(duì)象: 
    public int readBodyData(OutputStream output) 
    throws MalformedStreamException,IOException 

    // 用于控制循環(huán)的變量 
    boolean done = false; 
    int pad; 
    int pos; 
    int bytesRead; 
    // 寫入到輸出流中的字節(jié)個(gè)數(shù) 
    int total = 0; 
    while (!done) 

    pos = findSeparator();// 搜索分隔界線 
    if (pos != -1) //緩沖區(qū)buffer中包含有分隔界線 

    output.write(buffer, head, pos - head); 
    total += pos - head; 
    head = pos;//head變量跳過主體數(shù)據(jù),指向分隔界線的首字節(jié) 
    done = true;// 跳出循環(huán) 

    else //緩沖區(qū)buffer中沒有包含分隔界線 

    /*根據(jù)緩沖區(qū)中未被readHeaders方法讀取的數(shù)據(jù)內(nèi)容是否大于圖1.4中的 
    保留區(qū)的大小,來決定保留到下一次buffer緩沖區(qū)中的字節(jié)個(gè)數(shù) 
    */ 
    if (tail - head > keepRegion) 

    pad = keepRegion; 

    else 

    pad = tail - head; 

    output.write(buffer, head, tail - head - pad); 
    total += tail - head - pad;//統(tǒng)計(jì)寫入到輸出流中的字節(jié)個(gè)數(shù) 
    /*將上一次buffer緩沖區(qū)中的未處理的數(shù)據(jù)轉(zhuǎn)移到 
    下一次buffer緩沖區(qū)的開始位置 
    */ 
    System.arraycopy(buffer, tail - pad, buffer, 0, pad); 
    head = 0; //讓head變量指向緩沖區(qū)的開始位置 
    //向buffer緩沖區(qū)中載入新的數(shù)據(jù) 
    bytesRead = input.read(buffer, pad, bufSize - pad); 
    if (bytesRead != -1) 

    //設(shè)置buffer緩沖區(qū)中的有效字節(jié)的個(gè)數(shù) 
    tail = pad + bytesRead; 

    else 

    /*還沒有找到分隔界線,輸入流就結(jié)束了,輸入流中的數(shù)據(jù)格式 
    顯然不正確,保存緩沖區(qū)buffer中還未處理的數(shù)據(jù)后拋出異常 
    */ 
    output.write(buffer, 0, pad); 
    output.flush(); 
    total += pad; 
    throw new MalformedStreamException 
    ("Stream ended unexpectedly "); 



    output.flush(); 
    return total; 


    9. discardBodyData方法 
    MultipartStream類中的discardBodyData方法用來跳過主體數(shù)據(jù),它與readBodyData方法非常相似,不同之處在于readBodyData方法把數(shù)據(jù)寫 

    入到一個(gè)輸出流中,而discardBodyData方法是把數(shù)據(jù)丟棄掉。discardBodyData方法返回被丟掉的字節(jié)個(gè)數(shù),方法調(diào)用完成后成員變量head指 

    向下一個(gè)分區(qū)分隔界線的首字節(jié)。MultipartStream類中定義discardBodyData這個(gè)方法,是為了忽略主體內(nèi)容部分的第一個(gè)分隔界線前面的內(nèi) 

    容,按照MIME規(guī)范,消息頭和消息體之間的分隔界線前面可以有一些作為注釋信息的內(nèi)容,discardBodyData就是為了拋棄這些注釋信息而提供 

    的。discardBodyData方法的源代碼如下: 
    public int discardBodyData() throws MalformedStreamException,IOException 

    boolean done = false; 
    int pad; 
    int pos; 
    int bytesRead; 
    int total = 0; 
    while (!done) 

    pos = findSeparator(); 
    if (pos != -1) 

    total += pos - head; 
    head = pos; 
    done = true; 

    else 

    if (tail - head > keepRegion) 

    pad = keepRegion; 

    else 

    pad = tail - head; 

    total += tail - head - pad; 
    System.arraycopy(buffer, tail - pad, buffer, 0, pad); 
    head = 0; 
    bytesRead = input.read(buffer, pad, bufSize - pad); 
    if (bytesRead != -1) 

    tail = pad + bytesRead; 

    else 

    total += pad; 
    throw new MalformedStreamException 
    ("Stream ended unexpectedly "); 



    return total; 

    10. readBoundary方法 
    對(duì)于圖1.3中的每一個(gè)分區(qū)的解析處理,程序首先要調(diào)用readHeaders方法讀取描述頭,接著要調(diào)用readBodyData(OutputStream output)讀取主 

    體數(shù)據(jù),這樣就完成了一個(gè)分區(qū)的解析。readBodyData方法內(nèi)部調(diào)用findSeparator方法找到了分隔界線,然后讀取分隔界線前面的內(nèi)容,此時(shí) 

    MultipartStream類中的成員變量head指向了buffer緩沖區(qū)中的分隔界線boundary的第一個(gè)字節(jié)。findSeparator方法只負(fù)責(zé)尋找分隔界線 

    boundary在緩沖區(qū)buffer中的位置,不負(fù)責(zé)從buffer緩沖區(qū)中讀走分隔界線的字節(jié)數(shù)據(jù)。在調(diào)用readBodyData方法之后,程序接著應(yīng)該讓成員 

    變量head跳過分隔界線,讓它指向下一個(gè)分區(qū)的描述頭的第一個(gè)字節(jié),才能調(diào)用readHeaders方法去讀取下一個(gè)分區(qū)的描述頭。 
    MultipartStream類中定義了一個(gè)readBoundary方法,用于讓成員變量head跳過分隔界線,讓它指向下一個(gè)分區(qū)的描述頭的第一個(gè)字節(jié)。對(duì)于圖 

    1.3中的最后的分隔界線,它比其他的分隔界線后面多了兩個(gè)“-”字符,而其他分隔界線與下一個(gè)分區(qū)的內(nèi)容之間還有一個(gè)回車換行,所以, 

    readBoundary方法內(nèi)部跳過分隔界線后,還需要再讀取兩個(gè)字節(jié)的數(shù)據(jù),才能讓成員變量head指向下一個(gè)分區(qū)的描述頭的第一個(gè)字節(jié)。 

    readBoundary方法內(nèi)部讀取分隔界線后面的兩個(gè)字節(jié)數(shù)據(jù)后,根據(jù)它們是回車換行、還是兩個(gè)“-”字符,來判斷這個(gè)分隔界線是下一個(gè)分區(qū)的 

    開始標(biāo)記,還是整個(gè)請(qǐng)求消息的實(shí)體內(nèi)容的結(jié)束標(biāo)記。如果readBoundary方法發(fā)現(xiàn)分隔界線是下一個(gè)分區(qū)的開始標(biāo)記,那么它返回true,否則 

    返回false。readBoundary()方法的源代碼如下: 
    public boolean readBoundary()throws MalformedStreamException 

    byte[] marker = new byte[2]; 
    boolean nextChunk = false; 
    head += boundaryLength; // 跳過分隔界線符 
    try 

    marker[0] = readByte(); 
    marker[1] = readByte(); 
    // 靜態(tài)常量STREAM_TERMINATOR ={‘-’、‘-’} 
    if (arrayequals(marker, STREAM_TERMINATOR, 2)) 

    nextChunk = false; 

    // 靜態(tài)常量FIELD_SEPARATOR ={‘/n’、‘/r’} 
    else if (arrayequals(marker, FIELD_SEPARATOR, 2)) 

    nextChunk = true; 

    else 

    /*如果讀到的既不是回車換行,又不是兩個(gè)減號(hào), 
    說明輸入流有問題,則拋異常。 

    posted on 2011-12-07 20:08 Solitary 閱讀(2221) 評(píng)論(1)  編輯  收藏

    評(píng)論

    # re: Apache Common fileUpload API 詳解(轉(zhuǎn))[未登錄] 2013-09-24 18:51 小鳥

    最近項(xiàng)目中用到了關(guān)于文件上傳的相關(guān)知識(shí),我是新手。
    想和您學(xué)習(xí)一下。
    我要實(shí)現(xiàn)多文件上傳,要對(duì)單個(gè)文件進(jìn)行文件大小的檢驗(yàn),單獨(dú)使用setFileMaxSize的話,如果單個(gè)文件過大,檢驗(yàn)時(shí)很耽誤時(shí)間,我怎么能把request中的多個(gè)文件提取出來呢? 謝謝。
    我的聯(lián)系方式y(tǒng)xm_meng@163.com  回復(fù)  更多評(píng)論   


    只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     
    <2013年9月>
    25262728293031
    1234567
    891011121314
    15161718192021
    22232425262728
    293012345

    導(dǎo)航

    統(tǒng)計(jì)

    常用鏈接

    留言簿(1)

    隨筆分類

    隨筆檔案

    搜索

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    主站蜘蛛池模板: 亚洲日韩欧洲乱码AV夜夜摸 | 国产一卡2卡3卡4卡2021免费观看 国产一卡2卡3卡4卡无卡免费视频 | 亚洲人成网站18禁止久久影院 | 又粗又黄又猛又爽大片免费| 亚洲欧美成aⅴ人在线观看| 免费观看成人毛片a片2008| 亚洲最大av资源站无码av网址| 最近的免费中文字幕视频| 久久综合久久综合亚洲| 99久久免费精品国产72精品九九| 久久精品国产亚洲AV忘忧草18| 韩国免费一级成人毛片| 亚洲永久在线观看| 免费无码成人AV片在线在线播放| 亚洲AV综合永久无码精品天堂| 日韩一级免费视频| 国产亚洲福利一区二区免费看| 免费播放特黄特色毛片| 一级做a爰片久久毛片免费陪| 久久精品亚洲乱码伦伦中文| aa在线免费观看| 亚洲高清视频在线观看| 国产91色综合久久免费| 亚洲最大的成人网| 男人的天堂亚洲一区二区三区| 天天综合亚洲色在线精品| 亚洲国产综合久久天堂| 国产免费一区二区三区免费视频| 亚洲av无码av制服另类专区| 99re6热视频精品免费观看| 亚洲婷婷综合色高清在线| 久久精品女人天堂AV免费观看| 亚洲经典千人经典日产| 亚洲第一视频在线观看免费| 国产一级婬片A视频免费观看| 无码欧精品亚洲日韩一区| 国产91免费在线观看| 亚洲Aⅴ在线无码播放毛片一线天 亚洲avav天堂av在线网毛片 | 奇米影视亚洲春色| 久久综合国产乱子伦精品免费| 亚洲AV综合色区无码二区偷拍|