http://www.xuebuyuan.com/1287083.html

***********************************

经常会被乱码问题搅得头晕脑胀。事实上,乱码问题涉及的地方比较多,所以常常有了问题也很难定位,比如,可以发生在容器,可以发生在MVC框架,可以发生在数据库,可以发生在响应等等。

这里分析一下tomcat中是如何编解码的。

以"http://localhost:8080/测试?网络=编程"为例,可以将tomcat中编解码分解为这么几个地方:

1. pathInfo.即“测试”这个部分

2. queryParameter,即”网络=编程“这个部分

3. http header,即浏览器发送的http头部分

4. requestBody,http正文部分,即post的正文部分

1. pathInfo,Http11Processor中的process方法会调用InternelInputBuffer来解析请求 URL(inputBuffer.parseRequestLine)以及请求头(inputBuffer.parseHeaders),但是这里并不是 解码的地方。

public void process(Socket theSocket)
throws IOException {
...
inputBuffer.parseRequestLine();
request.setStartTime(System.currentTimeMillis());
keptAlive = true;
if (disableUploadTimeout) {
socket.setSoTimeout(soTimeout);
} else {
socket.setSoTimeout(timeout);
}
// Set this every time in case limit has been changed via JMX
request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
inputBuffer.parseHeaders();
...
}

真正解码的地方是CoyoteAdapter的convertURI

protected void convertURI(MessageBytes uri, Request request)
throws Exception { ByteChunk bc = uri.getByteChunk();
int length = bc.getLength();
CharChunk cc = uri.getCharChunk();
cc.allocate(length, -1); String enc = connector.getURIEncoding();
if (enc != null) {
B2CConverter conv = request.getURIConverter();
try {
if (conv == null) {
conv = new B2CConverter(enc);
request.setURIConverter(conv);
}
} catch (IOException e) {
// Ignore
log.error("Invalid URI encoding; using HTTP default");
connector.setURIEncoding(null);
}
if (conv != null) {
try {
conv.convert(bc, cc);
uri.setChars(cc.getBuffer(), cc.getStart(),
cc.getLength());
return;
} catch (IOException e) {
log.error("Invalid URI character encoding; trying ascii");
cc.recycle();
}
}
} // Default encoding: fast conversion
byte[] bbuf = bc.getBuffer();
char[] cbuf = cc.getBuffer();
int start = bc.getStart();
for (int i = 0; i < length; i++) {
cbuf[i] = (char) (bbuf[i + start] & 0xff);
}
uri.setChars(cbuf, 0, length); }

而这里的解码使用的是connector的URIEncoding,所以pathInfo的解码可以通过配置server.xml中的URIEncoding来改变。

2. queryParameter部分,这里其实有几个地方可以控制,首先,我们还是找到解码queryParameter的地方。在调用 request.getParameter时最终会调用到coyote内部的Parameter中的handleQueryParameters方法,可 以看到这里的queryStringEncoding。

public void handleQueryParameters() {
if( didQueryParameters ) return; didQueryParameters=true; if( queryMB==null || queryMB.isNull() )
return; if(log.isDebugEnabled()) {
log.debug("Decoding query " + decodedQuery + " " +
queryStringEncoding);
} try {
decodedQuery.duplicate( queryMB );
} catch (IOException e) {
// Can't happen, as decodedQuery can't overflow
e.printStackTrace();
}
processParameters( decodedQuery, queryStringEncoding );
}

queryStringEncoding是由什么地方决定的呢?事实上,有几个地方决定。第一个是CoyoteAdapter中的service方法,另 外就是FormAuthenticator,这两个地方都使用了connector.getURIEncoding()。

public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res)
throws Exception { if (request == null) { ... // Set query string encoding
req.getParameters().setQueryStringEncoding
(connector.getURIEncoding());
}
}

