http response乱码的真相

 

http response乱码的真相

分类: Java   2271人阅读  评论(0)  收藏  举报

目录(?)[+]

这个是很久以前的笔记,最近遇到一个编码问题,重新把它翻出来了。

这个只和java servlet有关,现在通常都用各种框架,很少会直接用到Servlet了。


查看servlet源代码的方法

查看servlet源代码的方法。因为servlet只是一些接口,并不是真正的实现,所以,如果想看真正的代码。
要去下对应的服务器的实现的源代码。比如Tomcat的代码在这里:
http://archive.apache.org/dist/tomcat/tomcat-6/v6.0.33/src/


Servlet里的PrintWriter和ServletOutputStream

在servlet里有两种方法可以输出:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. PrintWriter writer = response.getWriter();  
  2. ServletOutputStream outputStream = response.getOutputStream();  

其中PrintWriter只提供了一系列的println函数,不能写二进制内容。其实这个是很合理的,下面会解释原因。
ServletOutputStream则有println系列函数和wirte系列函数。


当使用ServletOutputStream来输出中文字符,则会出现设置了CharacterEncoding,而无效的情况。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. response.setCharacterEncoding("utf-8");  //这句话并不能解决编码问题  
  2. ServletOutputStream outputStream = response.getOutputStream();  
  3. outputStream.println("中文");  
我们在浏览器上,可以查看页面编码,可以发现的确是utf-8编码,但是为什么response.setCharacterEncoding("utf-8"),而还是乱码?


真正的罪人是ServletOutputStream,它根本没有实现编码转换。我们可以看下它是怎样实现的:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.   public void print(String s) throws IOException {  
  2. if (s==null) s="null";  
  3. int len = s.length();  
  4. for (int i = 0; i < len; i++) {  
  5.     char c = s.charAt (i);  
  6.   
  7.     //  
  8.     // XXX NOTE:  This is clearly incorrect for many strings,  
  9.     // but is the only consistent approach within the current  
  10.     // servlet framework.  It must suffice until servlet output  
  11.     // streams properly encode their output.  
  12.     //  
  13.     if ((c & 0xff00) != 0) {    // high order byte must be zero  
  14.     String errMsg = lStrings.getString("err.not_iso8859_1");  
  15.     Object[] errArgs = new Object[1];  
  16.     errArgs[0] = new Character(c);  
  17.     errMsg = MessageFormat.format(errMsg, errArgs);  
  18.     throw new CharConversionException(errMsg);  
  19.     }  
  20.     write (c);  
  21. }  
  22.    }  

很明显,它根本没有进行编码转换:XXX NOTE:  This is clearly incorrect for many strings。。

我们再用PrintWriter来输出:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. response.setCharacterEncoding("utf-8");  
  2. PrintWriter writer = response.getWriter();  
  3. writer.println("中文");  

我们可以在浏览器上查看,页面编码是utf-8,则显示是正确的中文字符。

我们再看看PrintWriter是怎样工作的:
在Tomcat中PrintWriter实际上是org.apache.catalina.connector.CoyoteWriter类,

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public void print(String s) {  
  2.     if (s == null) {  
  3.         s = "null";  
  4.     }  
  5.     write(s);  
  6. }  
  7.   
  8. public void write(String s, int off, int len) {  
  9.   
  10.     if (error)  
  11.         return;  
  12.   
  13.     try {  
  14.         ob.write(s, off, len);  
  15.     } catch (IOException e) {  
  16.         error = true;  
  17.     }  
  18.   
  19. }  
  20. public void write(String s) {  
  21.     write(s, 0, s.length());  
  22. }  
  23. public void write(String s, int off, int len) {  
  24.   
  25.     if (error)  
  26.         return;  
  27.   
  28.     try {  
  29.         ob.write(s, off, len);   //ob是org.apache.catalina.connector.OutputBuffer类  
  30.     } catch (IOException e) {  
  31.         error = true;  
  32.     }  
  33.   
  34. }  

org.apache.catalina.connector.OutputBuffer类中的write函数:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.     public void write(String s, int off, int len)  
  2.         throws IOException {  
  3.   
  4.         if (suspended)  
  5.             return;  
  6.   
  7.         charsWritten += len;  
  8.         if (s == null)  
  9.             s = "null";  
  10. //这里进行编码转换,conv的声明:protected C2BConverter conv;  
  11. //在调试过程中可以看到C2BConverter中的存放的正是utf-8编码。  
  12.         conv.convert(s, off, len);    
  13.         conv.flushBuffer();  
  14.   
  15.     }  

至此,我们终于找到了真相。PrintWriter会在底层把字符串的编码转换为对应的CharacterEncoding的编码。
这也就是为什么PrintWriter没有提供wirte系列函数的原因。
BTW:怎样用ServletOutputStream来输出我们想要的编码字符串?
在刚才的代码中,我们可以看到ServletOutputStream的pirnt系列函数实际上什么转换工作都没有做。所以我们可以先把字符串转换成想要的编码,再写到ServletOutputStream中。
如:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. response.setCharacterEncoding("utf-8");    
  2. ServletOutputStream outputStream = response.getOutputStream();  
  3. PrintStream printStream = new PrintStream(outputStream);  
  4. printStream.write("中文".getBytes("utf-8"));  

tomcat里一劳永逸解决乱码问题

要想在tomcat中一劳永逸解决乱码问题,可以这样做:

1.设置tomcat,conf/server.xml文件中,useBodyEncodingForURI="true":

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <Connector port="8080" protocol="HTTP/1.1"   
  2.            connectionTimeout="20000"   
  3.            redirectPort="8443" useBodyEncodingForURI="true"/>  
2.增加一个filter:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class CodeFilter implements Filter {  
  2.     @Override  
  3.     public void init(FilterConfig filterConfig) throws ServletException {     
  4.     }  
  5.     @Override  
  6.     public void doFilter(ServletRequest request, ServletResponse response,  
  7.             FilterChain chain) throws IOException, ServletException {  
  8.         request.setCharacterEncoding("utf-8");  
  9.         response.setCharacterEncoding("utf-8");  
  10.         chain.doFilter(request, response);  
  11.     }  
  12.     @Override  
  13.     public void destroy() {   
  14.     }  
  15. }  
3.在web.xml中配置filter:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <filter>  
  2.     <filter-name>CodeFilter</filter-name>  
  3.     <filter-class>com.leg.filter.CodeFilter</filter-class>  
  4. </filter>  
  5.   
  6. <filter-mapping>  
  7.     <filter-name>CodeFilter</filter-name>  
  8.     <url-pattern>*</url-pattern>  
  9. </filter-mapping>  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值