還是一個異常引發的故事:
需要實現一個手機客戶端行為分析的需求,手機客戶端需要上傳一些數據文件。手機客戶端通過http協議post方式上傳數據文件的時候,我們發現在服務器端無法通過request.getInputStream()獲取到相應的數據,調用request.getInputStream()讀取數據里頭啥都木有。
經過各種嘗試我們注意到如下的情況:
1.在不做任何修改的情況下,調用request.getParameter()可以獲取到部分數據,即url拼接參數的數據。
2.在不做任何修改的情況下,如果在之前任何地方都不調用request.getParameter(),而是直接調用request.getInputStream()是可以獲取到數據的。
3.原來客戶端使用默認的請求頭Content-Type:application/x-www-form-urlencoded,修改此值為multipart/form-data或者application/octet-stream之后,通過request.getInputStream()可以獲取到數據,即使之前調用過request.getParameter()。
最后的解決方法當然是客戶端修改Content-Type,因為服務器端request.getInputStream()的方法不方便修改。今天具體分析下里頭的原理。
一.從form的enctype屬性到Content-Type寫html的時候我們都知道form有個屬性enctype,默認值是application/x-www-form-urlencoded,這個值表示會將表單數據用&符號做一個簡單的拼接。例如:
[plain] view plain copyPRint?POST /post_test.php HTTP/1.1 Accept-Language: zh-CN User-Agent: Mozilla/4.0 Content-Type: application/x-www-form-urlencoded Host: 192.168.12.102 Content-Length: 42 Connection: Keep-Alive Cache-Control: no-cache title=test&content=%B3%AC%BC%B6%C5%AE%C9%FA&submit=post+article 
POST /post_test.php HTTP/1.1 Accept-Language: zh-CNUser-Agent: Mozilla/4.0 Content-Type: application/x-www-form-urlencoded Host: 192.168.12.102Content-Length: 42Connection: Keep-AliveCache-Control: no-cache title=test&content=%B3%AC%BC%B6%C5%AE%C9%FA&submit=post+article我們注意到這個時候Content-Type為application/x-www-form-urlencoded。如果enctype的值為multipart/form-data,這個值一般用于表單中包含文件上傳的情況,它會將表單中的數據使用一個boundary作為分隔上傳。例如:
[plain] view plain copyprint?POST /post_test.php?t=1 HTTP/1.1 Accept-Language: zh-CN User-Agent: Mozilla/4.0 Content-Type: multipart/form-data; boundary=---------------------------7dbf514701e8 Accept-Encoding: gzip, deflate Host: 192.168.12.102 Content-Length: 345 Connection: Keep-Alive Cache-Control: no-cache -----------------------------7dbf514701e8 Content-Disposition: form-data; name="title" test -----------------------------7dbf514701e8 Content-Disposition: form-data; name="content" .... -----------------------------7dbf514701e8 Content-Disposition: form-data; name="submit" post article -----------------------------7dbf514701e8-- 
POST /post_test.php?t=1 HTTP/1.1Accept-Language: zh-CNUser-Agent: Mozilla/4.0 Content-Type: multipart/form-data; boundary=---------------------------7dbf514701e8Accept-Encoding: gzip, deflateHost: 192.168.12.102Content-Length: 345Connection: Keep-AliveCache-Control: no-cache -----------------------------7dbf514701e8Content-Disposition: form-data; name="title"test-----------------------------7dbf514701e8Content-Disposition: form-data; name="content"....-----------------------------7dbf514701e8Content-Disposition: form-data; name="submit"post article-----------------------------7dbf514701e8--我們注意到這個時候Content-Type也相應的變為multipart/form-data,同時后面還加上了分隔符boundary的描述。所以,其實form的enctype屬性某種程度上決定了Content-Type值和請求body里頭的數據格式。
詳細的可以參考:http://imzc.NET/archives/131
二.從Content-Type到request.getInputStream()
上面說到了form的處理情況,但是其實如果我們不是使用瀏覽器,而是自己實現的客戶端來傳遞數據的話,這些頭信息就得都由自己處理。
所以上面就出現了,上傳文件的時候仍然使用了application/x-www-form-urlencoded的不標準用法。
但是為啥Content-Type會影響request的處理呢?這得從request的一些實現說起。
request.getParameter()、 request.getInputStream()、request.getReader()這三種方法是有沖突的,因為流只能被讀一次。
比如: 當form表單內容采用 enctype=application/x-www-form-urlencoded編碼時,先通過調用request.getParameter() 方法得到參數后,再調用 request.getInputStream()或request.getReader()已經得不到流中的內容,因為在調用 request.getParameter()時系統可能對表單中提交的數 據以流的形式讀了一次,反之亦然。 當form表單內容采用 enctype=multipart/form-data編碼時,即使先調用request.getParameter()也得不到數據,但是這時調用 request.getParameter()方法對 request.getInputStream()或request.getReader()沒有沖突,即使已經調用了 request.getParameter()方法也 可以通過調用request.getInputStream()或request.getReader()得 到表單中的數據,而request.getInputStream()和request.getReader()在同 一個響應中是不能混合使用的,如果混合使用就會拋異常。
更多也可以參考:http://robert-liu.iteye.com/blog/713568