(轉)multipart form-data boundary 說明

含義 ENCTYPE="multipart/form-data" 說明:
通過 http 協議上傳文件 rfc1867協議概述,jsp 應用舉例,客戶端發送內容構造

1、概述在最初的 http 協議中,沒有上傳文件方面的功能。 rfc1867 (http://www.ietf.org/rfc/rfc1867.txt) 爲 http 協議添加了這個功能。客戶端的瀏覽器,如 Microsoft IE, Mozila, Opera 等,按照此規範將用戶指定的文件發送到服務器。服務器端的網頁程序,如 php, asp, jsp 等,可以按照此規範,解析出用戶發送來的文件。Microsoft IE, Mozila, Opera 已經支持此協議,在網頁中使用一個特殊的 form 就可以發送文件。絕大部分 http server ,包括 tomcat ,已經支持此協議,可接受發送來的文件。各種網頁程序,如 php, asp, jsp 中,對於上傳文件已經做了很好的封裝。

2、上傳文件的實例:用 servelet 實現(http server 爲 tomcat 4.1.24)1. 在一個 html 網頁中,寫一個如下的form :

<form enctype="multipart/form-data" action="http://192.168.29.65/UploadFile" method=post> 
load multi files :<br>   
<input name="userfile1" type="file"><br>  
<input name="userfile2" type="file"><br>
<input name="userfile3" type="file"><br>    <input name="userfile4" type="file"><br>  
text field :<input type="text" name="text" value="text"><br>  
<input type="submit" value="提交"><input type=reset></form>



用戶可以選擇多個文件,填寫表單其它項,點擊“提交”按鈕後就開始上傳給 http://192.168.29.65/upload_file/UploadFile

這是一個 servelet 程序注意 enctype="multipart/form-data", method=post, type="file" 。根據 rfc1867, 這三個屬性是必須的。multipart/form-data 是新增的編碼類型,以提高二進制文件的傳輸效率。具體的解釋請參閱 rfc18672. 服務端 servelet 的編寫現在第三方的 http upload file 工具庫很多。Jarkata 項目本身就提供了fileupload 包http://jakarta.apache.org/commons/fileupload/ 。

文件上傳、表單項處理、效率問題基本上都考慮到了。在 Struts 中就使用了這個包,不過是用 Struts 的方式另行封裝了一次。這裏我們直接使用 fileupload 包。至於Struts 中的用法,請參閱 Struts 相關文檔。這個處理文件上傳的 servelet 主要代碼如下:

public void doPost( HttpServletRequest request, HttpServletResponse response )
{  
    DiskFileUpload diskFileUpload = new DiskFileUpload();    // 允許文件最大長度
    diskFileUpload.setSizeMax( 100*1024*1024 );    // 設置內存緩衝大小
    diskFileUpload.setSizeThreshold( 4096 );    // 設置臨時目錄  
    diskFileUpload.setRepositoryPath( "c:/tmp" ); 
    List fileItems = diskFileUpload.parseRequest( request ); 
    Iterator iter = fileItems.iterator();    for( ; iter.hasNext(); )
    {   
    FileItem fileItem = (FileItem) iter.next();  
      if( fileItem.isFormField() ) {         // 當前是一個表單項   
    out.println( "form field : " + fileItem.getFieldName() + ", " + fileItem.getString() );   
      } else {      
    // 當前是一個上傳的文件      
    String fileName = fileItem.getName(); 
    fileItem.write( new File("c:/uploads/"+fileName) );    
      } 

}}



爲簡略起見,異常處理,文件重命名等細節沒有寫出。3、 客戶端發送內容構造假設接受文件的網頁程序位於 http://192.168.29.65/upload_file/UploadFile.假設我們要發送一個二進制文件、一個文本框表單項、一個密碼框表單項。文件名爲 E:/s ,其內容如下:(其中的XXX代表二進制數據,如 01 02 03)abbXXXccc 客戶端應該向 192.168.29.65 發送如下內容:


POST /upload_file/UploadFile HTTP/1.1
Accept: text/plain, */*
Accept-Language: zh-cn
Host: 192.168.29.65:80
Content-Type:multipart/form-data;boundary=---------------------------7d33a816d302b6
User-Agent: Mozilla/4.0 (compatible; OpenOffice.org)
Content-Length: 424
Connection: Keep-Alive -----------------------------7d33a816d302b6
Content-Disposition:form-data;
name="userfile1";
filename="E:/s"Content-Type:
application/octet-stream abbXXXccc
-----------------------------7d33a816d302b6

Content-Disposition: form-data;

name="text1" foo

-----------------------------7d33a816d302b6

Content-Disposition: form-data;

name="password1" bar

-----------------------------7d33a816d302b6--


(上面有一個回車)此內容必須一字不差,包括最後的回車。

注意:Content-Length: 424 這裏的424是紅色內容的總長度(包括最後的回車)
注意這一行:Content-Type: multipart/form-data; boundary=---------------------------7d33a816d302b6

根據 rfc1867, multipart/form-data是必須的.---------------------------7d33a816d302b6 是分隔符,分隔多個文件、表單項。

其中33a816d302b6 是即時生成的一個數字,用以確保整個分隔符不會在文件或表單項的內容中出現。前面的 ---------------------------7d 是 IE 特有的標誌。

Mozila 爲---------------------------71用手工發送這個例子,在上述的 servlet 中檢驗通過。




使用POST發送數據

  以POST方式發送數據主要是爲了向服務器發送較大量的客戶端的數據,它不受URL的長度限制。POST請求將數據以URL編碼的形式放在HTTP正文中,字段形式爲fieldname=value,用&分隔每個字段。注意所有的字段都被作爲字符串處理。實際上我們要做的就是模擬瀏覽器POST一個表單。以下是IE發送一個登陸表單的POST請求:

POST http://127.0.0.1/login.do HTTP/1.0
Accept: image/gif, image/jpeg, image/pjpeg, */*
Accept-Language: en-us,zh-cn;q=0.5
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Content-Length: 28
/r/n
username=admin&password=1234


  要在MIDP應用程序中模擬瀏覽器發送這個POST請求,首先設置HttpConnection的請求方式爲POST:

hc.setRequestMethod(HttpConnection.POST);


  然後構造出HTTP正文:

byte[] data = "username=admin&password=1234".getBytes();

  並計算正文長度,填入Content-Type和Content-Length:

hc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
hc.setRequestProperty("Content-Length", String.valueOf(data.length));

  然後打開OutputStream將正文寫入:

OutputStream output = hc.openOutputStream();
output.write(data);


  需要注意的是,數據仍需要以URL編碼格式編碼,由於MIDP庫中沒有J2SE中與之對應的URLEncoder類,因此,需要自己動手編寫這個encode()方法,可以參考java.net.URLEncoder.java的源碼。剩下的便是讀取服務器響應,代碼與GET一致,這裏就不再詳述。

  使用multipart/form-data發送文件

  如果要在MIDP客戶端向服務器上傳文件,我們就必須模擬一個POST multipart/form-data類型的請求,Content-Type必須是multipart/form-data。

  以multipart/form-data編碼的POST請求格式與application/x-www-form-urlencoded完全不同,multipart/form-data需要首先在HTTP請求頭設置一個分隔符,例如ABCD:

hc.setRequestProperty("Content-Type", "multipart/form-data; boundary=ABCD");

  然後,將每個字段用“--分隔符”分隔,最後一個“--分隔符--”表示結束。例如,要上傳一個title字段"Today"和一個文件C:/1.txt,HTTP正文如下:

--ABCD
Content-Disposition: form-data; name="title"
/r/n
Today
--ABCD
Content-Disposition: form-data; name="1.txt"; filename="C:/1.txt"
Content-Type: text/plain
/r/n
<這裏是1.txt文件的內容>
--ABCD--
/r/n


  請注意,每一行都必須以/r/n結束,包括最後一行。如果用Sniffer程序檢測IE發送的POST請求,可以發現IE的分隔符類似於---------------------------7d4a6d158c9,這是IE產生的一個隨機數,目的是防止上傳文件中出現分隔符導致服務器無法正確識別文件起始位置。我們可以寫一個固定的分隔符,只要足夠複雜即可。

  發送文件的POST代碼如下:

String[] props = ... // 字段名
String[] values = ... // 字段值
byte[] file = ... // 文件內容
String BOUNDARY = "---------------------------7d4a6d158c9"; // 分隔符
StringBuffer sb = new StringBuffer();
// 發送每個字段:
for(int i=0; i
sb = sb.append("--");
sb = sb.append(BOUNDARY);
sb = sb.append("/r/n");
sb = sb.append("Content-Disposition: form-data; name=/""+ props[i] + "/"/r/n/r/n");
sb = sb.append(URLEncoder.encode(values[i]));
sb = sb.append("/r/n");
}
// 發送文件:
sb = sb.append("--");
sb = sb.append(BOUNDARY);
sb = sb.append("/r/n");
sb = sb.append("Content-Disposition: form-data; name=/"1/"; filename=/"1.txt/"/r/n");
sb = sb.append("Content-Type: application/octet-stream/r/n/r/n");
byte[] data = sb.toString().getBytes();
byte[] end_data = ("/r/n--" + BOUNDARY + "--/r/n").getBytes();
// 設置HTTP頭:
hc.setRequestProperty("Content-Type", MULTIPART_FORM_DATA + "; boundary=" + BOUNDARY);
hc.setRequestProperty("Content-Length", String.valueOf(data.length + file.length + end_data.length));
// 輸出:
output = hc.openOutputStream();
output.write(data);
output.write(file);
output.write(end_data);
// 讀取服務器響應:
// TODO...
發佈了43 篇原創文章 · 獲贊 177 · 訪問量 186萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章