Tomcat關於encoding編碼的預設設定以及亂碼產生的原因
http://www.linuxso.com/architecture/20099.html
亂碼的產生
譬如漢字“中”,以UTF-8編碼後得到的是3位元組的值%E4%B8%AD,然後通過GET或者POST方式把這3個位元組提交到Tomcat容器,如果你不告訴Tomcat我的參數是用UTF-8編碼的,那麼tomcat就認為你是用ISO-8859-1來編碼的,而ISO8859-1(相容URI中的標準字元集US-ASCII)是相容ASCII的單位元組編碼並且使用了單位元組內的所有空間,因此Tomcat就以為你傳遞的用ISO-8859-1字元集編碼過的3個字元,然後它就用ISO-8859-1來解碼,得到??--,解碼後。字串??--在Jvm是以Unicode的形式存在的,而HTTP傳輸或者資料庫儲存的其實是位元組,因此根據各終端的需要,你可以把unicode字串??--用UTF-8編碼後得到相應的位元組後儲存到資料庫(3個UTF-8字元),也可以取得這3個字元對應的ISO-8859-1的3個位元組,然後用UTF-8重新編碼後得到unicode字元“中”(特性:把其他任何編碼的位元組流當作ISO-8859-1編碼看待都沒有問題),然後用response傳遞給用戶端(根據你設定的content-type不同,傳遞的位元組也是不同的!)
總結:
1,HTTP GET或者POST傳遞的是位元組?資料庫儲存的也是位元組(譬如500MB空間就是500M位元組)
2,亂碼產生的原因是編碼和解碼的字元集(方式)不同導致的,即對於幾個不同的位元組,在不同的編碼方案下對應的字元可能不同,也可能在某種編碼下有些位元組不存在(這也是亂碼中?產生的原因)
3,解碼後的字串在jvm中以Unicode的形式存在
4,如果jvm中存在的Unicode字元就是你預期的字元(編碼,解碼的字元集相同或者相容),那麼沒有任何問題,如果jvm中存在的字元集不是你預期的字元,譬如上述例子中jvm中存在的是3個Unicode字元,你也可以通過取得這3個unicode字元對應的3個位元組,然後用UTF-8對這3個位元組進行編碼產生新的Unicode字元:漢字“中”
5,ISO8859-1是相容ASCII的單位元組編碼並且使用了單位元組內的所有空間,在支援ISO-8859-1的系統中傳輸和儲存其他任何編碼的位元組流都不會被拋棄。換言之,把其他任何編碼的位元組流當作ISO-8859-1編碼看待都沒有問題。
下面的代碼顯示,使用不同的編碼來Encoder會得到不同的結果,同時如果Encoder和Decoder不一致或者使用的漢字在編碼ISO-8859-1中不存在時,都會表現為亂碼的形式!
try {
// 漢字中以UTF-8編碼為 %E4%B8%AD(3位元組)
System.out.println(URLEncoder.encode("中", "UTF-8"));
// 漢字中以UTF-8編碼為 %3F (1位元組 這是由於漢字在ISO-8859-1字元集中不存在,返回的是?在ISO-8859-1下的編碼)
System.out.println(URLEncoder.encode("中", "ISO-8859-1"));
// 漢字中以UTF-8編碼為 %D6%D0 (2位元組)
System.out.println(URLEncoder.encode("中", "GB2312"));
// 把漢字中對應的UTF-8編碼 %E4%B8%AD 用UTF-8解碼得到正常的漢字 中
System.out.println(URLDecoder.decode("%E4%B8%AD", "UTF-8"));
// 把漢字中對應的ISO-8859-1編碼 %3F 用ISO-8859-1解碼得到?
System.out.println(URLDecoder.decode("%3F", "ISO-8859-1"));
// 把漢字中對應的GB2312編碼 %D6%D0 用GB2312解碼得到正常的漢字 中
System.out.println(URLDecoder.decode("%D6%D0", "GB2312"));
// 把漢字中對應的UTF-8編碼 %E4%B8%AD 用ISO-8859-1解碼
// 得到字元??-(這個就是所謂的亂碼,其實是3位元組%E4%B8%AD中每個位元組對應的ISO-8859-1中的字元)
// ISO-8859-1字元集使用了單位元組內的所有空間
System.out.println(URLDecoder.decode("%E4%B8%AD", "ISO-8859-1"));
// 把漢字中對應的UTF-8編碼 %E4%B8%AD 用GB2312解碼
// 得到字元涓?,因為前2位元組 %E4%B8對應的GB2312的字元就是涓,而第3位元組%AD在GB2312編碼中不存在,故返回?
System.out.println(URLDecoder.decode("%E4%B8%AD", "GB2312"));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Tomcat關於encoding編碼的預設設定以及相關標準:
對於Get請求,"URI Syntax"規範規定HTTP query strings(又叫GET parameters)使用US-ASCII編碼,所有不在這個編碼範圍內的字元,必須經常一定的轉碼:%61的形式(encode)。又由於ISO-8859-1 and ASCII對於0x20 to 0x7E範圍內的字元是相容的,大部分的web容器譬如Tomcat容器預設使用ISO-8859-1解碼URI中%xx部分的位元組。可以使用Connector中的URIEncoding來修改這個預設用來解碼URI中%xx部分位元組的字元集。URIEncoding要和get請求query
string中encode的編碼一直,或者通過設定Content-Type來告訴容器你使用什麼編碼來轉碼url中的字元
POST請求應該自己通過參數Content-Type指定所使用的編碼,由於許多用戶端都沒有設定一個明確的編碼,tomcat就預設使用ISO-8859-1編碼。注意:用來對URI進行解碼的字元集,Request字元集,Response字元集的區別!不同的Request實現中,對於上述3個編碼的關係是不同的
對於POST請求,ISO-8859-1是Servlet規範中定義的HTTP request和response的預設編碼。如果request或者response的字元集沒有被設定,那麼Servlet規範指定使用編碼ISO-8859-1,請求和相應指定編碼是通過Content-Type回應標頭來設定的。
如果Get、Post請求沒有通過Content-Type來設定編碼的話,Tomcat預設使用ISO-8859-1編碼。可以使用SetCharacterEncodingFilter來修改Tomcat請求的預設編碼設定(encoding:使用的編碼, ignore:true,不管用戶端是否指定了編碼都進行設定, false,只有在用戶端沒有指定編碼的時候才進行編碼設定, 預設true)
注意:一般這個Filter建議放在所有Filter的最前面(Servlet3.0之前基於filter-mapping在web.xml中的順序, Servlet3.0之後有參數可以指定順序),因為一旦從request裡面取值後,再進行設定的話,設定無效。因為在第一次從request取值時,tomcat會把querystring或者post方式提交的變數,用指定的編碼轉成從parameters數組,以後直接從這個數組中擷取相應參數的值!
到處都使用UTF-8建議操作:
1, Set URIEncoding="UTF-8" on your <Connector> in server.xml.使得Tomcat Http Get請求使用UTF-8編碼
2, Use a character encoding filter with the default encoding set to UTF-8. 由於很多請求本身沒有指定編碼, Tomcat預設使用ISO-8859-1編碼作為HttpServletRequest的編碼,通過filter修改
3, Change all your JSPs to include charset name in their contentType. For example, use <%@page contentType="text/html; charset=UTF-8" %> for the usual JSP pages and <jsp:directive.page contentType="text/html; charset=UTF-8" /> for the pages in XML syntax (aka
JSP Documents). 指定Jsp頁面使用的編碼
4, Change all your servlets to set the content type for responses and to include charset name in the content type to be UTF-8. Use response.setContentType("text/html; charset=UTF-8") or response.setCharacterEncoding("UTF-8"). 設定Response返回結果的編碼
5, Change any content-generation libraries you use (Velocity, Freemarker, etc.) to use UTF-8 and to specify UTF-8 in the content type of the responses that they generate.指定所有模版引擎佘勇的編碼
6, Disable any valves or filters that may read request parameters before your character encoding filter or jsp page has a chance to set the encoding to UTF-8. SetCharacterEncodingFilter一般要放置在第一位,否則可能無效
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package filters;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* <p>Example filter that sets the character encoding to be used in parsing the
* incoming request, either unconditionally or only if the client did not
* specify a character encoding. Configuration of this filter is based on
* the following initialization parameters:</p>
* <ul>
* <li><strong>encoding</strong> - The character encoding to be configured
* for this request, either conditionally or unconditionally based on
* the <code>ignore</code> initialization parameter. This parameter
* is required, so there is no default.</li>
* <li><strong>ignore</strong> - If set to "true", any character encoding
* specified by the client is ignored, and the value returned by the
* <code>selectEncoding()</code> method is set. If set to "false,
* <code>selectEncoding()</code> is called <strong>only</strong> if the
* client has not already specified an encoding. By default, this
* parameter is set to "true".</li>
* </ul>
*
* <p>Although this filter can be used unchanged, it is also easy to
* subclass it and make the <code>selectEncoding()</code> method more
* intelligent about what encoding to choose, based on characteristics of
* the incoming request (such as the values of the <code>Accept-Language</code>
* and <code>User-Agent</code> headers, or a value stashed in the current
* user's session.</p>
*
* @author Craig McClanahan
* @version $Id: SetCharacterEncodingFilter.java 939521 2010-04-30 00:16:33Z kkolinko $
*/
public class SetCharacterEncodingFilter implements Filter {
// ----------------------------------------------------- Instance Variables
/**
* The default character encoding to set for requests that pass through
* this filter.
*/
protected String encoding = null;
/**
* The filter configuration object we are associated with. If this value
* is null, this filter instance is not currently configured.
*/
protected FilterConfig filterConfig = null;
/**
* Should a character encoding specified by the client be ignored?
*/
protected boolean ignore = true;
// --------------------------------------------------------- Public Methods
/**
* Take this filter out of service.
*/
public void destroy() {
this.encoding = null;
this.filterConfig = null;
}
/**
* Select and set (if specified) the character encoding to be used to
* interpret request parameters for this request.
*
* @param request The servlet request we are processing
* @param result The servlet response we are creating
* @param chain The filter chain we are processing
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// Conditionally select and set the character encoding to be used
if (ignore || (request.getCharacterEncoding() == null)) {
String encoding = selectEncoding(request);
if (encoding != null)
request.setCharacterEncoding(encoding);
}
// Pass control on to the next filter
chain.doFilter(request, response);
}
/**
* Place this filter into service.
*
* @param filterConfig The filter configuration object
*/
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
this.encoding = filterConfig.getInitParameter("encoding");
String value = filterConfig.getInitParameter("ignore");
if (value == null)
this.ignore = true;
else if (value.equalsIgnoreCase("true"))
this.ignore = true;
else if (value.equalsIgnoreCase("yes"))
this.ignore = true;
else
this.ignore = false;
}
// ------------------------------------------------------ Protected Methods
/**
* Select an appropriate character encoding to be used, based on the
* characteristics of the current request and/or filter initialization
* parameters. If no character encoding should be set, return
* <code>null</code>.
* <p>
* The default implementation unconditionally returns the value configured
* by the <strong>encoding</strong> initialization parameter for this
* filter.
*
* @param request The servlet request we are processing
*/
protected String selectEncoding(ServletRequest request) {
return (this.encoding);
}
}
參考:tomcat wiki faq Character Encoding Issues
Apache Tomcat Configuration Reference - The HTTP Connectorhttp://www.linuxso.com/architecture/20099.html
Tomcat的中文處理(二)
http://zhangguyou2009.blog.163.com/blog/static/34691638200711810485391/
Tomcat的中文處理(二)
2007-12-08 10:48:05| 分類: TOMCAT
|字型大小
訂閱
上篇我們介紹了tomcat是怎麼對接收到字元進行編碼的,現在我們來看當向用戶端寫html文檔的時候到底發生了什嗎?
tomcate在向用戶端寫出資料的時候,使用的是response的輸出資料流來實現的。
但是jsp是怎樣使用response的流的呢?
在使用JSP內含對象out輸出的時候,out是一個JspWriter實作類別的對象實 例,JspWriterImpl(ServletResponse response, int sz, boolean autoFlush)是一個該類的建構函式,其使用到了response,在JspWriterImpl內部還有一個java.io.Writer對象實 例的引用,在使用JspWriter(JSP的out對象)寫出資料的時候,會調用如下的函數來初始化
protected void initOut() throws IOException
{
if(out == null)
{
out = response.getWriter();/////////初始化 java.io.Writer對象
}
}來初始化該內部對象的。
然後在jspWriter的各個輸出資料的函數的實現中就是調用上面的java.io.Writer對象的方法的。
所以不論是jsp或者是servlet,對用戶端寫出html的時候,都是通過response.getWriter();來得到的字元流或者由getOutputStream()得到2進位流的。
一個response存在一個字元流,也存在一個2進制流,但是在同一時刻只能打開使用一個流的。至於兩者的關係,我們在後面介紹。Jsp的out對象就是response的字元流的。
同樣的request也存在一個字元流和一個2進制流,但是在同一時刻只能打開使用一個流的。
response的兩個流的關係
我們來考察response的實作類別的getOutputStream()和getWriter函數的實現:
public ServletOutputStream getOutputStream() throws IOException
{
。。。。。。。。。。。。。。。。。。。。。
stream = createOutputStream();///建立response的2進位的輸出資料流
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
return stream;
}
public PrintWriter getWriter() throws IOException
{
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
ResponseStream newStream = (ResponseStream)createOutputStream();////////建立2進位流
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding());
writer = new ResponseWriter(osr, newStream);///得到response的字元輸出資料流
。。。。。。。。。。。。。。。。。。。。。。。。。。
}
}
顯然,我們的字元流就是從2進位流轉化而來的
還有兩個函數要注意:
public String getCharacterEncoding()//////response的編碼,預設是ISO-8859-1的
{
if(encoding == null)//////////////////////////////////如果沒有指定編碼
{
return "ISO-8859-1";
} else
{
return encoding;
}
}
public void setContentType(String type);設定response的類型和編碼
{
。。。。。。。。。。。。。
encoding = RequestUtil.parseCharacterEncoding(type);////////得到指定的編碼
if(encoding == null)
{
encoding = "ISO-8859-1";//////////////////////////如果沒有指定編碼方式
}
} else
if(encoding != null)
{
contentType = type + ";charset=" + encoding;
}
}
好 了,現在我們知道了在寫出字元的時候使用的response的字元流(不管是jsp或者servlet),也就是使用的 OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding());
注意的是newStream是response的2進位流的實現。
所以我們還得看看OutputStreamWriter的實現:
考察OutputStreamWriter的源代碼,他有一個StreamEncoder 類型的對象,就是依靠他來轉換編碼的;
StreamEncoder是由sun公司提供的,它有一個
public static StreamEncoder forOutputStreamWriter(OutputStream outputstream, Object obj, String s)來得到StreamEncoder對象執行個體。
對於jsp,servlet來說在構造他的時候 outputstream參數是response的2進位流,obj是OutputStreamWriter對象,s就是編碼方式的名字。其實得到是一個StreamEncoder的子類的對象執行個體,
return new CharsetSE(outputstream, obj, Charset.forName(s1)); CharsetSE是StreamEncoder的子類。
他有一個如下的函數來實現編碼轉換的:
void implWrite(char ac[], int i, int j)throws IOException /////// ac是要輸出String的char數組
{
CharBuffer charbuffer = CharBuffer.wrap(ac, i, j);
。。。。。。。。。。。。。。。。。。。。。。。
CoderResult coderresult = encoder.encode(charbuffer, bb, false);/////bb是ByteBuffer,存放編碼後的byte緩衝區
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
writeBytes();///////////////////////////////將bb轉化到byte數組寫入到response的2進位流中
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
至此,我們瞭解了tomcat背後的編碼轉換過程