http request乱码的真相
首先,從瀏覽器端看下有多少種情況:
1.在瀏覽器的地址欄,或者搜索框里輸入地址:http://www.test.com/衣服/search?keyword=T恤
2.在一個指定了編碼的網頁中,提交一個form,如:
| 1 2 3 4 5 6 7 8 9 10 11 12 | <html>? <head>? ????<meta charset="gbk">? </head>? <body>? <p>你好</p>? <form id="productSearchForm" action="http://127.0.0.1:8080/test.html" method="post">? ????<input name="keyword" class="keyword" value="T恤" maxlength="30">? ????<button type="submit">搜索</button>? </form>? </body>? </html> |
當然還有,各種細分的選項,如get/post,form里是否指定了編碼。
3. ajax請求里的編碼。
我們從流程上來看,一個http request要經過哪些東東的處理:
1.瀏覽器/JavaScript
2.web server,以tomcat/jetty為例
3.filter/servlet ,以Java為例
4.web 框架,以spring?mvc為例。
對于在瀏覽器的地址欄支持輸入的地址,各種瀏覽器是如何處理的,可以參考這個:
http://www.ruanyifeng.com/blog/2010/02/url_encoding.html
也可以自己簡單的測試,在Linux下執行:
| 1 | nc -l 8080 |
接著在瀏覽器里直接訪問 http://localhost:8080/衣服/search?keyword=T恤,
然后在就可以看到nc的輸出結果了。當然,瀏覽器的debug工具也可以很方法地看到編碼的結果,不過用nc,就不用自己跑一個web服務器了,非常方便。
另外那個keyword=T恤,也是有意選擇的,這樣可以很方便地看到編碼的結果。恤的gbk編碼是兩個byte,utf-8編碼是3個byte,也很容易區別到底是什么編碼。
簡單地總結下對于瀏覽器地址欄里直接訪問:http://www.test.com/衣服/search?keyword=T恤 ?的編碼情況:
對于chrome,“衣服”和“T恤”都是utf-8編碼;
對于IE8,“衣服”和“T恤”都是gbk編碼。
這里實際上有兩個概念,一個是URI的編碼,一個是query string(即?后面的字符串)的編碼。
http request里的Content-Type設置:
http request是可以指定request的編碼信息的,如:
| 1 | Content-Type: application/x-www-form-urlencoded ; charset=UTF-8 |
form提交里的編碼設置:
form可以這樣子設置編碼:
| 1 | <form accept-charset="UTF-8" enctype="application/x-www-form-urlencoded;charset=UTF-8" |
但是實際上瀏覽器卻不一定會這么做……
比如,把頁頁編碼設置為gbk,再把form編碼設置為utf-8。
簡單測試,IE8仍然把form編碼為gbk,chrome雖然編碼為utf-8,但卻沒有在request里指明。。
當然,還有一個小技巧可以強行使用某種編碼,那就是我們先自己轉換好編碼,如:
| 1 | <form id="productSearchForm" action="http://127.0.0.1:8080/%A3%A4" |
不過,這樣意義不大。
web server是如何處理http request的編碼的?
只討論tomcat和jetty。
Tomcat對于URI的編碼,有兩個參數可以配置:
URIEncoding:這個可以強制指定用什么編碼處理URI,默認是ISO-8859-1;
useBodyEncodingForURI:這個是一個兼容性比較好的選項,如果在request指定了編碼,則采用request里指定的編碼。因此,設置了這個選項為true之后,在java代碼里就可以調用request.setCharacterEncoding來設置編碼了。
參考:http://wiki.apache.org/tomcat/FAQ/CharacterEncoding#Q2
Jetty只提供了對query string的編碼的指定方式,沒有提供對URI編碼的設置。因此,對于http:www.test.com/衣服/abcd/ 這樣的URI,jetty總是把“/衣服/abcd/”當做是utf-8編碼。
參考:http://wiki.eclipse.org/Jetty/Howto/International_Characters#International_characters_in_URLs
Spring mvc是如何處理編碼的:
spring mvc里提供了一個Filter:
| 1 2 3 4 5 6 7 8 9 10 11 12 | <filter>? ????<filter-name>encodingFilter</filter-name>? ????<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>? ????<init-param>? ????????<param-name>encoding</param-name>? ????????<param-value>UTF-8</param-value>? ????</init-param>? ????<init-param>? ????????<param-name>forceEncoding</param-name>? ????????<param-value>true</param-value>? ????</init-param>? </filter> |
到源代碼里看一下,可以發現,其實里面只是設置了request的encoding:
| 1 2 3 4 5 6 | if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {? ????request.setCharacterEncoding(this.encoding);? ????if (this.forceEncoding) {? ????????response.setCharacterEncoding(this.encoding);? ????}? } |
但是這個對request URI的編碼實際上是不起效的。
再看下源代碼里是通過j2ee里的request的API來得到的:
RequestParamMethodArgumentResolver類里:
| 1 2 3 4 5 6 7 8 | protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {? if (arg == null) {? String[] paramValues = webRequest.getParameterValues(name);? if (paramValues != null) {? arg = paramValues.length == 1 ? paramValues[0] : paramValues;? }? } |
最終實際上調用的是底層web server的request實現類,如tomcat的是org.apache.catalina.connector.RequestFacade,而web server到底是怎么處理請求的編碼的,參照上一小節。
http request 編碼自動識別
這個比較少用到,只有搜索引擎需要識別這種情況。因為搜索引擎需要處理在地址欄里直接輸入的字符串的編碼。
我測試了google, 百度,淘寶的搜索引擎,都能自動識別編碼。但是其它的一些非搜索引擎的應用,都不能自動識別編碼。
當然,程序員通常只保證在自家的網頁上,點擊的產生的http request能正確地被編碼,被識別。
那么,假定我們現在要做一個搜索類的功能,而且要能自動識別編碼,要怎么處理?
以tomcat為例,首先要配置URIEncoding為ISO-8859-1,這樣保證信息不丟失。
接著,寫一個filter,從request里拿到uri,再進行編碼識別,轉換。編碼識別的庫參考:
https://code.google.com/p/juniversalchardet/
還有另外一個思路,寫一個nginx的插件,先在nginx層識別,轉換好編碼。當然原理都是一樣的。
其它的一些東東:
中文域名的的編碼:
這東東應該沒多少人用吧。不過在jetty的網頁里看到了一些有用的信息:
http://wiki.eclipse.org/Jetty/Howto/International_Characters#International_characters_in_domain_names
瀏覽器實際上會用一個叫Punycode的編碼,把域名轉換成ascii-only的域名,再發起請求。
我測試了下,在chrome里輸入:導航.中國
實際是轉到下面這個域名去了:http://xn--fet810g.xn--fiqs8s
C/C++里的編碼:
我們在源文件test.c里寫上:
printf(“%s”, “中文”);
那么它在源文件test.c里是什么編碼?在編繹出來的test.out/test.exe里是什么編碼?運行時輸出到屏幕(shell/cmd)上又是什么編碼?
其實Python也有這種蛋疼的情況……
QQ在User-Agent里的信息:
用IE8測試時,很神奇地發現在request里發現了QQDownload的字樣,真是相當的令人無語。。
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; QQDownload 751)
總結:
想要實現 www.test.com/王小明/文章 ?這種url是很困難的,因為你不但要應對各種瀏覽器的編碼,還要處理各種web服務器的不同情況。
出現亂碼時,首先區分request傳過來的是什么編碼,然后response返回的是什么編碼,再逐一排查。
編碼問題可以說是程序員無法回避的問題,我相信即使是很有經驗的程序員,也會被坑。沒有辦法,現實世界就是這么坑爹,只能尋根溯源,一一排查了。
對于程序員通常,只要保證下面幾點就沒有問題了:
- 指定網頁的編碼;
- 配置web server對uri使用request里配置的編碼;
- 在ajax請求里先encodeURI();
- 在web server端對request設置utf-8編碼,對于response設置utf-8編碼。
總結
以上是生活随笔為你收集整理的http request乱码的真相的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: javascript库函数大全
- 下一篇: Chrome 开发工具之Network