引言:
在進行Web開始時。亂碼是我們最常常遇到也是最主要的問題。有經驗的程序員非常easy能解決,剛開始學習的人則easy被泥潭困住。
並且非常多時候。我們即使攻克了亂碼問題也是不明就里。往往雲里霧里。
事實上亂碼問題非常easy,就是client和server使用了不一樣的字符集導致的。也就是我們發送文件時用的字符編碼和解析文件的編碼不一致。所以僅僅要搞清楚了我們的文件是怎么被編碼和解碼的解決亂碼就非常easy了。分析亂碼,我們從請求亂碼和響應亂碼來分析,請求亂碼又須要依據GET和POST來單獨分析。
請求亂碼——GET
請求的編碼是由瀏覽器發出的。使用GET方法請求server信息時。依據HTTP協議規定,Request包是沒有請求體的(也就是Request Body不存在)。所以我們僅僅能把請求參數放在URL中。因此使用GET方式與server通信,編碼方面我們關心的重點是瀏覽器對URL的編碼方式,和server對URL的解碼過程。
關於URL
URL是我們常常接觸並不是常簡單的一種技術,URL技術簡單到它事實上就是一個字符串。
實際上URL的結構是非常復雜的,僅僅只是通常上使用方法比較簡單而已。關於URL的具體介紹能夠參考以下的文章:
URL的規范定義在RFC 1738文檔中。通過URL我們能夠獲得通信協議、主機域名、處理port、應用路徑、路徑參數、查詢參數、頁面片段等信息。
比方:
http://user:pass@example.com/a/b;q=1/c?d=2;sessionid=qewfewrwer#2
依據上面的URL。我們能夠得到例如以下信息:
Part |
Data |
serverAPI |
Scheme |
http |
用req.getScheme |
user |
user |
囧,不知道 |
pass |
pass |
囧,不知道 |
host address |
example.com |
req.getServerName |
port |
80 |
req.getServerPort |
path |
/a/b;q=1/c |
req.getContextPath |
query parameters |
d=2;sessionid=qwefewrwer |
req.getQueryString |
fragement |
2 |
|
開發時,我們經經常使用到的就是path和query parameters。這兩個參數,剩下的參數使用的比較少。只是在RESTful API中,像路徑參數的信息可能會用到。fragement用來在頁面內進行錨點定位。(已經非經常見了)
瀏覽器對Path部分的編碼
path信息被用來匹配處理路徑。一般設計上非常少在path中包含中文參數。RFC文檔對於path的編碼也沒有明白規定。可是據其它文章的介紹。瀏覽器對Path的編碼一般都會採用UTF-8編碼,最新的URI標准已經定義了URI的編碼採用UTF-8編碼。
定義:簡單說path部分就是應用路徑部分,就是URL去掉協議、域名、port和查詢信息剩下的部分。
server對Path部分的解碼:(三種方案)
通常上,我們的請求都會首先發給Web容器(以下以Tomcat為例),URL也會被Web容器解碼,對於Tomcat容器來說,我們能夠在conf/server.xml的connector標簽中添加URL解碼參數,默認容器對URL的使用ISO-8859-1解碼。
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
上面的是Tomcat的默認設定,能夠給標簽加入URIEncoding屬性來指定URL的解碼方案。(PS:標簽寫法是URI不是URL)
假設不想使用這樣的硬解碼方案。還能夠指定還有一個屬性:useBodyEncodingForURI,這個屬性用來告訴Web容器。假設request指定了解碼方案,則使用request.setCharacterEncoding指定的編碼來解碼URL。
另外一種方案沒有經過測試。假設有須要能夠嘗試下。
具體資料能夠參考以下的Tomcat官方文檔:
http://wiki.apache.org/tomcat/FAQ/CharacterEncoding#Q2
此外。假設不想改動容器的全局配置。畢竟有時候容器里可能不止我們一個應用,那么我們還能夠採用以下的做法來提取參數:
String path = req.getServerPath();//自己手動提取,不適合配合框架 path = new String(path.getBytes(“ISO8859-1”,”UTF-8”));//又一次拼裝
上面的做法。我們要確定Web容器對URL的解碼用的是ISO8859-1,由於不排除其它人改動了容器配置或容器配置本身比較奇葩的可能。
瀏覽器對QueryParameter的編碼
查詢參數和Path是不一樣的,缺少查詢參數,web容器是能夠定位到我們的處理程序的,可是缺少path就不行。
另外,path和查詢參數的保留字符是不一樣的。
定義:簡單來說查詢參數就是path后面緊跟的?后面的部分,用&來連接各個查詢參數。
因為Path和查詢參數的不同,有些瀏覽器對查詢參數的編碼和path部分的編碼是不一致的。
詳細使用怎么編碼的比較混亂。能夠參考下以下的文章:
依據上面的文章總結的規律:
(1)Path部分或者說除查詢參數外的URL部分。各瀏覽器用UTF-8編碼;
(2)查詢參數,各瀏覽器依據操作系統編碼決定;
上面的文章比較老了,規律可能不有用了,可是也能說明一定問題。對於某些文章說的,查詢參數會依據頁面編碼來決定,我沒有做實驗。可是這樣的結論肯定是片面的。原因例如以下:
頁面的meta參數是用來向瀏覽器說明頁面編碼的,其次。在使用POST Method發送數據的時候,瀏覽器會依據meta的編碼來編碼Request Body。而Get方式。我們在沒有頁面的時候也能夠發起。所以瀏覽器根本找不到Meta標簽,也就沒法參考頁面編碼。
瀏覽器對查詢參數究竟使用哪種方式編碼的,我沒有找到專業、權威、可信的答案,可是我覺得這個還是詳細情況詳細分析。做個小實驗即可了。
畢竟時代在進步。廠商們統一使用UTF-8編碼的可能性比較大。並且后面有不依賴瀏覽器編碼的解決方式。
server對QueryParameter的解碼
查詢參數也是URL的一部分。所以Web容器對查詢參數的解碼比較明智,解碼和path使用的是一樣的方案的編碼,所以解決方式也是一樣的。
出現亂碼:
在處理查詢參數時,我們經常使用req.getParameters();來獲取某個參數。這種方法背后非常少有人關心它的工作原理。並且也不是必需。
這一部分是最easy出現亂碼的,畢竟它里面的參數可能是用戶輸入的,並非我們設計的。
在GET方式下,出現這樣的亂碼不要慌張,首先我們要分析出,瀏覽器對查詢參數究竟採用了哪種編碼。
方法簡單(也復雜),chrome下F12打開開發人員工具
找到network標簽,能夠看到Request URL中顯示的是k= %E4%B8%AD%E5%9B%BD,把%去掉,能夠得到6個16進制數,百度下unicode碼表。能夠看到他們正好是“中”和“國”的unicode編碼。所以能夠推測瀏覽器使用的是UTF-8編碼。這樣的推斷方式須要對字符編碼比較熟悉。只是也不算非常難,找點字符編碼的文章學學非常easy就能看出規律來。
PS:不要通過瀏覽器的地址欄看URL編碼,非常多瀏覽器的地址欄會對URL解碼顯示。
之后,server端,首先確定下。你的Web容器對URL使用的解碼方案,然后對應的選擇String(param.getBytes(“ISO8895-1”,”UTF-8”))或者是useBodyEncodingForURI、URIEncoding方案就好了。
總結:
使用GET方式出現亂碼時,最基本的是找出瀏覽器對URL的編碼方式,假設使用JS編程時。在瀏覽器能夠使用encodeURIComponent函數對中文參數進行編碼后再拼裝參數。Java端使用URLDecoder.decode方法解碼。JS端要進行兩次編碼,否則第一次的URL編碼會被Web容器解碼,獲取的參數仍有可能是亂碼。能夠參考: