欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
DiskFileUpload
1.setSizeMax方法

setSizeMax方法用于設置請求消息實(shí)體內容的最大允許大小,以防止客戶(hù)端故意通過(guò)上傳特大的文件來(lái)塞滿(mǎn)服務(wù)器端的存儲空間,單位為字節。

2.setSizeThreshold方法
Apache文件上傳組件在解析和處理上傳數據中的每個(gè)字段內容時(shí),需要臨時(shí)保存解析出的數據。因為Java虛擬機默認可以使用的內存空間是有

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

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

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

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

“<tomcat安裝目錄>/temp/”目錄。

4. parseRequest方法
parseRequest 方法是DiskFileUpload類(lèi)的重要方法,它是對HTTP請求消息進(jìn)行解析的入口方法,如果請求消息中的實(shí)體內容的類(lèi)型不是

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

包裝成獨立的FileItem對象,然后將這些FileItem對象加入進(jìn)一個(gè)List類(lèi)型的集合對象中返回。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方法方法用于判斷請求消息中的內容是否是“multipart/form-data”類(lèi)型,是則返回true,否則返回false。

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

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

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

字符集來(lái)編碼FORM表單字段中的內容,請參看筆者編著(zhù)的《深入體驗java Web開(kāi)發(fā)內幕——核心基礎》一書(shū)中的第6.9.2的講解,

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

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

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

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

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

段元素的數據。FileItem是一個(gè)接口,在應用程序中使用的實(shí)際上是該接口一個(gè)實(shí)現類(lèi),該實(shí)現類(lèi)的名稱(chēng)并不重要,程序可以采用FileItem接

口類(lèi)型來(lái)對它進(jìn)行引用和訪(fǎng)問(wèn),為了便于講解,這里將FileItem實(shí)現類(lèi)稱(chēng)之為FileItem類(lèi)。FileItem類(lèi)還實(shí)現了Serializable接口,以支持序

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

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

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

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

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

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

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

.tmp”這種形式,FileItem類(lèi)內部提供了維護臨時(shí)文件名中的數值不重復的機制,以保證了臨時(shí)文件名的唯一性。當應用程序將主體內容保存

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

了四個(gè)異常類(lèi),其中FileUploadException是其他異常類(lèi)的父類(lèi),其他幾個(gè)類(lèi)只是被間接調用的底層類(lèi),對于A(yíng)pache組件調用人員來(lái)說(shuō),只需對

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

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

表單,表單的enctype屬性設置值為“multipart/form-data”,表單的action屬性設置為“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>測試文件上傳組件的頁(yè)面</h3>
<form action="servlet/UploadServlet"
enctype="multipart/form-data" method="post">
作者:<input type="text" name="author"><br>
來(lái)自:<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

調用Apache文件上傳組件來(lái)處理FORM表單提交的文件內容和普通字段數據。
例程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();

//設置保存上傳文件的目錄
String uploadDir = getServletContext().getRealPath("/upload");
if (uploadDir == null)
{
out.println("無(wú)法訪(fǎng)問(wèn)存儲目錄!");
return;
}
File fUploadDir = new File(uploadDir);
if(!fUploadDir.exists())
{
if(!fUploadDir.mkdir())
{
out.println("無(wú)法創(chuàng )建存儲目錄!");
return;
}
}

if (!DiskFileUpload.isMultipartContent(request))
{
out.println("只能處理multipart/form-data類(lèi)型的數據!");
return ;
}

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

//得到所有表單字段對象的集合
List fileItems = null;
try
{
fileItems = fu.parseRequest(request);
}
catch (FileUploadException e)
{
out.println("解析數據時(shí)出現如下問(wè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();

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("存儲文件時(shí)出現如下問(wèn)題:");
e.printStackTrace(out);
return;
}
finally //總是立即刪除保存表單字段內容的臨時(shí)文件
{
fi.delete();
}

}
}

//顯示處理結果
out.println("用戶(hù):" + request.getAttribute("author") + "<br>");
out.println("來(lái)自:" + request.getAttribute("company") + "<br>");


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()));

}


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編譯程序報告的錯誤,直

