由 XMLHttpRequest 送出中文資料給 Tomcat


由 XMLHttpRequest 送出中文資料給 Tomcat

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: 國立中興大學資管系呂瑞麟

請勿轉貼
看其他教材 在嘗試 Dojo 和 Yahoo! User Interface Library (YUI) 的時候,才突然發現之前的 AJAX 範例都沒有從 XMLHttpRequest 傳送中文的資料給遠端的 Tomcat servlet。 而發現的原因在於為 Dojo 和 YUI 所寫的程式中,其中一個範例是由 browser 送出 一個中文的(Big5)欄位資料到遠端的 Tomcat servlet 處理,而處理的結果 卻是亂碼。這樣子一來就打亂了我的休假,連續兩個晚上測到凌晨一、兩點 (對一個老人來說,是很大的折磨)。這兩天找了不少資料,目前以
的文章比較有參考價值。尤其,OC'onner 的文章直接說明目前的傳送以及 encoding 的方式並沒有標準,完全要看你所使用的工具而定。看了這個說明之後,測試的方向 就比較明確一些。大概需要檢查的細節有:
  • 由 XMLHttpRequest 送出去的 URL:如果 URL 包含 Big5 的資料,XMLHttpRequest 會不會 encode,而且又是以何種方式 encode?
  • server 是如何解讀收到的 URL:目前知道 Tomcat (我的主要 servlet 開發 平台)是採用 ISO-8859-1 編碼方式來接收,因此解碼的方式可以用 ISO-8859-1 來進行。但是,如果 browser 送過來的資料是以 UTF-8 編碼,那要怎麼辦呢?
目前,已經找出 Microsoft IE 6.x/7.x 以及 Firefox 1.5.x 或者新版本的 解決方案了。測試出來的情形整理如下:
  • IE 6.x/7.x:
    • 若 Javascript 對中文資料 encodeURIComponent(),則 servlet 需要對接收到的資料先以 ISO-8859-1 解出來之後,以 UTF-8 編碼出來。
    • 若 Javascript 不對中文資料進行 encodeURIComponent(),則 servlet 需要對接收到的資料先以 ISO-8859-1 解出來之後,以 Big5 編碼出來。
  • Firefox 1.5.x 或者新版本:不論 browser 是否對中文資料 encodeURIComponent(),servlet 需要對接收到的資料 先以 ISO-8859-1 解出來之後,以 UTF-8 編碼出來。
從剛剛的討論結果可以知道:如果希望寫一個程式能夠同時使用在 IE 和 Firefox 的環境下正確的執行,在 XMLHttpRequest 送出要求之前,最好先對中文 的資料進行編碼(也就是使用 encodeURIComponent 對傳送的資料先 進行編碼),而送到 Tomcat 之後, servlet 必須先以 ISO-8859-1 的方式接收之後, 再依據 UTF-8 的方式解碼使資料回到中文的格式,你可以 連結 測試網頁 試試看。 有了以上的認知,AJAX 網頁的程式部份,只有小部份需要修改。以測試網頁 為例,因為會被傳送的資料只有一個,也就是名為 elements[0] 的文字欄位, 所以我們只需要再傳送出 url 之前,將該部分的資料利用 encodeURIComponent 進行編碼;程式碼如下所示,其中需要編碼的部份以綠色字體標示出來。 請注意這樣的設計方式,也可以處理英文,所以不必為了英文,另外又寫一段程式。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Big5">
<script type="text/javascript" language="javascript">
  function makeRequest(url) {
    var http_request = false;

    if (window.XMLHttpRequest) { // Mozilla, Safari,...
      http_request = new XMLHttpRequest();
    } else if (window.ActiveXObject) { // IE
      try {
        http_request = new ActiveXObject("Msxml2.XMLHTTP");
      } catch (e) {
        try {
          http_request = new ActiveXObject("Microsoft.XMLHTTP");
        } catch (e) {}
      }
    }

    if (!http_request) {
      alert('Giving up :( Cannot create an XMLHTTP instance');
      return false;
    }
    // 定義事件處理函數為 alterContents()
    http_request.onreadystatechange = function() { 
                                      alertContents(http_request); };

    // IE 6.x 和 Firefox 1.5.x 皆要 encodeURI()
    
    url = url + "?name=" +
          encodeURIComponent(document.myform.elements[0].value);
    
    http_request.open('GET', url, true);
    http_request.send(null);
  }

  function alertContents(http_request) {
    if (http_request.readyState == 4) {
      if (http_request.status == 200) {
        var mesg = http_request.responseText;
        alert(mesg);
      } else {
        alert('There was a problem with the request.');
      }
    }
  }
</script>
</head>

<body>
<form name="myform">
姓名: <input type="text" name="name"></input>
<input type="button" value="OK" onClick="makeRequest('適當的 servlet URL');"></input>
</form>
</body>
</html>
遠端的 servlet 程式碼如下所示。由於傳送的參數名稱為 name,而收到 name 的值 由於被 Tomcat 當作 ISO-8859-1 的資料處理,所以我們需要先以 ISO-8859-1 的方式解讀該值,並形成位元組陣列;由於之前中文的資料以 encodeURIComponent() 編碼過(也就是 UTF-8 的資料),所以在下列程式的綠色字體部份,我們再以 UTF-8 的方式將其還原回來。
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class Hello extends HttpServlet {
  public void doGet(HttpServletRequest req, HttpServletResponse res)
         throws ServletException, IOException
  {
    res.setContentType("text/plain;charset=UTF-8");
    PrintWriter output = res.getWriter();

    String msg = req.getParameter("name");
    
    msg = new String(msg.getBytes("ISO-8859-1"), "UTF-8");
    

    StringBuffer buf = new StringBuffer();
    buf.append("Hello " + msg);
    buf.append(" , 歡迎使用 AJAX");
    output.println(buf.toString());
    output.close();
  }
}
為了讓讀者更清楚了解編碼和解碼的過程,整個過程以下圖呈現:
瀏覽器中的中文 --經過 encodeURIComponent() 轉碼(這部份的程式碼是為了執行跟所有瀏覽器預設的動作)
               --經過 Tomcat 編碼 --> ISO-8859-1 (Tomcat 無法知道客戶端送過來的編碼為何,因此它會先 decodeURI() 然後把資料以 ISO-8859-1 編碼)
               --經過 servlet 的 getBytes("ISO-8859-1") 以 ISO8859-1 的方式解讀 URL 的資料 
               --再經過 new String(..., "需要的編碼") 將資料從 ISO-8859-1 的編碼轉換成需要的編碼;在這個範例中,由於程式碼中的中文是以 
                 UTF-8 編碼(我在最近一次升級伺服器之後,將系統預設編碼設定成 UTF-8),所以回傳的資料編碼為 UTF-8(程式碼中的 
                 setContentType() 可以看得出來)。 
個人心得: 雖然非常多的專家都認為應該將 Linux 的預設編碼設定為 UTF-8 (也就是 zh_TW.UTF-8),但是經過一段時間的使用之後,我還真的 有點後悔,原因很簡單:我的客戶端作業系統是 XP Professional 繁體中文版, 而且之前開發的一些程式(內含中文)都是以 XP 的預設編碼來做的,也就是 大五碼(Big5),在這樣的環境下,不斷的注意編碼的一致性實在很痛苦,所以 我個人建議還是將 Linux 的預設編碼設定為 Big5 (也就是 zh_TW.Big5) 會比較方便。

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



沒有留言:

張貼留言