也就是说跟pathInfo是一样的,但是千万不要以为就这样了,其实还有另一个地方会让整个事情变得很奇怪。在调用 request.getParameter时,事实上会先调用parseParameters方法,然后才调用 handleQueryParameters,而parseParameters就是第三个设置queryStringEncoding的地方。 getCharacterEncoding首先会去找request中设置的charEncoding,找不到就去找requestHeader中 contentType的编码,还找不到就返回null,这时如果在server.xml中设置了 useBodyEncodingForURI=true,则queryStringEncoding编码就会变成默认编码,即IS08859-1;而考虑 另一种情况,如果contentType能找到这个编码(如UTF-8),则queryStringEncoding跟随contentType。

所以,结论是,queryStringEncoding编码的优先级是,第一是随contentType,第二随URIEncoding(即没有设 置contentType编码,同时也没有设置useBodyEncodingForURI),第三则是默认编码(即没有设置contentType,设 置了useBodyEncodingForURI=true)

protected void parseParameters() {

        parametersParsed = true;

        Parameters parameters = coyoteRequest.getParameters();
// Set this every time in case limit has been changed via JMX
parameters.setLimit(getConnector().getMaxParameterCount()); // getCharacterEncoding() may have been overridden to search for
// hidden form field containing request encoding
String enc = getCharacterEncoding(); boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
if (enc != null) {
parameters.setEncoding(enc);
if (useBodyEncodingForURI) {
parameters.setQueryStringEncoding(enc);
}
} else {
parameters.setEncoding
(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
if (useBodyEncodingForURI) {
parameters.setQueryStringEncoding
(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
}
} }

3. httpheader, 在InternalInputBuffer的parseHeader中解析,最终会调用到ByteChunk的toStringInternal,里面用 到的是DEFAULT_CHARSET,这个默认字符集就是ISO8859-1,意味着不能更改httpheader

public String toStringInternal() {
if (charset == null) {
charset = DEFAULT_CHARSET;
}
// new String(byte[], int, int, Charset) takes a defensive copy of the
// entire byte array. This is expensive if only a small subset of the
// bytes will be used. The code below is from Apache Harmony.
CharBuffer cb;
cb = charset.decode(ByteBuffer.wrap(buff, start, end-start));
return new String(cb.array(), cb.arrayOffset(), cb.length());
}

4. post中的参数正是上面解析queryStringEncoding中的parameters,也就是说post请求仍然是contentType中的编码方式优先,其次就是默认的ISO8859-1。

到这里,tomcat的编码基本上算是分析完了。但是编码问题涉及的点太多,比如数据库,可以修改数据库的编码或者jdbc连接时指定编码;比如一些框 架,如springmvc中的ResponseBody就硬编码了ISO8859-1,可以换用ResponseEntity,或者 Response.getWriter直接输出。总之,查到什么地方有问题,才能对症下药。

搞清tomcat中的编解码的更多相关文章

  1. Java 字符编码(二)Java 中的编解码

    Java 字符编码(二)Java 中的编解码 java.nio.charset 包中提供了一套处理字符编码的工具类,主要有 Charset.CharsetDecoder.CharsetEncoder. ...

  2. Java 字符编码(三)Reader 中的编解码

    Java 字符编码(三)Reader 中的编解码 我们知道 BufferedReader 可以将字节流转化为字符流,那它是如何编解码的呢? try (BufferedReader reader = n ...

  3. python3中的编解码

    #一个知识点是:python3中有两种字符串数据类型:str类型和 bytes类型:sty类型存储unicode数据,bytes类型存储bytes数据 #当我们在word上编辑文件的时候,数据保存之前 ...

  4. python中的编解码小结

    在用python27写文件或者上传文件时遇到这样一个问题:.在网上搜了下说加入以下三行代码可以解决: import sys reload(sys) sys.setdefaultencoding('ut ...

  5. Java Web中涉及的编解码

    用户从浏览器发起一个HTTP请求,存在编码的地方是URL.Cookie.Paramiter.服务器端接收到HTTP请求后要解析HTTP协议,其中URL.Cookie和POST表单参数要解码,服务器端可 ...

  6. 【转】Java web 编解码

    几种常见的编码格式 为什么要编码 不知道大家有没有想过一个问题,那就是为什么要编码?我们能不能不编码?要回答这个问题必须要回到计算机是如何表示我们人类能够理解的符号的,这些符号也就是我们人类使用的语言 ...

  7. JSP与Servlet的编解码

    一.java web中涉及编解码的地方 (1)浏览器端向后台发起请求时:URL.Cookie.Parameter: (2)后台响应返回数据时:页面编码,数据库数据编码:

  8. 【MINA】用protobuf做编解码协议

    SOCKET协议 支持java serial 与 AMF3的混合协议,目前没有基于xml 与 json的实现. 协议说明: * 9个字节协议头+协议体. * * 协议头1-4字节表示协议长度 =协议体 ...

  9. Nodejs进阶:服务端字符编解码&乱码处理

    写在前面 在web服务端开发中,字符的编解码几乎每天都要打交道.编解码一旦处理不当,就会出现令人头疼的乱码问题. 不少从事node服务端开发的同学,由于对字符编码码相关知识了解不足,遇到问题时,经常会 ...

随机推荐

  1. Unity 文字爆炸(风化)消失效果 粒子系统应用

    利用Unity的粒子系统,使用C#代码控制粒子的位置和速度,实现文字风化爆炸的效果. Unity的东西,不像flash,不能直接放到网页中,没办法了,只能放截图了.有兴趣的可以下载看看:text_ex ...

  2. FPGA视频拼接器的DP 的4k 功能输入, 把lvds 转为 serdes

    4k(3840 x 2160 @60hz)越来越成为一种视频趋势,怎样把4K 接入到拼接器中.是一个棘手的问题. 先看一下原理图的接口: 这是 anx1122的 lvds的接口. 以下看一下系统的框架 ...

  3. 查询SQL存储过程创建时间

    select  [name] ,create_date ,modify_date FROM  sys.all_objects where  type_desc = N'SQL_STORED_PROCE ...

  4. JDBC实例--JDBC连接池技术解密,连接池对我们不再陌生

    一.为什么我们要用连接池技术? 前面的数据库连接的建立及关闭资源的方法有些缺陷.统舱传统数据库访问方式:一次数据库访问对应一个物理连接,每次操作数据库都要打开.关闭该物理连接, 系统性能严重受损. 解 ...

  5. Windwos在cmd如何复制文本

    生活的琐事,总是要解决. 01.Win+R打开运行窗口 cmd--回车 02. 勾选快速编辑模式 注意: 快速编辑模式就是可以Ctrl+c(复制).Ctrl+v(粘贴)

  6. CentOS 6.5上使用gdb调试时出现Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.i686 .

    在CentOS6.5上用gdb调试时提示Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.i686先修改 ...

  7. bss段,代码段及数据段,堆栈段的区别

    bss段,代码段及数据段,堆栈段的区别 时间:2012-11-21 10:0772人阅读 BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域.BSS是英 ...

  8. iOS利用SDWebImage实现缓存的计算与清理

    概述 可以仅仅清理图片缓存, 也可以清理所有的缓存文件(包括图片.视频.音频等). 详细 代码下载:http://www.demodashi.com/demo/10717.html 一般我们项目中的缓 ...

  9. Mysql 5.7 从节点配置多线程主从复制

    Mysql 采用多线程进行复制是从 Mysql 5.6 开始支持的内容,但是 5.6 版本下有缺陷,虽然支持多线程,但是每个数据库只能一个线程,也就是说如果我们只有一个数据库,则主从复制时也只有一个线 ...

  10. 【转载】delphi下如何复制文件

    1. CopyFile(PChar(源目录),PChar(目标目录),True); CopyFileTo('F:\MyProject\delphi\message\data\data.mdb','c: ...