轉http://hongjiang.info/http-application-x-www-form-urlencoded/
同事遇到在servlet端通過request對象getInputStream讀取POST過來的數據,卻讀不到的問題,懷疑是tomcat的問題。查了一下Content-type
是application/x-www-form-urlencoded
,估計是被解析成了parameters
,果然在他獲取流之前,有過request.getParameter
的操作。
熟悉servlet的話,這個問題應該算常識了。它其實跟容器無關,所有的servlet容器都是這樣的行為。幾年前在實現一個網關代理的時候就遇到過這個問題,當時使用的是jetty,發現POST過來的數據讀不到,也是application/x-www-form-urlencoded
編碼,斷點跟蹤發現是在獲取流之前有過request.getParameter
,數據會被解析,並且后續數據流不可再被讀取。
在servlet規范3.1.1節里,對POST數據何時會被當做parameters有描述:
1. The request is an HTTP or HTTPS request.
2. The HTTP method is POST.
3. The content type is application/x-www-form-urlencoded.
4. The servlet has made an initial call of any of the getParameter family of methods on the request object.
If the conditions are met, post form data will no longer be available for reading directly from the request object’s input stream.
規范里已經明確的聲明當請求滿足: 1) http/https, 2) POST, 3) Content-type 是application/x-www-form-urlencoded
, 4) 調用過getParameter方法;則數據會被當做請求的paramaters,而不能再通過 request 的 inputstream 直接讀取。
所以不論tomcat、jetty還是其他servlet容器都遵循這個方式。不過話說回來,為什么application/x-www-form-urlencoded
編碼的數據會被當做parameter來解析呢?
使用http上傳數據可以用GET或POST,使用GET的話,只能通過uri的queryString形式,這會遇到長度的問題,各個瀏覽器或server可能對長度支持的不同,所以到要提交的數據如果太長並不適合使用GET提交。
采用POST的話,既可以在uri中帶有queryString也可以將數據放在body中。body內容可以有多種編碼形式,其中application/x-www-form-urlencoded
編碼其實是基於uri的percent-encoding
編碼的,所以采用application/x-www-form-urlencoded
的POST數據和queryString只是形式不同,本質都是傳遞參數。
在tomcat的Request.parseParameters方法里,對於application/x-www-form-urlencoded
是有做判斷的,對這種編碼會去解析body里的數據,填充到parameters里,所以后續想再通過流的方式讀取body是讀不到的(除非你沒有觸發過getParameter相關的方法)。
在HTML4之前,表單數據的編碼方式只有application/x-www-form-urlencoded
這一種(現在默認也是這種方式),因為早期的時候,web上提交過來的數據也是非常簡單的,基本上以key-value形式為主,所以表單采用application/x-www-form-urlencoded
這種編碼形式也沒什么問題。
在HTML4里又引入了multipart/form-data
編碼,對於這兩種編碼如何選擇,請參考這里。