從原理上搞定編碼(二)-- Web編碼


  周末宅在家里睡完覺就吃飯,吃完飯接着睡覺,這日子過的實在是沒勁啊。明明還有計划中的事情沒有做, 為什么就是不想去做呢,這樣的生活持續下去,必然會成為一個徹頭徹尾的loser。上一篇寫的 初識編碼 ,這一篇把web編碼寫出來和菜鳥們分享一下。圖片比較多,手機用戶不要看,流量沒了俺不負責。

一.html頁面編碼

  當瀏覽器請求一個靜態的html頁面時,服務器會將html頁面的字節流通過網絡傳輸給瀏覽器。瀏覽器再將字節流解碼成相應的html文本字符,然后將html元素渲染出來。在這個流程中瀏覽器有一個解碼html字節流的過程。瀏覽器如何判斷用什么編碼格式去解碼呢?在html頁面的head標簽中通常會包含一個指定頁面編碼的<meta/>標簽,如果指定的是utf-8編碼,瀏覽器就用utf-8解碼html頁面。問題是在瀏覽器解析<meta/>標簽獲得頁面編碼之前,瀏覽器是用什么編碼來解析<meta/>標簽的呢?因為html開頭都是字母,基本不會存在ASCII碼之外的字符,所以識別起來還是沒有難度的。

  如下是我的html代碼,第5行和第6行,會根據不同的測試條件分別打開注釋,具體打開哪個后邊會有提示。

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 3 <html xmlns="http://www.w3.org/1999/xhtml">
 4 <head>
 5 <!-- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -->
 6 <!-- <meta http-equiv="Content-Type" content="text/html; charset=GBK" /> -->
 7 <title>utf-8 html</title>
 8 </head>
 9 <body>
10 <p>Get 請求</p>
11 <form action="TestServlet" method="get">
12 <input type="text" value="你好" name="txtKey"/>
13 <input type="submit" value="提交"/>
14 </form>
15 <p>Post 請求</p>
16 <form action="TestServlet" method="post">
17 <input type="text" value="你好" name="txtKey"/>
18 <input type="submit" value="提交"/>
19 </form>
20 </body>
21 </html>

  演示過程用的環境是tomcat+servlet,瀏覽器是chrome。項目結構如下,其中有兩個html頁面,index.htm頁面用utf-8編碼存儲,lindexGBK.html頁面用GBK編碼存儲。兩個頁面的html代碼都是上邊給出的html代碼。

     

 

 實驗一: 在utf-8編碼的html頁面中指定不同的編碼格式

  啟動項目,用瀏覽器打開index.html的url,如左下圖所示,可以看出瀏覽器解析到<meta/>標簽中的utf-8編碼,並用utf-8解碼html頁面,頁面正確顯示。如果在<meta/>標簽中指定頁面編碼為GBK,那么瀏覽器就用GBK解碼,肯定就不能正確顯示頁面了,如右下圖所示,頁面中文出現亂碼。如果想避免亂碼的話,還是必須保證編解碼格式統一。

     

 

實驗二: 不指定頁面編碼的情況下測試不同的編碼頁面

  這次index.html和indexGBK.html都沒有指定頁面編碼。用瀏覽器打開index.html對應的url,如左下圖所示,可以看出瀏覽器用utf-8解碼html頁面,頁面正確顯示。用瀏覽器打開indexGBK.html對應的url,瀏覽器也是用utf-8解碼,肯定就不能正確顯示頁面了,如右下圖所示,頁面中文出現亂碼。因為在沒有指定頁面編碼的情況下,瀏覽器采用操作系統默認編碼,我的系統就是utf-8編碼,所以index.html頁面就碰巧解析正確了。

     

 

實驗三: 在一個html頁面中指定兩個不同的編碼

  用瀏覽器打開index.html對應的url,如左下圖所示,有兩個<meta/>標簽指定了不同的頁面編碼。當utf-8編碼在GBK編碼上面時,可以看出瀏覽器用utf-8解碼html頁面,頁面正確顯示。如右下圖所示,如果GBK編碼在utf-8編碼上面,那么瀏覽器就用GBK解碼,頁面中文出現亂碼。所以如果出現多個編碼以第一個出現的為主,當然這種情況是不可能出現的(出現了也是代碼問題),為什么會出現這種情況我覺得需要解釋一下,因為瀏覽器在解析<meta/>標簽時一旦發現頁面編碼,立馬就用這個編碼解析頁面。 

     

  html的form在沒有指定accept-charset屬性的情況下,html的表單提交采用的編碼也是指定的頁面編碼,如果沒有指定頁面編碼,也是采用操作系統的默認編碼。總之,html頁面的加載與表單提交采用的編碼格式是一樣的。因為瀏覽器認為你指定的頁面編碼就是html文件的實際編碼,所以不管是否亂碼,它都會照做。

  與html頁面編碼相關的還有js文件編碼,默認情況下,瀏覽器采用html的編碼格式去解碼js文件。如果js文件與html頁面的編碼不同怎么辦?script標簽有一個charset屬性,這個屬性就決定了瀏覽器去加載完js,解析字節流時用的編碼。

<script type="text/javascript" src="myscripts.js" charset="UTF-8"/>

  

二.Web后台編碼

  示例代碼是Java寫的,其他語言也是一個道理,不耽誤理解。請求后台無非是GET或POST方法,原理是一樣的,我只介紹POST。

html頁面是utf-8格式的, 頁面編碼也指定為utf-8。

實驗一:后台解碼

  后台響應代碼如下所示,頁面以post方式請求時會觸發doPost方法。

 1 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  2         System.out.println("START...");  3         String param=request.getParameter(PARAM);  4         System.out.println("原始參數");  5  System.out.println(param);  6         System.out.println("utf-8解碼");  7         System.out.println(new String(param.getBytes("ISO-8859-1"),"utf-8"));  8         System.out.println("GBK解碼");  9         System.out.println(new String(param.getBytes("ISO-8859-1"),"GBK")); 10         System.out.println("END..."); 11         System.out.println(""); 12     }

   先是直接獲取參數打印出來,然后又將獲取的參數用ISO-8859-1編碼,再用utf-8和GBK分別解碼並打印出來。點擊頁面的Post表單提交,控制台結果如下:

  html提交普通表單時,參數以字節流的方式傳輸到web服務器,web服務器解碼字節流得到參數。tomcat默認采用ISO-8859-1編解碼,所以直接獲取參數是亂碼,以ISO-8859-1編碼回原本的字節流再用utf-8重新解碼就可以得到正確的參數。

  為什么tomcat采用ISO-8859-1為默認的編碼格式,導致解碼流程這么復雜?在servlet規范中明確規定:如果客戶端請求沒有指定字符編碼,web容器用來創建請求讀取器和解析POST 數據的編碼必須是“ISO-8859-1”。為什么會有這么奇葩的規定呢?我猜可能是適應早期的瀏覽器吧,早期的瀏覽器大多采用ISO-8859-1為默認編碼,這個編碼的應用是非常廣泛的,覺得陌生或不可思議只是因為我們在中國。稍微捋一下,ASCII碼是1967年的,ISO-8859-1是1987年的,unicode是1994定稿,瀏覽器是在94年底95年初發布,所以估計早期的瀏覽器來不及用unicode作為默認編碼。我是用這么一套理論來說服自己的,對不對就不重要了,總之ISO-8859-1在很多國家是普遍采用的,在人家的圈子里做東西采用人家的編碼也是正常的。

  可以在獲取任何參數前調用 request.setCharacterEncoding("utf-8") ,即可用utf-8解碼參數,就不用那么麻煩了。其他語言在原理上也都是相同的。

 

實驗二:響應編碼

   響應代碼更改為如下代碼。

