HTTP協議和瀏覽器是Web國際化的基礎,在進入Java服務器端之前,必須先對它們的編碼行爲有所瞭解。
HTTP協議是B/S體系結構應用程序的基礎,只有瞭解了HTTP協議,才能理解如何在B/S體系結構下實現應用程序的國際化。
1.HTTP請求
當用戶在瀏覽器的地址欄中輸入一個URL並按回車鍵之後,瀏覽器會向HTTP服務器發送HTTP請求。HTTP請求主要分爲“Get”和“Post”兩種方法。
2.採取“Get”方法的HTTP請求
“Get”請求的典型用途是從HTTP服務器獲取指定的資源,這樣的請求不包含請求體。在瀏覽器中輸入一個URL並按回車鍵後,瀏覽器就會生成這種類型的請求。HTTP服務器根據該請求所包含URL中的參數來動態產生響應內容,即“Get”請求的參數是URL的一部分。例如:
http://www.baidu.com/s?wd=Chinese
上述URL是一個使用百度搜索關鍵字“Chinese”的URL,參數“wd”包含在URL中,一起發送到HTTP服務器,參數的值是“Chinese”。當參數名和參數值都是ASCII字符時不會出現問題,但當參數名或參數值中包含非ASCII字符時就有可能出現問題。
由於URL通過網絡傳遞,因此,爲了保證信息的兼容性和通用性,當URL包含非 ASCII字符時,必須對其進行轉義。如果將上例中的參數值改爲“中文”,則URL變爲:
http://www.baidu.com/s?wd=中文
當在瀏覽器(我們使用的是Firefox2.0)的地址欄中輸入上述URL並按回車鍵後,可以看到瀏覽器會自動對URL進行轉義,得到的是:
http://www.baidu.com/s?wd=%D6%D0%CE%C4
可以看到“中文”已經被瀏覽器自動轉義成爲了%D6%D0%CE%C4,它們是漢字“中文”的GBK編碼對應的轉義形式。另外,不同的瀏覽器對URL進行轉義的行爲是不同的,具體內容請參閱6.1.2節的介紹。
當HTTP服務器收到這樣的請求時,必須先將轉義的字符解釋爲有效的字符,再對URL進行處理。但是,HTTP協議中並沒有指定使用何種編碼和字符集來解釋URL中的非ASCII字符(細節可參閱RFC2396,2.1節),因此,是否能成功解析就完全取決於URL中非ASCII內容的編碼是否與HTTP服務器的解析編碼一致。例如,如果我們希望在Google中也搜索“中文”,構造如下URL:
http://www.google.com/search?q=%D6%D0%CE%C4
在瀏覽器地址欄中輸入這個URL並按回車鍵後,會發現搜索結果頁面查詢的關鍵字並不是“中文”而是一個不能識別的亂碼。這是因爲Google的HTTP服務器使用UTF8編碼來解釋URL中的非ASCII字符。如果使用下面以UTF8編碼的URL就能得到正確的結果:
http://www.google.com/search?q=%E4%B8%AD%E6%96%87
請注意:Google在不同區域的服務器可能會使用不用的編碼方式來解析URL。例如www.google.cn可以正確解析:http://www.google.cn/search?q=%D6%D0%CE%C4;而www.google.com只能正確解析:http://www.google.com/search?q=%E4%B8%AD%E6%96%87。
而且,由於Google可以根據用戶瀏覽器的區域設置自動將用戶重定向到某個特定區域的服務器上,因此在Firefox中,如果瀏覽器的首選區域是zh-cn,那麼訪問如下url:http://www.google.com/search?q=%D6%D0%CE%C4會被自動重定向到http://www.google.cn /search?q=%D6%D0%CE%C4,因此,顯示的結果是正確的。
3.採取“Post”方法的HTTP請求
“Post”請求通常用來向HTTP服務器提交量比較大的數據(比如請求中包含許多參數或者文件上傳操作等),它與“Get”方法的主要區別在於請求的參數包含在消息體而非 URL中,服務器同樣需要獲得正確的編碼信息才能夠正確解析在消息體中的請求參數。在 “Post”方法的HTTP請求中,通常包含一個“Content-Type”消息頭指明該消息體的媒體類型和編碼,如“Text/XML; charset=gb2312”,指明該請求的消息體中包含的是純文本的XML類型的數據,字符編碼採用“gb2312”。
使用一些Firefox插件可以輔助開發人員分析請求的消息頭和消息體,較常用的有Firebug等。
4.HTTP響應
HTTP響應是HTTP服務器在接收請求之後向客戶端返回的信息。一個HTTP響應通常由狀態行、消息頭和消息體組成。HTTP響應消息的第一行是狀態行,表示服務器對請求的應答。常見的應答有:“200:OK”、“404:Not Found””、“500:Internal Server Error”等。
與HTTP請求類似,HTTP響應消息也包含一個“Content-Type”消息頭,它指定了消息體中內容的類型和編碼,例如“text/html; charset=UTF-8”。只有正確指定了“Content-Type”消息頭,瀏覽器才能正確解析收到響應消息體中的數據並呈現頁面。
瀏覽器是發送HTTP請求和接收HTTP響應的客戶端,HTTP協議保證了大多數情況下瀏覽器行爲的一致性,但不同瀏覽器之間仍有許多差異。這些差異經常導致B/S體系結構應用程序的開發變得困難。本節着重解釋不同瀏覽器在涉及國際化方面的不同行爲。
1.發送請求
使用瀏覽器發送HTTP請求有多種方式:
在瀏覽器地址欄中直接輸入URL;
在頁面中通過點擊“提交”按鈕提交表單;
用戶在頁面中點擊超鏈接產生的請求;
使用JavaScript腳本的XMLHTTPRequest對象發送請求。
(1)在瀏覽器地址欄中直接輸入URL
當URL中包含非ASCII碼字符時,Firefox會自動將這些字符進行轉義,轉義使用的編碼由瀏覽器的語言版本決定。例如,“http://www.baidu.com/s?wd=中文”將會轉義爲http://www.baidu.com/s?wd=%D6%D0%CE%C4。
默認情況下,中文Windows平臺上的IE瀏覽器將URL分爲兩個部分,“?”之前的部分URL使用UTF8進行轉義,而“?”之後的參數部分,則不進行轉義而直接使用GBK編碼發送。例如URL“http://localhost/中文.jsp?test=中文”,前一個“中文”將按照UTF8編碼的轉義形式“%E4%B8%AD%E6%96%87”發送,而參數部分的“中文”則直接以GBK編碼發送,因此,最終發送的URL如圖6-2所示。
在IE的“Internet選項”的“高級”選項卡頁中有一個選項“總是以UTF-8發送URL”,在缺省情況下該選項是選中的。如果去掉這個選項,IE將會以系統當前的代碼頁來對URL進行編碼。在中文Windows中整個URL都將以GBK編碼發送,如圖6-3所示。
(2)在頁面中通過單擊“提交”按鈕來提交表單
在表單中屬性“method”用來指定提交表單時所使用的HTTP請求方法,可以選擇Post或者Get。用戶不指定時,默認採用Get方法。而表單所提交內容採用的編碼則由頁面當前的編碼決定。例如,在一個JSP中包含以下表單代碼:
===formencoding.jsp==== <%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="GBK"%> <form action="formencoding.jsp" > <input type="text" name="中文" ></input> <input type="submit"/> </form> |
在IE或Firefox瀏覽器中打開該頁面,在“中文”輸入框中填入“中文”並單擊“提交”按鈕,會產生一個Get請求,所使用的URL爲:
http://localhost:8080/jsbook/formencoding.jsp?%D6%D0%CE%C4=%D6%D0%CE%C4
即使用GBK編碼對URL進行轉義。
如果將該頁面的contentType重置爲contentType="text/html; charset=UTF-8",則該表單所產生的URL爲:
http://localhost:8080/jsbook/formencoding.jsp? %E4%B8%AD%E6%96%87= %E4%B8%AD%E6%96%87%
即使用UTF-8編碼對URL進行轉義。
如果表單使用Post方法,則提交的參數將放在請求的消息體中,而使用的編碼方式仍將由該頁面的編碼方式決定。
(3)在頁面中單擊超鏈接產生的請求
用戶單擊頁面中的超鏈接時,瀏覽器將會產生一條“Get”請求。這個請求的URL使用的編碼方式由當前頁面使用的編碼及使用的瀏覽器共同決定。我們仍然使用前文的例子“http://localhost/中文.jsp?test=中文”來說明。
在IE中,頁面編碼爲UTF8時,這一請求中“?”前的部分將以UTF8編碼轉義,而“?”後的參數部分將直接使用UTF8編碼發送;當頁面編碼爲GBK時,請求中“?”前的部分仍以UTF8編碼轉義,而“?”後的參數部分將直接使用GBK編碼發送。
在Firefox中,頁面編碼爲UTF8時,整個URL將以UTF8編碼轉義。如果頁面編碼爲GBK,則請求以GBK編碼轉義。
如果在IE中禁用了“總是以UTF-8發送URL”選項,那麼當頁面編碼爲UTF8時,這一請求中“?”前的部分將以UTF8編碼轉義,而“?”後的參數部分將直接使用UTF8編碼發送;當頁面編碼爲GBK時,整個請求都將直接使用GBK編碼發送。
(4)使用XMLHTTPRequest對象發送請求
最後,我們來看一下使用JavaScript腳本來發送請求的情形。XMLHTTPRequest對象(下面簡稱XHR)是構成Ajax應用程序的基礎,它允許JavaScript腳本直接向服務器發送HTTP請求,在頁面不刷新的前提下與服務器通信,提交和獲取數據。
使用XHR對象發送請求也分爲Get和Post兩種。
IE中使用XHR對象發送“Get”請求時,對URL所使用的編碼規則和在地址欄中輸入URL是一致的。
Firefox中使用XHR對象發送“Get”請求時始終使用UTF-8編碼對URL進行轉義,而發送“Post”請求時,參數和URL分離,參數部分在消息體中,使用UTF-8編碼。要使Web服務器能夠正確識別,最好在Content-type消息頭中添加“Charset”信息,如以下代碼段所示:
//創建 XHR 對象,並準備 URL 和請求參數 xmlHttp.open("Post",url,true); xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded;charset=UTF-8"); xmlHttp.setRequestHeader("Content-length", params.length); xmlHttp.setRequestHeader("Connection", "close"); xmlHttp.send(params); |
請注意,設置上述請求的消息頭只能用來告知服務器該消息體所使用的編碼,並不能通過修改此消息頭的值來改變該請求所使用的編碼。
2.接受響應
前文描述了瀏覽器在發送HTTP請求時選取編碼的行爲。那麼從服務器端返回HTTP響應時,瀏覽器又是如何判斷該響應使用了何種編碼的呢?
瀏覽器判斷返回的HTTP響應消息所使用的編碼遵循以下一系列規則。
首先,瀏覽器會檢查HTTP響應中的“Content-type”消息頭。如“text/html; charset=UTF-8”,表明該消息所包含的內容是純文本的HTML文檔,採用UTF-8編碼。但在很多情況下,服務器返回的Content-type消息頭並不包含“charset”信息。
當響應消息不包含“charset”信息時,瀏覽器會嘗試自動探測編碼。第一個步驟是檢查響應消息體的開頭是否包含UTF-8的BOM(字節順序標記,Byte Order Marker)。BOM是一種用來判斷文件編碼的特定字節標記,如果一個文件的開頭幾個字節包含了UTF-8的BOM,那麼瀏覽器就可以斷定這個HTML文件是採用UTF-8編碼的。
如果該HTML中不包含BOM,那麼,瀏覽器就會嘗試尋找HTML頁面中的<meta>標記,如:
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> |
如果頁面中又不包含<meta>標記,那麼,瀏覽器將採用默認的編碼來解析。在中文的IE和Firefox裏就是採用GBK或GB2312編碼。
因此,要使服務器端返回的響應消息能夠正確地被瀏覽器解析,最簡單有效的方法就是在響應的“Content-type”消息頭中設置charset屬性。在Servlet編程中可以在doGet()或doPost()方法中調用:
response.setCharacterEncoding("UTF-8") |
在JSP編程中可以在頁面開頭指定響應的編碼:
<%@ page language="java" contentType="text/html; charset=UTF-8" import="java. util.*" pageEncoding="UTF-8"%> |
6.1.3 簡單總結
本節描述了HTTP協議中數據傳輸時需要考慮的編碼問題,以及在瀏覽器中發送請求時所使用的不同編碼設置。由於編碼設置在整個B/S體系結構的“請求-處理-響應”各個環節中無處不在,其中任何一個環節的錯誤設置都會導致最終呈現給用戶的數據出現亂碼,因此,理解這些編碼設置的原理將有助於我們在遇到問題時檢測和判斷究竟是哪個環節設置錯誤。
而避免這些錯誤和複雜的編碼設置的最好辦法,就是在所有的環節都統一使用UTF-8編碼,這也可以說是在設計B/S體系結構的Web應用程序時需要貫穿始終的設計原則。
|
瀏覽器需要使用恰當的字符編碼來解釋HTML文檔,以使網頁可以用正確的語言、字符集和字體顯示。爲了保證這一點,HTML文檔應當包含明確的信息來表明所使用的字符編碼,這可以通過設置META標記實現。網站管理員還可以通過配置IBM HTTP Server 來爲每一個響應加上合適的HTTP消息頭,從而確保在META標記缺失的情況下網頁也能正常顯示。
1.在HTML文檔中使用META標記來標識字符編碼
META標記的作用是什麼?META標記在W3C HTML規範中定義,嵌入在HEAD標記中,用於標識關於HTML文檔的元信息,這些元信息可以被瀏覽器和Web服務器所解析和使用。在META中設置HTTP-EQUIV屬性,可以告訴瀏覽器該META信息與HTTP信息頭等價。如果HTTP-EQUIV屬性缺失,那就需要設置NAME屬性來標識該元信息,並且不應將這個元信息作爲HTTP信息頭來解析。如果NAME屬性缺失,但HTTP-EQUIV存在,那麼會假設NAME與HTTP-EQUIV相同。而CONTENT屬性用來保存與HTTP-EQUIV或NAME相關的值,以構成一個“鍵/值對”(key-value pair)。
設置META標記最簡單也是最安全的做法是將HTML以UTF-8編碼保存,並且在HEAD中加入名稱爲Content-Type的META信息。
... <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>這是網頁的標題</title> </head> ... |
如果HTML整體顯示正確,但只有標題爲亂碼,那是什麼問題?這在多數情況下是因爲TITLE標記出現在META標記之前,瀏覽器順序解讀HTML時首先遇到TITLE,而在那時還不曾確定應當用什麼字符編碼解析,比如下面就是一個錯誤的例子。
... <head> <title>這是網頁的標題</title> <!--這一行不應該出現在 META 之前--> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> </head> ... |
2.配置IBM HTTP Server
這是一種自動防錯機制,即便個別HTML文檔因爲這樣或那樣的原因,沒有用META標記聲明字符編碼,IBM HTTP Server仍能夠自動地在HTTP響應消息頭中提供編碼信息,幫助瀏覽器正確解讀HTML文檔。
具體的配置方法是修改IBM HTTP Server的配置文件httpd.conf(默認情況下位於conf目錄下),如下面的代碼片段所示。
... AddType ‘text/html; charset=UTF-8’ html htm ... |
這樣,服務器就會爲每一個後綴爲html和htm的請求回覆中自動加上消息頭(如下所示),以達到通知瀏覽器文檔的編碼格式的目的。
Content-Type: text/html; charset=UTF-8 |
上面的配置適用於整個網站統一採用UTF-8編碼的情況。那麼,如果並沒有統一爲UTF-8編碼該怎麼辦呢?IBM HTTP Server可以根據文檔所處目錄的不同,自動添加不同的消息頭,當然這種配置會複雜一些。
假設網站包含不同編碼的文檔,分佈在不同的目錄中。
/root/-----/en/---index.html (encoded in ASCII) | page1.html | |--/cn/---index.html (encoded in GB2312) | page1.html | |--/ja/---index.html (encoded in Shift_JIS) page1.html |
那麼相應的 httpd.conf 大致如下。
<Directory /root/en/ > ... AddType 'text/html; charset=iso-8859-1' html htm ... </Directory> ... <Directory /root/cn/ > ... AddType 'text/html; charset=GB2312' html htm ... </Directory> ... <Directory /root/ja/ > ... AddType 'text/html; charset=Shift_JIS' html htm ... </Directory> |
有了上面的配置,在目錄/root/下的html和htm就會以相應的HTTP消息頭返回給瀏覽器。比如對於/root/cn/index.html,就會返回“Content-Type: text/html; charset=UTF-8”消息頭。
如果不希望直接修改httpd.conf,那麼,可以通過修改.htaccess來覆蓋httpd.conf中的配置,可以達到相同的目的。.htaccess是一個作用於其所在目錄和所有子目錄的配置文件,對.htaccess的修改不需要重新啓動IBM HTTP Server就能生效,因此被經常使用。要保證.htaccess中的配置覆蓋httpd.conf,首先需要在httpd.conf中對相關目錄加上“AllowOverride”參數,如下所示。
... <Directory /root/cn/ > ... AllowOverride All ... </Directory> |
然後在.htaccess中關於/root/cn/的配置中加入下面一行以使之生效。
AddType 'text/html; charset=GB2312' html htm |
更多關於IBM HTTP Server配置文件和參數的信息,請查閱相關文檔。
3.小結
以UTF-8編碼保存HTML文檔。
在HTML文檔中加入META標記以標識字符編碼。
配置IBM HTTP Server以啓用自動防錯功能。
在當代的網絡應用程序中,JSP常常扮演MVC模型中視圖的角色,用來生成HTML以返回客戶端,從而在瀏覽器中呈現用戶界面。對國際化的應用程序來說,JSP文件可能以多種編碼(爲不同的語言)編寫,因此,需要有特定的參數標識JSP的字符編碼,以保證應用程序服務器能正確地解析和編譯JSP文件。本節會介紹相關的參數並說明它們的用途,同時再一次強調,採用統一的UTF-8編碼是最簡單和最安全的策略。
1.設置JSP文件的編碼
在JSP能夠被執行並響應用戶請求之前,首先要經過一個編譯的過程,這裏JSP文件的編碼指的是應用程序服務器讀取JSP,進行編譯時所使用的字符編碼。最通常的做法是將所有JSP都保存爲UTF-8格式,並在文件中使用pageEncoding標明字符編碼。
<%@ page language="java" pageEncoding="UTF-8" %> |
如上pageEncoding就是一個專用於標識JSP字符編碼的參數,但同時它也是可選的。你也可以不提供pageEncoding,而通過設置contentType參數來指定頁面編碼。
<%@ page language="java" contentType="text/html; charset=UTF-8" %> |
像這樣缺少pageEncoding的情況下,contentType中的charset部分將用於JSP頁面的字符編碼。如果同時使用了contentType和pageEncoding,哪一個參數優先級更高呢?答案是pageEncoding優先。在contentType和pageEncoding中使用不同的編碼是一種非常少見的情況,後面的章節會具體討論。但一般而言,字符編碼在contentType和pageEncoding中應當保持一致。
除了上述在每一個JSP文件中指定字符編碼外,從JSP 2.0規範開始,也可以在web.xml中一次聲明多個JSP文件的編碼。
<jsp-config> <jsp-property-group> <description>For all JSPs</description> <url-pattern>*.jsp</url-pattern> <page-encoding>UTF-8</page-encoding> </jsp-property-group> </jsp-config> |
如果在web.xml中加入上面的配置,那麼,所有的JSP文件都會採用UTF-8編碼執行編譯,這比在每一個JSP文件中加入聲明要簡潔得多,當然,這首先要求應用程序服務器支持JSP 2.0規範。
在JSP 2.0規範中,JSP文件的編碼聲明和查詢次序的定義如下。
(1)首先,尋找web.xml中url-pattern匹配的jsp-config聲明。
(2)其次,尋找JSP文件中的pageEncoding參數,如果pageEncoding參數與jsp-config中的配置不符,將產生一個JSP編譯錯誤。
(3)再次,尋找contentType中的charset聲明,這僅當jsp-config和pageEncoding參數都不存在時纔有效。
(4)最後,如果上述所有設置都不存在,那麼,採用ISO-8859-1作爲默認編碼。
2.設置JSP響應字符編碼
JSP響應字符編碼是指JSP生成的HTTP響應所使用的字符編碼,也就是JSP運行後生成的HTML文檔的字符編碼,該編碼由contentType參數指定。在每一個JSP文件中都加入合適的contentType聲明是推薦的做法。比如:
<%@ page language="java" contentType="text/html; charset=UTF-8" %> |
這樣會調用ServletResponse.setContentType()方法,從而設置HTTP響應的字符編碼。
如果缺少contentType,那麼會繼續查詢pageEncoding參數及web.xml中的jsp-config配置。如果其中任意一個存在,那麼其字符編碼將作用於HTTP響應,如果都不存在,那麼最終會使用ISO-8859-1字符編碼。
3.符合XML語法的JSP文件
JSP規範從V2.0開始支持完全符合XML語法的JSP文件,對於這類符合XML語法的JSP文件,上述的文件字符編碼和響應字符編碼的設置會略有不同。
對於JSP文件編碼,它取決於XML文件第一行的編碼聲明。
<?xml encoding='UTF-8'?> |
文件中pageEncoding和web.xml中的jsp-config設置仍然有效,但僅起到一個複查的作用。如果任意一處有不一致的編碼聲明,都將導致JSP編譯時出錯。例如,可以如下聲明pageEncoding。
<jsp:directive.page pageEncoding="UTF-8"/> |
對於HTTP響應字符編碼,仍然使用contentType參數,當然語法有所不同,以便符合XML規範。
<jsp:directive.page contentType="text/html; charset=UTF-8"/> |
如果contentType參數不存在,那麼默認的字符編碼將爲UTF-8,而不再是ISO-8859-1。
4.一些不推薦的方法
對所有JSP統一使用UTF-8編碼是最好的策略。如果非要在JSP中使用和語言相關的編碼格式,那麼,對於國際化的應用程序來說將是一場噩夢,因爲這將導致同一個網頁有多個對應的JSP文件,每個文件對應一種語言。如果要支持10種語言,那就有網頁數乘以10的JSP文件,其中任意一個網頁需要變更,都要改變10個JSP文件,這幾乎是無法管理的。
另一種不推薦的情形是對一個JSP使用不同的文件編碼和響應編碼。比如,下面的聲明表示JSP文件本身的編碼是GB2312,而生成的HTML內容則使用UTF-8。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="GB2312"%> |
這種做法雖然存在理論上的可能性,但實際操作中卻很容易把兩種編碼混淆,從而產生錯誤。調試這類編碼錯誤是非常複雜的,因爲有太多的配置可能對其產生影響。
5.小結
最好以UTF-8編碼保存JSP文檔。
在JSP中使用pageEncoding參數聲明文件字符編碼,使用contentType參數聲明響應字符編碼。
如果應用程序服務器支持JSP 2.0規範,也可以在web.xml中使用jsp-config標記統一聲明JSP文件編碼。
對於一個國際化的應用程序而言,僅設置HTML和JSP編碼是遠遠不夠的,除了考慮服務器端輸出內容的字符編碼外,還要考慮如何正確解析來自全球範圍內不同地域和語言的用戶輸入。本節將討論如何設置編碼以確保一個網頁上的表單能夠接收不同語言的輸入,並且在服務器端這些不同語言的字符能夠被正確地解析。
1.使用UTF-8編碼提交數據
要使應用程序能夠接收國際化的輸入,首先要確保瀏覽器使用UTF-8編碼向Web服務器提交數據。UTF-8作爲通用編碼,可以爲全球不同的語言進行統一的編碼和解碼,這樣可以大大簡化服務器端爲了適應多語言所需的編程。
當用戶在瀏覽器單擊提交表單時,瀏覽器會使用當前網頁的字符編碼提交表單上輸入的內容,因此,只要對所有的網頁都採用UTF-8編碼,就可以保證瀏覽器總是使用UTF-8編碼提交數據。
對網頁編碼的設置已經在“6.2.1 HTML的編碼設置”和“6.2.2 JSP的編碼設置”中詳細討論過了,可以根據網頁輸出的具體方式在相應的章節找到編碼的設置方法,這裏就不重複了。
2.設置字符編碼以正確解析HTTP請求
當數據提交到服務器端後,一般在Servlet中進行字符編碼的解析,然後獲取用戶輸入,進行邏輯運算和業務處理。爲了正確地解碼用戶輸入,程序在讀取HTTP請求之前,必須調用ServletRequest.setCharacterEncoding()方法來設置合適的字符編碼,以便將HTTP請求中包含的字節流解碼爲字符流。
protected final void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String input = req.getParameter("order"); } |
如上例所示,在Servlet的doPost()方法中,首先調用setCharacterEncoding(),以便將UTF-8設置爲字符編碼,然後讀取名爲order的輸入參數。這裏setCharacterEncoding()設置的編碼必須和瀏覽器提交數據所使用的字符編碼一致,上例中即爲UTF-8,否則,就會導致讀入的字符串爲亂碼。
關於setCharacterEncoding()方法,一個需要強調的要點是:必須在讀取HTTP請求之前設置編碼,否則是無效的。這裏說的“讀取HTTP請求”不僅包括使用getParameter(), getParameterMap(),getParameterNames()和getParameterValues()之類的方法,還包括getReader() 方法,比如下面就是一個錯誤的例子。
protected final void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Reader reader = req.getReader(); req.setCharacterEncoding("UTF-8"); // 錯誤!必須在 getReader() 之前調用! String input = req.getParameter("order"); // 有可能讀入亂碼 } |
對於一般的程序,要確保在第一時間調用setCharacterEncoding()可能極爲簡單,但是,如果使用了比較複雜的Web層框架,情形就會不同。因爲程序的控制權往往並不直接進入應用程序代碼,而是通過框架轉發調用再進入應用程序,此時如果在框架內部首先讀取了HTTP請求,那麼在應用程序中調用setCharacterEncoding()就爲時已晚。
因此,根據所使用的Web框架的不同,調用setCharacterEncoding()的時機和位置都有可能不同,需要根據具體情況而定。上面的示例僅對最普通的Servlet而言,並不適用於所有的Web框架。後面的章節會對一些主流的Web框架進行與國際化相關的具體分析。
3.設置Servlet的響應字符編碼
在大多數情況下,Java應用程序通過JSP生成HTML文檔,作爲響應發回瀏覽器,相關的設置已經在6.2.2中的“2.設置JSP響應字符編碼”中介紹過了。然而,也存在個別特殊情況,程序會選擇由Servlet直接生成響應,此時就需要在Servlet中設置響應的字符編碼。在ServletAPI中,有三個方法可以用於設置響應字符編碼,它們都在ServletResponse接口中定義,分別是setCharacterEncoding(), setContentType()和setLocale()。
一般推薦用setContentType()或者setCharacterEncoding()設置編碼。它們的用法大致如下:
protected final void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/html; charset=UTF-8"); PrintWriter writer = resp.getWriter(); writer.println("..."); } |
兩個方法都會在HTTP響應頭中加入ContentType聲明,通知瀏覽器響應內容的字符編碼。
僅當舊版本的Servlet規範不支持setContentType()和setCharacterEncoding()時,纔會使用setLocale()。調用setLocale()會導致採用和語言相關的默認編碼,但仍通過ContentType響應頭通知客戶端。
這裏同樣需要注意,設置響應編碼必須在輸出響應之前執行,否則將起不到預期的效果。下面是一個錯誤例子。
protected final void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter writer = resp.getWriter(); writer.println("..."); resp.setCharacterEncoding("UTF-8"); // 錯誤,必須在 getWriter() 之前設置 } |
4.小結
將UTF-8設置爲網頁編碼,以UTF-8提交數據。
調用ServletRequest.setCharacterEncoding()設置編碼解析HTTP請求。
如果不通過JSP,而是通過Servlet直接生成響應,那麼調用ServletResponse接口中定義的setCharacterEncoding()或者setContentType()來設置響應字符編碼。
在實際項目開發中,往往不會直接使用Servlet/JSP,更多的是在一些Web框架的基礎上搭建應用程序,這些Web框架在帶來完善的設計模式的同時,也對Serlet/JSP編程接口進行包裝和擴展,這在一定程度上造成了不小的學習曲線。習慣在Servlet/JSP模式下工作的開發人員有時也會產生困擾:如何在這些框架下寫出支持國際化的代碼呢?面對更豐富的編程接口和類繼承,應該從何處入手,通過什麼方法來設置恰當的字符編碼呢?本節將嘗試回答這些問題。
1.Struts框架下的編碼設置
Struts可能是時下最流行的Web框架,它推薦的MVC模式幾乎是所有Web應用程序的首選。從原理上說,Struts是基於Servlet/JSP的一種擴展,就設置字符編碼而言,仍然是通過Servlet編程接口來執行,其不同處主要在於設置編碼的時機。
與Servlet不同,在Struts框架下,服務器端的程序入口並不在應用程序,而總是經由Struts框架,再輾轉進入應用程序的操作(Action)邏輯,這對需要在第一時間設置的字符編碼造成了不小的困擾。更重要的是,Struts還負責自動從HTTP請求讀取表單輸入,並導入ActionForm中,以供應用程序使用。所以,必須尋找到一個時機,在Struts讀取HTTP請求之前完成編碼設置。
縱觀Struts框架,最合適設置編碼的位置是ActionForm的reset()方法。該方法在每次重置ActionForm時調用,緊接着Struts就會從HTTP請求讀取表單輸入。下面是一個範例。
public class SampleForm extends ActionForm { public void reset(ActionMapping mapping, HttpServletRequest request) { // 在此設置字符編碼 request.setCharacterEncoding("UTF-8"); ... } } |
具體設置的方法與Servlet中的編碼設置一樣,更詳細的內容可以參閱6.2.3節中的“2.設置字符編碼以正確解析HTTP請求”。
2.使用過濾器的通用編碼設置方法
Java社區框架種類繁多是出名的。除了Struts以外,其他的Web框架也如雨後春筍般層出不窮。每一個框架都有自己的個性和特質,實現國際化、設置編碼的方法都有可能不同。逐一分析每一個框架,尋找恰當時機和位置來設置字符編碼固然可行,但有沒有更通用的方法,能夠普遍適用於大多數Web框架呢?
答案是:有。Servlet規範V2.3引入了過濾器(Filter)機制,這是一種非常有用的工具,能夠通用地設置Web層的字符編碼。其原理是,通過恰當的配置,過濾器可以在所有的HTTP請求進入Servlet之前進行攔截,從而保證在調用Web框架之前(不論什麼框架,其入口總是一個特定的Servlet)第一時間完成字符編碼的設置。
關於過濾器的詳細說明和使用方法不在本書範圍之內,讀者可以自行參考Servlet規範以瞭解更多信息。下面僅以一個示例說明如何使用一個UTF8Filter來保證所有的HTTP請求都以UTF-8編碼進行解析。
首先是一個名爲UTF8Filter的Java類,它實現了Filter接口,用以攔截HTTP請求,並在請求進入Servlet之前對其進行編碼設置。
package filters; import java.io.IOException; import javax.servlet.*; public class UTF8Filter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 進行攔截,然後設置字符編碼 request.setCharacterEncoding("UTF-8"); // 繼續,進入 Servlet 執行階段 chain.doFilter(request, response); } public void init(FilterConfig filterConfig) throws ServletException { } public void destroy() { } } |
UTF8Filter中實現了攔截的邏輯,至於對哪一些HTTP請求進行攔截則是在web.xml中配置的。下面的web.xml片段將UTF8Filter配置爲攔截所有的HTTP請求。
<filter> <filter-name>UTF8Filter</filter-name> <filter-class>filters.UTF8Filter</filter-class> </filter> <filter-mapping> <filter-name>UTF8Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
請注意上面片段中的<url-pattern>/*</url-pattern>一行,模式“/*”和所有的URL都匹配,從而保證UTF8Filter攔截所有的HTTP請求。
3.小結
對於Struts框架,可以在ActionForm.reset()中設置字符編碼。
用過濾器(Filter)攔截HTTP請求,在進入Web框架之前設置字符編碼。
|
本章介紹了 Java 國際化開發在 Web 應用程序領域的相關技術。涉及的技術包括 HTTP 協議、HTML/JSP/Servlet 編碼設置、“資源包”和“語言目錄”的實現策略、標記庫(Tag Lib),以及 JavaScript 的國際化開發等。
原帖來至:http://www.ibm.com/developerworks/cn/java/book_global_development/6/
Firefox也提供了使用UTF-8進行URL編碼的選項。在地址欄中輸入“about:config”,並按回車鍵打開配置頁面,在過濾器中輸入“network.standard-url.encode-utf8”以定位到該選項,如圖6-1所示。將該選項的值修改爲true,以使Firefox始終用UTF-8對URL進行轉義。這樣,上述URL將轉義爲http://www.baidu.com/s?wd=%E4%B8%AD%E6%96%87。