http request亂碼的真相
當然,終極原因http協議裏沒有規定request一定要指定編碼,導致瀏覽器,web服務器都各搞一套……
下麵一一理清。
首先,從瀏覽器端看下有多少種情況:
1.在瀏覽器的地址欄,或者搜索框裏輸入地址:https://www.test.com/衣服/search?keyword=T恤
2.在一個指定了編碼的網頁中,提交一個form,如:
<html> <head> <meta charset="gbk"> </head> <body> <p>你好</p> <form action="https://127.0.0.1:8080/test.html" method="post"> <input name="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為例。
對於在瀏覽器的地址欄支持輸入的地址,各種瀏覽器是如何處理的,可以參考這個:
https://www.ruanyifeng.com/blog/2010/02/url_encoding.html
也可以自己簡單的測試,在linux下執行:
nc -l 8080
接著在瀏覽器裏直接訪問 https://localhost:8080/衣服/search?keyword=T恤,
然後在就可以看到nc的輸出結果了。當然,瀏覽器的debug工具也可以很方法地看到編碼的結果,不過用nc,就不用自己跑一個web服務器了,非常方便。
另外那個keyword=T恤,也是有意選擇的,這樣可以很方便地看到編碼的結果。恤的gbk編碼是兩個byte,utf-8編碼是3個byte,也很容易區別到底是什麼編碼。
簡單地總結下對於瀏覽器地址欄裏直接訪問:https://www.test.com/衣服/search?keyword=T恤 的編碼情況:
對於chrome,“衣服”和“T恤”都是utf-8編碼;
對於IE8,“衣服”和“T恤”都是gbk編碼。
這裏實際上有兩個概念,一個是URI的編碼,一個是query string(即?後麵的字符串)的編碼。
http request裏的Content-Type設置:
http request是可以指定request的編碼信息的,如:
Content-Type: application/x-www-form-urlencoded ; charset=UTF-8
form提交裏的編碼設置:
form可以這樣子設置編碼:
<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裏指明。。
當然,還有一個小技巧可以強行使用某種編碼,那就是我們先自己轉換好編碼,如:
<form action="https://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來設置編碼了。
參考:https://wiki.apache.org/tomcat/FAQ/CharacterEncoding
Jetty隻提供了對query string的編碼的指定方式,沒有提供對URI編碼的設置。因此,對於http:www.test.com/衣服/abcd/ 這樣的URI,jetty總是把“/衣服/abcd/”當做是utf-8編碼。
參考:https://wiki.eclipse.org/Jetty/Howto/International_Characters
Spring mvc是如何處理編碼的:
spring mvc裏提供了一個Filter:
<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:
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類裏:
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的網頁裏看到了一些有用的信息:
https://wiki.eclipse.org/Jetty/Howto/International_Characters
瀏覽器實際上會用一個叫Punycode的編碼,把域名轉換成ascii-only的域名,再發起請求。
我測試了下,在chrome裏輸入:導航.中國
實際是轉到下麵這個域名去了:https://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編碼。
最後更新:2017-04-03 14:54:32