http response乱码的真相

简介:

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

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


查看servlet源代码的方法

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


Servlet里的PrintWriter和ServletOutputStream

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

PrintWriter writer = response.getWriter();
ServletOutputStream outputStream = response.getOutputStream();

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


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

response.setCharacterEncoding("utf-8");  //这句话并不能解决编码问题
ServletOutputStream outputStream = response.getOutputStream();
outputStream.println("中文");
我们在浏览器上,可以查看页面编码,可以发现的确是utf-8编码,但是为什么response.setCharacterEncoding("utf-8"),而还是乱码?


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

   public void print(String s) throws IOException {
	if (s==null) s="null";
	int len = s.length();
	for (int i = 0; i < len; i++) {
	    char c = s.charAt (i);

	    //
	    // XXX NOTE:  This is clearly incorrect for many strings,
	    // but is the only consistent approach within the current
	    // servlet framework.  It must suffice until servlet output
	    // streams properly encode their output.
	    //
	    if ((c & 0xff00) != 0) {	// high order byte must be zero
		String errMsg = lStrings.getString("err.not_iso8859_1");
		Object[] errArgs = new Object[1];
		errArgs[0] = new Character(c);
		errMsg = MessageFormat.format(errMsg, errArgs);
		throw new CharConversionException(errMsg);
	    }
	    write (c);
	}
    }

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

我们再用PrintWriter来输出:

response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();
writer.println("中文");

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

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

    public void print(String s) {
        if (s == null) {
            s = "null";
        }
        write(s);
    }

    public void write(String s, int off, int len) {

        if (error)
            return;

        try {
            ob.write(s, off, len);
        } catch (IOException e) {
            error = true;
        }

    }
    public void write(String s) {
        write(s, 0, s.length());
    }
    public void write(String s, int off, int len) {

        if (error)
            return;

        try {
            ob.write(s, off, len);   //ob是org.apache.catalina.connector.OutputBuffer类
        } catch (IOException e) {
            error = true;
        }

    }

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

    public void write(String s, int off, int len)
        throws IOException {

        if (suspended)
            return;

        charsWritten += len;
        if (s == null)
            s = "null";
//这里进行编码转换,conv的声明:protected C2BConverter conv;
//在调试过程中可以看到C2BConverter中的存放的正是utf-8编码。
        conv.convert(s, off, len);  
        conv.flushBuffer();

    }

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

response.setCharacterEncoding("utf-8");  
ServletOutputStream outputStream = response.getOutputStream();
PrintStream printStream = new PrintStream(outputStream);
printStream.write("中文".getBytes("utf-8"));

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

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

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

    <Connector port="8080" protocol="HTTP/1.1" 
               connectionTimeout="20000" 
               redirectPort="8443" useBodyEncodingForURI="true"/>
2.增加一个filter:

public class CodeFilter implements Filter {
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {	
	}
	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		request.setCharacterEncoding("utf-8");
		response.setCharacterEncoding("utf-8");
		chain.doFilter(request, response);
	}
	@Override
	public void destroy() {	
	}
}
3.在web.xml中配置filter:

	<filter>
		<filter-name>CodeFilter</filter-name>
		<filter-class>com.leg.filter.CodeFilter</filter-class>
	</filter>
	
	<filter-mapping>
		<filter-name>CodeFilter</filter-name>
		<url-pattern>*</url-pattern>
	</filter-mapping>



目录
相关文章
HTTP request以及response原理 request请求消息数据
HTTP request以及response原理 request请求消息数据
|
存储 开发框架 .NET
ASP.NET中HTTP请求(Request)、响应(Response)以及状态管理讲解
ASP.NET中HTTP请求(Request)、响应(Response)以及状态管理讲解
ASP.NET中HTTP请求(Request)、响应(Response)以及状态管理讲解
HTTP协议:响应消息和 Response对象和ServletContext对象
HTTP协议:响应消息和 Response对象和ServletContext对象
|
API
Swoole v4.6 版本新特性之 Http\Response 增强
在 4.6 版本中,对 Swoole\Http\Response 进行了一些增强
210 0
|
JSON 前端开发 数据格式
|
Web App开发
Http Response Code
HTTP协议状态码表示的意思主要分为五类 ,大体是 :   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~   1××   保留    2××   表示请求成功地接收    3××   为完成请求客户需进一步细化请求    4××   客户错误    5××   服务器错误     ---------------------------------------------------------------     100 Continue 初始的请求已经接受,客户应当继续发送请求的其余部分。
1203 0
|
Web App开发 数据格式
谈一谈Http Request 与 Http Response
原文:谈一谈Http Request 与 Http Response 写在前面的话:最近帮朋友弄弄微信商城,对于微信的基础开发,基本上就是各种post、get,有时是微信服务器向我们的服务器post、get数据,有时需要我们自己的服务器向微信服务器各种post、get,之间通过json或者xml传送数据。
1367 0
|
Web App开发 存储
Http服务器如何在HTTP response中传送二进制图片
要想知道如何传送这些二进制,先来点二进制文件的背景知识    —文件魔数 magic number: 操作系统的文件,其起始的几个字节的内容是固定的。
1270 0
|
应用服务中间件 Java
http response乱码的真相
这个是很久以前的笔记,最近遇到一个编码问题,重新把它翻出来了。 这个只和java servlet有关,现在通常都用各种框架,很少会直接用到Servlet了。
1005 0