到編譯成功通過(guò)為止。
(3)修改<tomcat的安裝目錄>\webapps\fileupload\WEB-INF\classes\web.xml文件,在其中注冊和映射UploadServlet的訪(fǎng)問(wèn)路徑,如例程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
填寫(xiě)返回頁(yè)面中的FORM表單,如圖1.4所示,單擊“上載”按鈕后,瀏覽器返回的頁(yè)面信息如圖1.5所示。
圖1.4
圖1.5(這些圖的標題欄中的it315改為fileupload)
查看<tomcat安裝目錄>\webapps\it315\upload目錄,可以看到剛才上傳的兩個(gè)文件。
(4)單擊瀏覽器工具欄上的“后退”按鈕回到表單填寫(xiě)頁(yè)面,只在第二個(gè)文件字段中選擇一個(gè)文件,單擊“上載”按鈕,瀏覽器返回的顯示結果

如圖1.6所示。
圖1.6
M腳下留心:
上面編寫(xiě)的Servlet程序將上傳的文件保存在了當前WEB應用程序下面的upload目錄中,這個(gè)目錄是客戶(hù)端瀏覽器可以訪(fǎng)問(wèn)到的目錄。如果用戶(hù)

通過(guò)瀏覽器上傳了一個(gè)名稱(chēng)為test.jsp的文件,那么用戶(hù)接著(zhù)就可以在瀏覽器中訪(fǎng)問(wèn)這個(gè)test.jsp文件了,對于本地瀏覽器來(lái)說(shuō),這個(gè)jsp文件

的訪(fǎng)問(wèn)URL地址如下所示:
http://localhost:8080/fileupload/upload/test.jsp
對于遠程客戶(hù)端瀏覽器而言,只需要將上面的url地址中的localhost改寫(xiě)為T(mén)omcat服務(wù)器的主機名或IP地址即可。用戶(hù)可以通過(guò)上面的Servlet

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

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

。

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

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

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

習和研究該組件的源碼,本節將分析Apache文件上傳組件的源代碼實(shí)現。對于只想了解如何使用Apache文件上傳組件來(lái)上傳文件的讀者來(lái)說(shuō),

不必學(xué)習本節的內容。在學(xué)習本節內容之前,讀者需要仔細學(xué)習了筆者編著(zhù)的《深入體驗java Web開(kāi)發(fā)內幕——核心基礎》一書(shū)中的第6.7.2節

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

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

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

DiskFileUpload類(lèi)再調用和協(xié)調更底層的類(lèi)來(lái)完成具體的功能。解析類(lèi)MultipartStream和工廠(chǎng)類(lèi)DefaultFileItemFactory就是DiskFileUpload

類(lèi)調用的兩個(gè)的底層類(lèi)。MultipartStream類(lèi)用于對請求消息中的實(shí)體數據進(jìn)行具體解析,DefaultFileItemFactory類(lèi)對MultipartStream類(lèi)解

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

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

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

用一個(gè)ByteArrayOutputStream類(lèi)對象來(lái)存儲數據,當寫(xiě)入它里面的主體內容的大小大于DiskFileUpload.setSizeThreshold方法設置的臨界值

時(shí),DeferredFileOutputStream類(lèi)內部創(chuàng )建一個(gè)文件輸出流對象來(lái)存儲數據,并將前面寫(xiě)入到ByteArrayOutputStream類(lèi)對象中的數據轉移到文

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

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

字段元素的數據封裝成FileItem類(lèi)對象。
(5)DefaultFileItemFactory工廠(chǎng)類(lèi)對象把MultipartStream類(lèi)對象解析出的各個(gè)表單字段元素的數據封裝成若干DefaultFileItem類(lèi)對象,然

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

工廠(chǎng)類(lèi)把該表單字段元素封裝成對應的FileItem類(lèi)對象。

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

析出每個(gè)分區的數據,接著(zhù)再從每個(gè)分區中解析出描述頭和主體內容部分。
在讀取HTTP請求消息中的實(shí)體內容時(shí),只能調用HttpServletRequest.getInputStream方法返回的字節輸入流,而不能調用

HttpServletRequest.getReader方法返回的字符輸入流,因為不管上傳的文件類(lèi)型是文本的、還是其他各種格式的二進(jìn)制內容,WEB服務(wù)器程序

要做的工作就是將屬于文件內容的那部分數據原封不動(dòng)地提取出來(lái),然后原封不動(dòng)地存儲到本地文件系統中。如果使用

