本作品採用知識共用署名-非商業性使用-相同方式共用 2.5 中國大陸許可協議進行許可。
當我們通過Java程式員的視角來瀏覽網頁時會發現:一方面使用者端瀏覽器(IE或Firefox)以表單或連結的方式提交HTTP請求同時又處理HTTP伺服器發出的響應資料,將其中的資料流(HTML資料或其它種類的資料)以適當的方式展示給使用者瀏覽。另一方面在Java WEB應用伺服器上,一個HTTP請求可以由一個Servlet類或一個JSP網頁來處理,請求資料來自於HttpServletRequest,響應資料發送至HttpServletResponse。通過使用者端提交請求、伺服器端處理請求、伺服器端返迴響應資料以及使用者端處理響應資料四個步驟組成了一次HTTP請求的全部過程。資料在這四個重要環節中進行傳輸時,都將以指定的編碼方式進行編碼或解碼。如果處理不當就會出現亂碼問題。
使用者端的處理
當使用者端發出一個HTTP請求時,一個如下格式的資料將發送給伺服器端:
<request-line>
<headers>
<CRLF>
[<request-body><CRLF>]
關於HTTP請求的格式,可以在HTTP協議與HTML表單(再談GET與POST的區別)中瞭解更多的內容。
在此,request-line與request-body均需要進行相應的編碼處理。
request-line的編碼處理
request-line中的URL部分必須以application/x-www-form-urlencoded方式編碼。編碼時使用的字元集是當前網頁在瀏覽器上顯示時所使用的字元集。
JDK中專門有兩個類處理application/x-www-form-urlencoded類型的資料,它們是URLEncoder及URLDecoder。當網頁上的資料需要手動進行URLEncoding處理時,可使用URLEncoder類完成編碼工作。需要手動進行URLEncoding處理的位置包括:
- 連結(<a></a>)中的href標籤屬性;
- 以POST方式提交的表單(<form></form>)中的action標籤屬性。
例如,網頁上不應該產生這樣的連結:
<!-- 不正確的寫法 --><br /><a href="/hello/checkUser.html?opt=中文>使用者身分識別驗證"</a>
正確的寫法是:
<!-- 使用UTF-8字元集進行URLEncoding的結果 --><br /><a href="/hello/checkUser.html?opt=%E4%B8%AD%E6%96%87">使用者身分識別驗證</a>
為此,方案之一可以在JSP網頁上使用指令碼化語言進行URLEncoding處理。如:
<%@page import="java.net.URLEncoder"%><br /><a href="/hello/checkUser.html?opt=<%=URLEncoder.encode("中文", "UTF-8")%>">使用者身分識別驗證</a>
request-body的編碼處理
request-body只有在POST提交的方式下才會產生。request-body的編碼方式由表單的enctype標籤屬性指定,同request-line一樣,編碼request-body時使用的字元集也是當前網頁在瀏覽器上顯示時所使用的字元集。request-body的編碼過程由用戶端瀏覽器自動完成,不需要額外的編程處理。
伺服器的處理
相對於使用者端,伺服器端在接收到HTTP請求時提供了兩種處理請求資料的方式:自動處理與不處理。
伺服器一般會自動處理application/x-www-form-urlencoded類型的資料(包括request-line及request-body中的資料),就servlet(Servlet類或JSP網頁)而言,可以通過request對象的getParameter()或getParameterValues()取得這些資料。對於除此以外的其它MIME類型的資料,HTTP伺服器則是將處理的過程直接交到了與HTTP請求相對應的servlet(Servlet類或JSP網頁)身上。
例如使用者端有以下表單被提交:
<form action="checkUser.html?opt=xxx" method="POST"><br /> <input type="text" name="username" value="yyy"/><br /> <input type="text" name="username" value="zzz"/><br /> <inupt type="submit" value="submit"/><br /></form>
表單提交時經伺服器端自動處理後與checkUser.html相對應的servlet(Servlet類或JSP網頁)可以通過下面的方式取得資料:
String opt = request.getParameter("opt");<br />String[] users = request.getParameterValues("username");
預設情況下,伺服器對於接收到的application/x-www-form-urlencoded類型資料進行字元集為ISO-8859-1的URLDecoding處理,經過處理之後的字串內碼為ISO-8859-1。對於沒有附加任何設定的HTTP伺服器而言,我們的servlet在取得資料之後必須進行相應的解碼處理,產生內碼為UTF-16(unicode)的字串。
例如對於使用者端請求資料中以UTF-8字元集進行URLEncoding的資料,servlet需要進行如下方式的解碼:
String opt = request.getParameter("opt");<br />if (opt!=null && !"".equals(opt)) {<br /> opt = new String(opt.getBytes("ISO-8859-1"), "UTF-8");<br />}
為了避免這種額外的編碼/解碼處理,也就是說讓伺服器瞭解到使用者端在URLEncoding時所使用的字元集,從而直接進行相應字元集的URLDecoding處理,不同的HTTP伺服器提供了不同的解決方案。
以Tomcat為例,Tomcat自動解碼request-line的處理方式由Tomcat的設定檔server.xml指定。在server.xml中的Connector標籤中提供了URIEncoding標籤屬性,只要為其指定解碼用的字元集,Tomcat就會自動解碼request-line中經過application/x-www-form-urlencoded編碼處理的資料。例如:
<Connector connectionTimeout="40000" port="8080" protocol="HTTP/1.1"
URIEncoding="UTF-8" redirectPort="8443"/>
Tomcat自動解碼request-body的處理方式是設定request的characterEncoding值。如:
request.setCharacterEncoding("UTF-8");
但是這一操作必須提前在filter中完成,在servlet中使用此方法已經不起作用了。filter的例子如下:
import java.io.IOException;<br />import javax.servlet.Filter;<br />import javax.servlet.FilterChain;<br />import javax.servlet.FilterConfig;<br />import javax.servlet.ServletException;<br />import javax.servlet.ServletRequest;<br />import javax.servlet.ServletResponse;</p><p>public class CharacterEncodingFilter implements Filter {</p><p> private String encoding;</p><p> public CharacterEncodingFilter() {<br /> encoding = null;<br /> }</p><p> public void destroy() {<br /> encoding = null;<br /> }</p><p> public void doFilter(ServletRequest request, ServletResponse response,<br /> FilterChain chain) throws IOException, ServletException {</p><p> request.setCharacterEncoding(encoding);<br /> chain.doFilter(request, response);<br /> }</p><p> public void init(FilterConfig filterConfig) throws ServletException {</p><p> encoding = filterConfig.getInitParameter("encoding");<br /> if (encoding == null || "".equals(encoding)) {</p><p> encoding = "UTF-8";<br /> }<br /> }<br />}
我們可以在web.xml使用這個filter。web.xml的相應配置如下:
<filter><br /> <filter-name>Character Encoding Filter</filter-name><br /> <filter-class><br /> CharacterEncodingFilter<br /> </filter-class><br /> <init-param><br /> <param-name>encoding</param-name><br /> <param-value>UTF-8</param-value><br /> </init-param><br /></filter><br /><filter-mapping><br /> <filter-name>Character Encoding Filter</filter-name><br /> <url-pattern>/*</url-pattern><br /></filter-mapping>
通過上述兩種方式的預先處理,在servlet中取出的資料可以不必進行ISO-8859-1解碼而直接使用。
字元集的選擇
在處理application/x-www-form-urlencoded類型的資料過程中,需要注意的另一個問題是字元集的選擇問題。如上所述,不論是request-line還是request-body,其URLEncoding所使用的字元集都是當前網頁在瀏覽器上顯示時所使用的字元集。而這個資訊又是HTTP伺服器端產生HTML網頁時,在HTTP響應中提供的。
當HTTP伺服器接收到一個HTTP請求時,伺服器總是需要向使用者端發送一個HTTP響應。HTTP響應資料與HTTP請求資料格式相同,同樣由以下幾個部分組成:
<response-line>
<headers>
<CRLF>
[<response-body><CRLF>]
下面描述的是請求一個HTML網頁資料時伺服器的響應資訊:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=UTF-8
Content-Length: 265
Date: Thu, 17 Dec 2009 05:20:36 GMT
<!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=UTF-8">
<title>test</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
[End]
其中headers的Content-Type指定了資料流的資料格式以及顯示用的字元集。這一指標可以通過以下幾種方式指定:
1. HTML網頁
在HTML網頁的<head></head>中存在多個<meta/>標籤,其中可以設定Content-Type。例如:
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
2. JSP網頁
JSP網頁中,除了<meta/>標籤之外,還需要在JSP網頁頭部設定如下代碼:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
3. Servlet類
如果要在Servlet類中通過response向使用者端傳送HTML資料,需要在傳送前指定Content-Type。代碼如下:
response.setContentType("text/html;charset=UTF-8");
通過上述三種方式,可以確保響應資料傳送到使用者端瀏覽器時,瀏覽器使用正確解碼方式和字元集顯示其內容。
結束語
總之,Content-Type是聯絡使用者端與伺服器端的紐帶,通過這一指標,雙方得以對相關的資料進行正確的編碼與解碼。只要瞭解了Content-Type的作用以及使用方法,亂碼問題就會迎刃而解。