1. 程式人生 > >關於application/x-www-form-urlencoded編碼

關於application/x-www-form-urlencoded編碼

同事遇到在servlet端通過request物件getInputStream讀取POST過來的資料,卻讀不到的問題,懷疑是tomcat的問題。查了一下Content-typeapplication/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編碼,對於這兩種編碼如何選擇,請參考這裡