1 protected void doPost(HttpServletRequest request, 2             HttpServletResponse response) throws ServletException, IOException { 3         // response.setCharacterEncoding("utf-8"); ① 4         // response.setContentType("text/html; charset=UTF-8"); ② 5         // response.setHeader("Content-Type", "text/html; charset=UTF-8"); ③
6         PrintWriter writer = response.getWriter(); 7         writer.print("<html><head><meta charset=\"utf-8\"/></head><body><font color=\"red\">你好</font></body></html>"); 8  writer.close(); 9     }

  只在響應的html頁面中指定了頁面編碼,顯示是亂碼,如左下圖所示。因為在輸出時並沒有指定編碼格式,默認采用ISO-8859-1編碼,瀏覽器用utf-8解碼自然就是亂碼了。

  代碼中注釋掉的三行,是java常用的設置響應編碼方式。②跟③的效果是一樣一樣的,只討論①和②。將注釋①打開,響應如右下圖所示,指定了響應編碼為utf-8,瀏覽器用utf-8解碼正確顯示。

      
實驗三:setCharacterEncoding 與 setContentType 區別

  setCharacterEncoding只是把響應字符按指定格式編碼,setContentType除此之外,還在header里添加了contentType屬性。只把注釋②打開也可以正常顯示,在下圖右側ResponseHeaders中有Content-Type屬性 “text/html; charset=utf-8” 。當header中出現Content-Type屬性並出現指定編碼,瀏覽器將忽略html里<meta/>標簽中指定的編碼,以header中的編碼解碼html頁面。

   當setCharacterEncoding和setContentType同時出現時,后邊的編碼會覆蓋前邊的編碼。響應代碼更改為如下所示。

1 protected void doPost(HttpServletRequest request, 2             HttpServletResponse response) throws ServletException, IOException { 3         response.setContentType("text/html; charset=utf-8"); 4         response.setCharacterEncoding("GBK"); 5         PrintWriter writer = response.getWriter(); 6         writer.print("<html><head><meta charset=\"utf-8\"/></head><body><font color=\"red\">你好</font></body></html>"); 7  writer.close(); 8     } 

  響應編碼設為GBK,contentType屬性指定的編碼卻是utf-8,瀏覽器按照contentType指定的編碼來解碼豈不是亂碼?如下圖所示,答案是否定的,真實的contentType是“text/html; charset=GBK”,這是為什么,明明設置的“text/html; charset=utf-8”。因為contentType包含的其實是兩部分,一部分是text/html,第二部分是charset=utf-8。設置了contentType之后,這兩部分是分別存儲的,setCharacterEncoding會把第二部分編碼的值覆蓋掉。這時contentType就變成了“text/html; charset=GBK”。

 

 實驗四:瀏覽器對動態頁面與靜態頁面在默認編碼上的區別

  前邊說過了,html靜態頁面在沒有指定頁面編碼的時候是按照系統默認編碼解碼的。響應代碼更改如下所示。

1 protected void doPost(HttpServletRequest request, 2             HttpServletResponse response) throws ServletException, IOException { 3         response.setCharacterEncoding("utf-8"); 4         PrintWriter writer = response.getWriter(); 5         writer.print("<html><head></head><body><font color=\"red\">你好</font></body></html>"); 6  writer.close(); 7     }  

  沒有任何地方指定頁面編碼。按照靜態html的規律,瀏覽器采用我的系統默認編碼utf-8解碼,不會出現亂碼。但是實際情況並不是這樣,如左下圖所示,亂碼還是出現了,因為瀏覽器使用的GBK去解碼。這是為什么呢?打開chrome的設置-》顯示高級設置-》自定義字體-》出現右下圖所示的窗口,可以看到chrome的默認編碼是GBK,也就說如果動態頁面沒有指定頁面編碼將會按照瀏覽器的默認編碼來解碼。為了驗證我們的猜測,將chrome編碼設置為utf-8,重新請求一遍,果然頁面正確顯示。這只是我的猜測,具體對錯未知,但是未指定頁面編碼的情況是不多見的,大家在編碼過程中一定要指定頁面編碼為頁面實際的存儲編碼。

     

下篇文章介紹一下應用程序交互時的字節流編碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM