URL中出現漢字的情況有兩種,一種是漢字出現在URL的路徑部分,一種是漢字出現在URL的參數部分。
第一種情況依賴於WEB伺服器和作業系統是否支援,但是在做WEB應用的時候應該避免這種做法。
第二種情況的時候必須採用編碼後傳參,接受時解碼的方式完成參數值的擷取。
參數可以通過表單提交,瀏覽器地址欄輸入,URL連結點擊。
下面的這些測試是在tomcat中進行的
先來看通過表單post提交的方式。
html檔案如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=GB2312" /><title>test encoding</title></head><body><form action="encode/enctest1.jsp" method="post"><input type="text" name="param" size="50"><br><input type="submit" value="submit"></form><br>GB2312頁面</body></html>
meta的設定為<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
通過Fiddler抓取的提交內容如:param=%D6%D0%CE%C4,
System.out.println(java.net.URLEncoder.encode("中文", "gb2312"));得到的結果為 %D6%D0%CE%C4
可以判斷瀏覽器將參數編碼為GB2312
在後端的代碼中通過下面的代碼能夠得到正確的資料
request.setCharacterEncoding("GB2312");
String param = request.getParameter("param");
注意setCharacterEncoding必須在從request取資料之前執行。
如果不通過setCharacterEncoding指定編碼,字元流預設以ISO-8859-1的編碼,需要按照下面的方式轉換。
String gbparam = new String(request.getParameter("param").getBytes("ISO-8859-1"),"GB2312");
meta的設定為<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
通過Fiddler抓取的提交內容如下:param=%E4%B8%AD%E6%96%87,
System.out.println(java.net.URLEncoder.encode("中文", "utf-8"));得到的結果為 %E4%B8%AD%E6%96%87
可以判斷瀏覽器將參數編碼為utf-8
在後端的代碼中通過下面的代碼能夠得到正確的資料
request.setCharacterEncoding("utf-8");
String param = request.getParameter("param");
在地址欄中直接輸入http://localhost:8080/prjWebSec/encode/enctest1.jsp?param=中文
通過Fiddler抓取的提交內容如下,在firefox和chrome都是用的utf-8編碼,[這個可能和OS以及伺服器的設定有關,通過System.out.println(System.getProperty("file.encoding"))得到的是utf8]
http://localhost:8080/prjWebSec/encode/enctest1.jsp?param=%E4%B8%AD%E6%96%87
在我的測試中,如果是jsp檔案post提交,不管meta的content設定的是gb2312還是utf-8,
提交的參數都是以utf-8編碼的。[這個可能和OS以及伺服器的設定有關,通過System.out.println(System.getProperty("file.encoding"))得到的是utf8]
如果在link中對參數進行encode如下
<a href="encode/enctest2.jsp?param=<%=URLEncoder.encode("中文","utf-8")%>">中文encoded</a><br>
瀏覽器中源碼如下:中文被utf-8編碼
<a href="encode/enctest2.jsp?param=%E4%B8%AD%E6%96%87">中文encoded</a><br>
點選連結後瀏覽器地址欄變成如下,瀏覽器顯示URL在地址欄中的時候做了一次自動解碼。 http://localhost:8080/prjWebSec/encode/enctest2.jsp?param=中文,
提交和傳輸到伺服器端的是%E4%B8%AD%E6%96%87。
後端取到參數後和post的處理原則一致。
如果在link中對參數進行兩次encode如下
<a href="encode/enctest2.jsp?param=<%=URLEncoder.encode
(URLEncoder.encode("中文","utf-8"),"UTF-8")%>">
瀏覽器中源碼如下:第一次中文被utf-8編碼成%E4%B8%AD%E6%96%87,第二次將%再編碼成%25,最後的結果如下
<a href="encode/enctest2.jsp?param=%25E4%25B8%25AD%25E6%2596%2587">中文double encoded</a>
點選連結後瀏覽器地址欄和源碼中的形式一致,瀏覽器只會對%xx自動解碼,對%xxxx不會自動解碼。
http://localhost:8080/prjWebSec/encode/enctest2.jsp?param=%25E4%25B8%25AD%25E6%2596%2587
後端取到參數後需要做一次utf-8的decode,request的編碼則無所謂,通過request.getParameter("param")
得到的值總是%E4%B8%AD%E6%96%87,下面的代碼能得到"中文"這個值.
String decodedparam = URLDecoder.decode(request.getParameter("param"),"UTF-8");
對於編碼的處理,如果處理多位元組的字元,一般來講最好將提交和接收端都設定成utf-8.
接收端編碼設定要在從request拿資料之前。在連結中一般也沒有必要對參數做兩次encoding。
參數提交和讀取的過程應該是瀏覽器對參數進行編碼,
編碼後的參數傳給web伺服器,到達伺服器後,伺服器會將得到的內容以一種編碼來表示。
以tomcat為例,這時候發生了(UTF-8 -->ISO-8859-1),這時候參數值是ISO-8859-1表示的。
在request.setCharacterEncoding("utf-8");
然後request.getParameter("param")的時候又轉回了utf-8。
如果直接通過stream的方式取到位元組流,內容如下
112 97 114 97 109 61 37 69 52 37 66 56 37 65 68 37 69 54 37 57 54 37 56 55
正好對應 param=%E4%B8%AD%E6%96%87,所以發生解碼的時候是在 request.getParameter的時候。
如果在這之前setCharacterEncoding設定了編碼,則用設定的編碼來解碼。否則用servlet伺服器的
預設編碼來解碼。取位元組流的代碼如下
ServletInputStream input = request.getInputStream();System.out.println(input);byte[] readBytes = new byte[10];while (true) {if (input.available() >= 10) {input.read(readBytes);for (byte b : readBytes) {System.out.print(b + " ");}} else {int readByte = 0; while((readByte = input.read()) !=-1){System.out.print(readByte + " "); }break;}}
通常一個做法是配置一個fittler,在filtter中對request設定編碼,如下:
<filter><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF8</param-value></init-param><init-param><param-name>forceEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>*.html</url-pattern></filter-mapping><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>*.jsp</url-pattern></filter-mapping>
對ISO-88591的補充
ISO-8859-1是單位元組編碼,它只是將位元組流一個位元組一個位元組的讀入,然後轉成ISO-8859-1編碼的字元,
編碼範圍0-255(x00-xFF),和其他所有編碼的單個位元組的編碼範圍一樣,
因為是單位元組的操作,所以不會對位元組流的內容做改變。
如果將一個多位元組編碼的檔案以ISO-8859-1讀入,轉成了ISO-8859-1的字串,
然後又以ISO-8859-1編碼輸出成另外一個檔案,儲存的檔案內容(二進位)並沒有變化。
ASCII也是單位元組的編碼,但是由於其編碼範圍0-127不能包含其他編碼的單位元組,超過127的
位元組被ascii轉成了?(x3F)字元,位元組的內容就變成了x3F。
多位元組之間如果用錯,則會發生錯位,如GB2312的每個漢字用兩個位元組表示,utf-8用3個位元組表示。
如果以GB2312去讀取一個utf8編碼的檔案,以"中文"的utf-8為例(%E4%B8%AD%E6%96%87),
GB2312會將%E4%B8作為一個字元,可能在gb231編碼中是別的字元,這時候位元組內容沒有被改變, 將%AD%E6作為一個字元,可能找不到對應的字元,如果找不到,則變成了?字元,位元組內容也變了,%96%87同理。
%E4%B8%AD%E6%96%87對應到gb2312為"涓??".再儲存則變成了%E4%B8%3F%3F了。
下面的代碼不會"破壞"檔案的內容。
public static void ioiso88591file(String fileName,String outfileName) throws Exception {File file = new File(fileName);FileInputStream fis = new FileInputStream(file);InputStreamReader fr = new InputStreamReader(fis,"iso-8859-1");BufferedReader br = new BufferedReader(fr);File outfile = new File(outfileName);FileOutputStream fos = new FileOutputStream(outfile);OutputStreamWriter ofr = new OutputStreamWriter(fos,"iso-8859-1");BufferedWriter bwr = new BufferedWriter(ofr);String line;while ((line=br.readLine()) != null){System.out.println(line);bwr.write(line);}br.close();fr.close();fis.close();bwr.close();ofr.close();fos.close();}
如果以下面的方式調用上面的方法,utf8.txt和utf8iso88591.txt的二進位內容是一模一樣的。
gb2312.txt和gb2312iso88591.txt的二進位內容也是一模一樣的。
ioiso88591file("C:/D/charset/utf8.txt","C:/D/charset/utf8iso88591.txt");
ioiso88591file("C:/D/charset/gb2312.txt","C:/D/charset/gb2312iso88591.txt");