HttpServletRequest.getReader方法返回的字符輸入流對象來(lái)讀取HTTP請求消息中的實(shí)體內容,它將HTTP請求消息中的字節數據轉換成字符后

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

HttpServletRequest.getReader方法返回的字符輸入流對象來(lái)進(jìn)行讀取。
另外,不能期望用一個(gè)很大的字節數組就可以裝進(jìn)HTTP請求消息中的所有實(shí)體內容,因為程序中定義的字節數組大小總是有限制的,但應該允

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

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

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

對象中。
程序要識別出圖1.3中的每一個(gè)分區,需要在圖1.9所示的字節數組緩沖區buffer中尋找分區的字段分隔界線(xiàn),當找到一個(gè)字段分隔界線(xiàn)后,就

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

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

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

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

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

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

。
圖1.10

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

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

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

buffer上一次讀取的數據的后一部分內容正好是分隔界線(xiàn)字符序列的前面一部分內容,而這一部分內容的長(cháng)度是不確定的,可能只是分隔界線(xiàn)

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

區buffer上一次讀取的數據的后一部分內容正好是分隔界線(xiàn)字符序列的前面一部分內容,但它們在整個(gè)輸入字節流中的后續內容不一定就整個(gè)

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

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

容從頭開(kāi)始查找字段分隔界線(xiàn),如圖1.12所示。
圖1.12

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

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

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

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

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

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

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

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

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

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

個(gè)對象又是保存在DefaultFileItem類(lèi)對象中的一個(gè)成員變量,這樣,readBodyData方法就可以將一個(gè)分區的主體部分的數據寫(xiě)入到

DefaultFileItem類(lèi)對象中。
因為圖1.3中的實(shí)體內容內部的字段分隔界線(xiàn)是在content-type頭中指定的字段分隔界線(xiàn)前面增加了兩個(gè)減號(-)字符而形成的,而每個(gè)字段

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

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

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

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

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

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

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

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

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

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

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

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

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

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

bufSize指定圖1.10中的buffer緩沖區字節數組的長(cháng)度,默認值是4096個(gè)字節。這個(gè)構造方法的源代碼如下:
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緩沖區中保留給下次讀取的最大字節個(gè)數
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中生成最終的分隔界線(xiàn)
System.arraycopy (boundary, 0, this.boundary, 4, boundary.length);

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

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

入流中讀取一批新的字節數據來(lái)重新填充buffer緩沖區。readByte()方法的源代碼如下:
public byte readByte () throws IOException
{
// 判斷是否已經(jīng)讀完了buffer緩沖區中的所有數據
if (head == tail)
{
head = 0;
//讀入新的數據內容來(lái)填充buffer緩沖區
tail = input.read(buffer, head, bufSize);
if (tail == -1)
{
throw new IOException("No more data is available ");
}
}
return buffer[head++];// 返回當前字節,head++
}
其中,head變量是MultipartStream類(lèi)中定義的一個(gè)int類(lèi)型的成員變量,它用于表示正在讀取的字節在buffer數組緩沖區中的位置;tail變量

也是MultipartStream類(lèi)中定義的一個(gè)int類(lèi)型的成員變量,它用于表示當前buffer數組緩沖區裝入的實(shí)際字節內容的長(cháng)度。在MultipartStream

類(lèi)中主要是通過(guò)控制成員變量head的值來(lái)控制對buffer緩沖區中的數據的讀取和直接跳過(guò)某段數據,通過(guò)比較head與tail變量的值了解是否需

要向buffer緩沖區中裝入新的數據內容。當每次向buffer緩沖區中裝入新的數據內容后,都應該調整成員變量head和tail的值。

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

false。arrayequals方法的源代碼如下,參數count指定了對字節數組中的前面幾個(gè)字節內容進(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類(lèi)中的findByte()方法從字節數組緩沖區buffer中的某個(gè)位置開(kāi)始搜索一個(gè)特定的字節數據,如果找到了,則返回該字節在

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

是要搜索的字節數據:
protected int findByte(byte value,int pos)
{
for (int i = pos; i < tail; i++)
{
if (buffer[i] == value)
{
return i; // 找到該值,findByte方法返回
}
}
return - 1;
}
如果程序需要在buffer緩沖區中多次搜索某個(gè)特定的字節數據,那就可以循環(huán)調用findByte方法,只是在每次調用findByte方法時(shí),必須不斷

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

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

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

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

了buffer緩沖區中的最后boundaryLength -1個(gè)字節。findSeparator方法內部為什么調用findByte方法查找到buffer緩沖區中的最后

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

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

節作為保留區,在下次讀取buffer緩沖區時(shí)將這些保留的字節數據重新填充到buffer緩沖區的開(kāi)始部分。findSeparator方法的源代碼如下:
protected int findSeparator()
{
int first;
int match = 0;
int maxpos = tail - boundaryLength;//在buffer中搜索的最大位置

for (first = head;(first <= maxpos) && (match != boundaryLength);
first++)
{
//在buffer緩沖區中尋找boundary的第一個(gè)字節
first = findByte(boundary[0], first);

if (first == -1 || (first > maxpos))
{
return -1;
}
//確定隨后的字節序列是否確實(shí)是分隔界線(xiàn)的其他字節內容
for (match = 1; match < boundaryLength; match++)
{
if (buffer[first + match] != boundary[match])
{
break;
}
}
}
// 當前buffer中找到boundary,返回第一個(gè)字節所在位置值
if (match == boundaryLength)
{
return first - 1;
}
return -1; // 當前buffer中沒(méi)找到boundary,返回-1
}
圖1.14中描述了findSeparator方法內部定義的各個(gè)變量的示意圖。
圖1.14
findSeparator方法內部的代碼主要包括如下三個(gè)步驟:
(1)循環(huán)調用findByte(boundary[0], first)找到buffer緩沖區中的與boundary[0]相同的字節的位置,并將位置記錄在first變量中。
(2)比較buffer緩沖區中的first后的boundaryLength-1個(gè)字節序列是否與boundary中的其他字節序列相同。如果不同,說(shuō)明這個(gè)first變量指

向的字節不是分隔界線(xiàn)的開(kāi)始字節,跳出內循環(huán),將first變量加1后繼續外循環(huán)調用findByte方法;如果相同,說(shuō)明在當前緩沖區buffer中找

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

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

,由于第(2)中將first加1了,所以這里的返回值應該是first-1;如果不等,說(shuō)明當前緩沖區huffer中沒(méi)有分隔界線(xiàn),返回-1。

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

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

的成員變量head指向了buffer緩沖區中的分隔界線(xiàn)boundary的第一個(gè)字節,程序接著(zhù)應調用readBoundary方法跳過(guò)分隔界線(xiàn)及其隨后的回車(chē)換

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

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

描述頭的全部?jì)热?,此時(shí)成員變量head將指向分區中的主體內容的第一個(gè)字節。readHeaders()方法的源代碼如下:
public String readHeaders()throws MalformedStreamException
{
int i = 0;
//從下面的代碼看來(lái),這里定義成一個(gè)byte即可,不用定義成byte數組
byte b[] = new byte[1];
//用于臨時(shí)保存描述頭信息的字節數組輸出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//對描述頭部分的數據內容過(guò)大進(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]); // 將當前字節存入緩沖流
}
}
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)調用readByte()方法逐個(gè)讀取buffer緩沖區中的字節,并將讀取的字節與HEADER_SEPARATOR ={‘\n’,‘\r’,‘\n’

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

法讀取的字節將與HEADER_SEPARATOR中的第二字節比較,如果相等,則依照這種方式比較后面的字節內容,如果連續讀取到了

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

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

數據,所以,readHeaders方法執行完以后,成員變量head指向分區主體部分的首字節。readHeaders方法最后將把存入字節數組輸出流中的字

節數據按指定字符集編碼轉換成字符串并返回,該字符串就是描述頭字符串。

8. readBodyData方法
MultipartStream類(lèi)中的readBodyData方法用于把主體部分的數據寫(xiě)入到一個(gè)輸出流對象中,并返回寫(xiě)入到輸出流中的字節總數。當調用

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

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

置。從分區主體部分的首字節開(kāi)始,直到在findSeparator方法找到的下一個(gè)分區分隔界線(xiàn)前的所有數據都是這個(gè)分區的主體部分的數據,

readBodyData方法需要把這些數據都寫(xiě)到輸出流output對象中。如果findSeparator方法在buffer緩沖區中沒(méi)有找到分區分隔界線(xiàn),

readBodyData方法還必須向buffer緩沖區中裝入新的數據內容后繼續調用findSeparator方法進(jìn)行處理。在向buffer緩沖區中裝入新的數據內容

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

的參數實(shí)際上是一個(gè)DeferredFileOutputStream類(lèi)對象:
public int readBodyData(OutputStream output)
throws MalformedStreamException,IOException
{
// 用于控制循環(huán)的變量
boolean done = false;
int pad;
int pos;
int bytesRead;
// 寫(xiě)入到輸出流中的字節個(gè)數
int total = 0;
while (!done)
{
pos = findSeparator();// 搜索分隔界線(xiàn)
if (pos != -1) //緩沖區buffer中包含有分隔界線(xiàn)
{
output.write(buffer, head, pos - head);
total += pos - head;
head = pos;//head變量跳過(guò)主體數據,指向分隔界線(xiàn)的首字節
done = true;// 跳出循環(huán)
}
else //緩沖區buffer中沒(méi)有包含分隔界線(xiàn)
{

if (tail - head > keepRegion)
{
pad = keepRegion;
}
else
{
pad = tail - head;
}
output.write(buffer, head, tail - head - pad);
total += tail - head - pad;//統計寫(xiě)入到輸出流中的字節個(gè)數

System.arraycopy(buffer, tail - pad, buffer, 0, pad);
head = 0; //讓head變量指向緩沖區的開(kāi)始位置
//向buffer緩沖區中載入新的數據
bytesRead = input.read(buffer, pad, bufSize - pad);
if (bytesRead != -1)
{
//設置buffer緩沖區中的有效字節的個(gè)數
tail = pad + bytesRead;
}
else
{

output.write(buffer, 0, pad);
output.flush();
total += pad;
throw new MalformedStreamException
("Stream ended unexpectedly ");
}
}
}
output.flush();
return total;
}

9. discardBodyData方法
MultipartStream類(lèi)中的discardBodyData方法用來(lái)跳過(guò)主體數據,它與readBodyData方法非常相似,不同之處在于readBodyData方法把數據寫(xiě)

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

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

容,按照MIME規范,消息頭和消息體之間的分隔界線(xiàn)前面可以有一些作為注釋信息的內容,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方法
對于圖1.3中的每一個(gè)分區的解析處理,程序首先要調用readHeaders方法讀取描述頭,接著(zhù)要調用readBodyData(OutputStream output)讀取主

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

MultipartStream類(lèi)中的成員變量head指向了buffer緩沖區中的分隔界線(xiàn)boundary的第一個(gè)字節。findSeparator方法只負責尋找分隔界線(xiàn)

boundary在緩沖區buffer中的位置,不負責從buffer緩沖區中讀走分隔界線(xiàn)的字節數據。在調用readBodyData方法之后,程序接著(zhù)應該讓成員

變量head跳過(guò)分隔界線(xiàn),讓它指向下一個(gè)分區的描述頭的第一個(gè)字節,才能調用readHeaders方法去讀取下一個(gè)分區的描述頭。
MultipartStream類(lèi)中定義了一個(gè)readBoundary方法,用于讓成員變量head跳過(guò)分隔界線(xiàn),讓它指向下一個(gè)分區的描述頭的第一個(gè)字節。對于圖

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

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

readBoundary方法內部讀取分隔界線(xiàn)后面的兩個(gè)字節數據后,根據它們是回車(chē)換行、還是兩個(gè)“-”字符,來(lái)判斷這個(gè)分隔界線(xiàn)是下一個(gè)分區的

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

返回false。readBoundary()方法的源代碼如下:
public boolean readBoundary()throws MalformedStreamException
{
byte[] marker = new byte[2];
boolean nextChunk = false;
head += boundaryLength; // 跳過(guò)分隔界線(xiàn)符
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
{
/*如果讀到的既不是回車(chē)換行,又不是兩個(gè)減號,
說(shuō)明輸入流有問(wèn)題,則拋異常。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
FIleItem
javaee之文件上傳和下載
FileItem類(lèi)
jsp用戶(hù)上傳頭像、上傳圖片、郵件上傳附件代碼
Java web-文件上傳的原理
JSP上傳excel及excel插入至數據庫實(shí)現方法代碼
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久