Java Web亂碼分析及解決方案(一)——GET請求亂碼
引言:
在進行Web開始時,亂碼是我們最經常遇到也是最基本的問題,有經驗的程式猿很容易能解決,初學者則容易被泥潭困住。而且很多時候,我們即使解決了亂碼問題也是不明就裡,往往雲裡霧裡。
其實亂碼問題很簡單,就是客戶端和伺服器使用了不一樣的字符集導致的。也就是我們傳送檔案時用的字元編碼和解析檔案的編碼不一致。所以只要搞清楚了我們的檔案是怎麼被編碼和解碼的解決亂碼就很簡單了。分析亂碼,我們從請求亂碼和響應亂碼來分析,請求亂碼又需要根據GET和POST來單獨分析。
請求亂碼——GET
請求的編碼是由瀏覽器發出的,使用GET方法請求伺服器資訊時,根據HTTP協議規定,Request包是沒有請求體的(也就是Request
Body不存在)
關於URL
URL是我們經常接觸並非常簡單的一種技術,URL技術簡單到它其實就是一個字串。實際上URL的結構是很複雜的,只不過通常上用法比較簡單而已。關於URL的詳細介紹可以參考下面的文章:
URL的規範定義在RFC 1738文件中。通過URL我們可以獲得通訊協議、主機域名、處理埠、應用路徑、路徑引數、查詢引數、頁面片段等資訊。比如:
http://user:[email protected]/a/b;q=1/c?d=2;sessionid=qewfewrwer#2
根據上面的URL,我們可以得到如下資訊:
Part |
Data |
伺服器API |
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去掉協議、域名、埠和查詢資訊剩下的部分。
伺服器對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官方文件:
此外,如果不想修改容器的全域性配置,畢竟有時候容器裡可能不止我們一個應用,那麼我們還可以採用下面的做法來提取引數:
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編碼的可能性比較大。而且後面有不依賴瀏覽器編碼的解決方案。
伺服器對QueryParameter的解碼
查詢引數也是URL的一部分,所以Web容器對查詢引數的解碼比較明智,解碼和path使用的是一樣的方案的編碼,所以解決方案也是一樣的。
出現亂碼:
在處理查詢引數時,我們常用req.getParameters();來獲取某個引數,這個方法背後很少有人關心它的工作原理,而且也沒必要。這一部分是最容易出現亂碼的,畢竟它裡面的引數可能是使用者輸入的,並不是我們設計的。在GET方式下,出現這種亂碼不要慌張,首先我們要分析出,瀏覽器對查詢引數到底採用了哪種編碼。方法簡單(也複雜),chrome下F12開啟開發者工具
找到network標籤,可以看到Request URL中顯示的是k= %E4%B8%AD%E5%9B%BD,把%去掉,可以得到6個16進位制數,百度下unicode碼錶,可以看到他們正好是“中”和“國”的unicode編碼。所以可以猜測瀏覽器使用的是UTF-8編碼。這種判斷方式需要對字元編碼比較熟悉。不過也不算很難,找點字元編碼的文章學學很容易就能看出規律來。
PS:不要通過瀏覽器的位址列看URL編碼,很多瀏覽器的位址列會對URL解碼顯示。
之後,伺服器端,首先確定下,你的Web容器對URL使用的解碼方案,然後相應的選擇String(param.getBytes(“ISO8895-1”,”UTF-8”))或者是useBodyEncodingForURI、URIEncoding方案就好了。
總結:
使用GET方式出現亂碼時,最主要的是找出瀏覽器對URL的編碼方式,如果使用JS程式設計時,在瀏覽器可以使用encodeURIComponent函式對中文引數進行編碼後再拼裝引數。Java端使用URLDecoder.decode方法解碼。JS端要進行兩次編碼,否則第一次的URL編碼會被Web容器解碼,獲取的引數仍有可能是亂碼。可以參考: