細談 URL 編碼


細談 URL 編碼

The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟

請勿轉貼
看其他教材

在這十幾年的教學生涯中,不論在教學或是指導學生做專題的過程中,總是 不斷的跟學生解釋為什麼在網頁上輸入中文(Big5 或者 UTF-8),但是處理 的結果(可能網頁的呈現或者資料庫的存取)卻老是會出現亂碼。因為我有 寫網頁教材的習慣,所以總是寫一寫就叫學生自己去看,而曾經探討過這個 議題的有:資料處理入門(這是使用 JDK 1.2 版以前所需要解決的方式)、Java Servlet 入門(這是第一次處理網頁的中文資料的解決方式)、MySQL Server 簡介(這是針對將 Big5 資料存入 MySQL,並將 MySQL 的資料正確轉回 Big5 的 解決方式)、以及 由 XMLHttpRequest 送出中文資料給 Tomcat(這是針對 XMLHttpRequest 傳送中文給 Tomcat 的解決方式)。 最近有一次跟學生聊天,他們提到網頁內容的編碼方式是利用 UTF-8 的方式, 處理的結果卻不盡相同,於是讓我想要重新做一次(儘可能)完整的探討,來細細的說明 為什麼網頁中文資料的傳送有那麼樣的"曲折離奇"!
在參考了幾篇文章之後,我列出最有幫助的兩篇:
首先我們來看幾件事實:第一、雖然網頁的內容允許各式各樣的編碼(我們在之後 的範例中使用兩種常見的中文編碼:Big5 和 UTF-8),但是 URL 的內容(依據 RFC 1738 的規定)只能出現部分的 ASCII 碼;第二、目前並沒有任何標準來規範 網頁資料與後端伺服器之間應該如何的編碼,而且這些資料在各式的程式介面之間 被轉換的方式也沒有一定的規範(也就是說,開發人員只知道有這一項事實,但是 解決的方式卻會依據作業系統、預設語系、瀏覽器的不同而有不同的結果),所以 解決的方法必須依環境而定,而這可能正是考驗程式設計人員能力的時候。

知道了這些事實之後,我們先把我們的測試環境描述一下:在瀏覽器的部分,我們 使用 Firefox 3.x 和 IE 8.x;後端伺服器的部分,我們使用 Tomcat 5.5.x, 後端程式是 Java 的 servlet。如果你使用的環境不同,應該也可以用類似的方法 來測試,並找尋你的解決方法。[註:2014 年 10 月將系統的 Tomcat 升級到 7.0.x 版,由於其中一個測試平台是在 Debian Wheezy 上的,而它的 Tomcat 設定是將編碼預設為 UTF-8,這可以從 server.xml 中是否包含下列敘述而定。想要測試這篇文章的範例,建議將 URIEncoding="UTF-8" 移除,並重新啟動 Tomcat,因為從 Apache 下載的 Tomcat 7/8 都沒有這個設定。]

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               URIEncoding="UTF-8"
               redirectPort="8443" />


網頁的內容,我們採用兩種主要的編碼:Big5 和 UTF-8。由於我習慣上都是 使用 Microsoft Windows 的記事本(也就是 notepad.exe),所以我要提醒一下 讀者:使用 notepad 來儲存網頁內容的時候,預設的編碼方式就是 Big5; 如果你想產生 UTF-8 的內容,請記得在儲存網頁的時候,選擇 UTF-8 的 編碼(如下圖所示)。如果你使用的是其他的文字編輯器,請再測試前先確認 網頁儲存時所使用的編碼是什麼,然後做必要的調整。

測試用的網頁還蠻簡單的,以下我只提供第一個版本的網頁內容,其他的,讀者 可以自己去閱讀網頁的原始碼。原始碼如下:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=Big5">
<title>測試劉覽器 URL 編碼</title>
</head>
<body>
<h2>測試劉覽器 URL 編碼</h2>
<h3>HTML 以 Big5 編碼,呼叫 EchoBig5</h3>
<form method="GET" action="/xml/servlet/EchoBig5">
<input type="text" value="老呂" name="data">
<input type="submit" value="確定">
</form>
</body>
</html>
讀者可以到 test1Big5 來看它的畫面。在這個網頁中,由於包含中文的內容,而又由於 notepad 預設的 文字編碼是 Big5,所以網頁中包含一個 <meta> 標籤,其中指明網頁的 內容是以 Big5 的方式編碼,因此瀏覽器(包含 Firefox 和 IE)都能正確的顯示 網頁資料。另一個要注意的地方在 <form> 標籤;我們在 <form> 中 為 method 定義的屬性值是 GET,這樣子一來,我們也可以從瀏覽器的 URL 欄位 看出究竟瀏覽器送出的 URL 長什麼樣子。 請在測試網頁上點一下"確定"按鈕,然後你應該會看到如下的畫面:

如果你使用的是 IE,也會得到同樣的結果。首先,請注意畫面上方的紅色框框; 我們在網頁中輸入的資料是"老呂",而在 URL 欄位中出現的卻是 %A6%D1%A7f。 從"老呂"變成 %A6%D1%A7f,這就是 URL 編碼的工作。在之前我們曾經說過, 雖然網頁允許各式各樣的編碼,但是依據標準,URL 中只能出現部分的 ASCII 碼; 這些 ASCII 碼包含 a-z、A-Z、0-9、以及一些特殊字元和保留字元。"老呂" 絕對不是 ASCII 碼的任何部分,所以瀏覽器必須對"老呂"進行編碼。一般來說, 瀏覽器會根據網頁所指定的編碼(以本例來說就是 Big5)對"老呂"編碼,而 編碼的結果就是 A6 D1 A7 66 這四個 16 進位(hex number)的數字(其實, 這也是畫面中間紅色框框內的值);然後, 瀏覽器會在每一個數字之前加上 % 符號,所以就形成了 %A6%D1%A7%66; 又因為 16 進位數字的 66 就是 f,因此編碼結果也可以是 %A6%D1%A7f
讀者也可以使用一般的 16 進位編輯器來確認"老呂"就是由 A6 D1 A7 66; 我們使用的是 notepad++ 以及其 16 進位編輯器的外掛程式,顯示"老呂"畫面(highlighted) 如下:
一般畫面16 進位畫面

到目前為止,我們已經清楚了瀏覽器是如何進行 URL 編碼,可是編碼後的資料 是如何被伺服器所處理呢?就之前所說的事實,這部份是沒有編準的,所以我們 就以我們所使用的伺服器 Tomcat 5.5.x 版來說明。在預設的情形下,Tomcat 無法以 Big5 的方式來解讀從瀏覽器傳過來的 URL,而是以 ISO-8859-1 的編碼 方式來解讀。為了確認這個說法,我們設計了以下的程式,也就是剛剛 <form> 標籤中的 EchoBig5:
01  import javax.servlet.*;
02  import javax.servlet.http.*;
03  import java.io.*;
04  
05  public class EchoBig5 extends HttpServlet {
06    public void doGet(HttpServletRequest req, HttpServletResponse res)
07           throws ServletException, IOException {
08      PrintWriter output;
09  
10      res.setContentType("text/html;charset=Big5");
11      output = res.getWriter();
12  
13      StringBuffer buf = new StringBuffer();
14      buf.append("<html><head><title>\n");
15      buf.append("Echo Big5\n");
16      buf.append("</title></head><body>\n");
17  
18      String data = req.getParameter("data");
19      String orig = data.length() + " ";
20      for(int i=0; i<data.length(); i++) {
21        int ch = (int)data.charAt(i);
22        orig = orig + "%" + Integer.toHexString(ch);
23      }
24      String out1 = new String(data.getBytes("ISO-8859-1"), "Big5");
25  
26      buf.append("<h1>String: " + data + "</h1>\n");
27      buf.append("<h1>Hex Numbers: " + orig + "</h1>\n");
28      buf.append("<h1>Big5 Encoded: " + out1 + "</h1>\n");
29      buf.append("</body></html>\n");
30      output.println(buf.toString());
31  
32      output.close();
33    }
34  }
EchoBig5 類別是一個 Java servlet,不清楚的讀者可以參考 Java Servlet 入門。 這個程式有幾個地方稍微說明一下:第 10 行中,servlet 告訴瀏覽器即將回傳的 資料編碼方式是 Big5;第 18 行是把瀏覽器傳送過來的資料直接以字串的方式 放到 data 變數中;第 19-23 行是把 data 內的資料一次以一個字元的方式取出來, 並顯示每一個字元的 16 進位值,也就是 orig;在第 19 行,orig 的第一個資料 代表 data 的長度。 由於 Tomcat 對於傳送進來的 URL 資料是以 ISO-8859-1 的方式解讀,因此 把 data 直接輸出(或者顯示)的結果就是如執行畫面中第一行的輸出( ISO-8859-1 是一個支援西歐語系的編碼,它是無法正確顯示中文)。 畫面中,第二行顯示從瀏覽器總共傳送了四個字元,而每一個字元的 16 進位值 分別是 A6 D1 A7 66;而程式的第 22 行將輸出的數字之前加上了 % 符號, 這也就形成了 %A6%D1%A7%66 的結果。
由於 Tomcat 將 A6 D1 A7 66 視為 ISO-8859-1 碼,因此要能夠正確的 顯示其 Big5 的內容,我們必須藉助 Java 的 String 轉碼功能;在第 24 行的 地方,程式先把 data 字串依據 ISO-8859-1 碼的方式取出並形成一個 字元陣列,然後利用該陣列來建構一個字串物件,而轉碼的方式是把其 視為 Big5,而結果就是 out1 字串,該字串在第 28 行中輸出,也就是 執行畫面中第三行的結果。
練習題: 既然 Tomcat 是使用 ISO-8859-1 的方式編、解碼,那麼 我們的網頁內容直接使用 ISO-8859-1 來寫,可不可以?
繼續閱讀:Part II


Written by: 國立中興大學資管系呂瑞麟

















3 則留言:

  1. 目前最新版的notepad++,版本號為6.2.2,已不支援Hex Edit外掛了…手動下載到plugin資料夾也沒用。

    回覆刪除
  2. 謝謝提供資訊,你可以試著下載舊版的。其實這份文件的主要目的在於概念的描述以及驗證方式,使用的工具只要能看得到 16 進位的值即可。

    回覆刪除