谈谈服务器的编码问题

本文详细介绍了Tomcat服务器处理HTTP请求参数的过程,包括GET和POST请求的参数如何被解析及编码转换,强调了正确设置字符编码的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考tomcat-5.5.20

1)核心流程涉及到的几个类


1.1)org.apache.catalina.connector.Request
(这个就是我们在servlet中看到的HttpServletRequest的tomcat实现,有一个非常常见的属性ParameterMap parameterMap)

1.2)org.apache.coyote.Request
(原始的tomcat请求数据结构)

1.3)org.apache.tomcat.util.http.Parameters
(我们通过http传递的参数的数据结构)

注意:org.apache.tomcat.util.http.Parameters有非常重要的几个属性:

  • String encoding:

用于post数据的编码

  • String queryStringEncoding:

用于get数据的编码

  • MessageBytes queryMB:

原始的get请求部分的字节

  • CoyoteInputStream inputStream

原始的post请求部分的字节(实际上来自coyoteRequest[org.apache.coyote.Request的实例]的inputBuffer)

  • MessageBytes decodedQuery:

在对请求字节进行解码时,会拷贝queryMB即decodedQuery是queryMB 涉及请求参数那部分字节的拷贝
这里所说的解码会在下面的请求过程中详细描述

  • Hashtable paramHashStringArray:

把解码后的字节(包括key和value)转为字符保存在hashtable 中,编码的选取规则如下:

get请求对应queryStringEncoding,post对应encoding
paramHashStringArray也是HttpServletRequest.getParameterMap的来源

3者的关系如下:
1)包含 2),2)包含 3)

见下图:

2)请求过程


2.1) 浏览器发起请求比如post部分:x=a, get部分:para=%C6%B1%CE%D6%B6%FB(“票沃尔"的gbk url编码)

2.2) tomcat接到请求会填充org.apache.coyote.Request和org.apache.tomcat.util.http.Parameters

此时

  • get部分的数据存入的上文提到的queryMB为

[112, 97, 114, 97, 61, 37, 67, 54, 37, 66, 49, 37, 67, 69, 37, 68, 54, 37, 66, 54, 37, 70, 66]

(对应请求para=%C6%B1%CE%D6%B6%FB,对照ascii码表可以看出%C6%B1%CE%D6%B6%FB即

[37, 67, 54, 37, 66, 49, 37, 67, 69, 37, 68, 54, 37, 66, 54, 37, 70, 66])

  • post部分的数据存入上文提到的coyoteRequest的inputBuffer中

2.3) tomcat此时还不会解析参数,什么时候解析依赖web应用程序

如果直接使用servlet或者jsp,通常会用到 request.getParameter("para"), 此时会触发解析过程, 目前web应用程序大量的使用

web框架比 如struts等,那么框架会负责触发解析过程,比如调用request.getParameterMap()得到Map,并将Map中

的key-value以框 架特有的方式传递给应用程序的action之类的东东,比如struts2会将Map中的数据通过valuestack注入

到action中的属性

2.4) 参数解析(详见Parameters.parseParameters)

2.4.1)解析get部分请求的参数
2.4.1.1) 定位%和+
2.4.1.2) 将+变为空格,将%后面连续的两个字符 转为字节(通过x2c方法)
于是上文中的[37, 67, 54, 37, 66, 49, 37, 67, 69, 37, 68, 54, 37, 66, 54, 37, 70, 66]
在解码之后变成 :[-58, -79, -50, -42, -74, -5](这就是"票沃尔"的gbk字节)
2.4.1.3)将byte传为字符paramHashStringArray会有key-value即{para:票沃尔},在字符过程会使用编码,
这个编码来自与服务器的设置,比如<connector ..... URIEncoding="GBK" />,tomcat通常会有2个connector,

一个http,一个ajp, 使用哪个取决于服务器的架构。

2.4.2)解析post部分请求的参数,解析过程与get请求一样,详见Parameters.processParameters方法
不同点在于

a) 字节来源

如上文中提到到,post部分的请求数据取自coyoteRequest的inputBuffer,get来自queryMB

b) 字符编码

post编码与服务器设置的那个URIEncoding无关,具体是什么取决与使用

的框架或者filter之类 , 比如框架或者filter之类常常会在触发解析之前调用Request.setCharacterEncoding方法,

就是设置上文中提到的encoding属性,而post参数正是依赖这个encoding属性


注意:Request.setCharacterEncoding设置的是原始org.apache.coyote.Request的characterEncoding,而Parameters

的 encoding 属性取自org.apache.coyote.Request的charEncoding,所以Request.setCharacterEncoding

间接的设置了Parameters的encoding


2.5)将解析好的参数(包括get部分和post部分)以key(字符)-value(字符)的形式写入paramHashStringArray


2.6)将paramHashStringArray拷贝到Request的parameterMap


2.7)parameterMap----》框架或者应用程序

见下图:

3) 总结

3.1)整个过程就是a)服务器获得参数----》b)框架/servlet/filter设置编码(调用Request.setCharacterEncoding方法)

----》c)触发参数解析过程-----》d)应用程序处理参数

3.2)整个过程需保持客户端/浏览器编码和服务器编码一致,否者会会出现乱码(废话)

3.2.1)对于客户端/浏览器来说,如果是get部分,必须显式的编码即urlencode,此编码需和服务器端的URIEncoding保持一致,

如果没办法保持一致,需特殊处理可以考虑在filter框架中调用

request.getCoyoteRequest().getParameters().setQueryStringEncoding("xxencoding")

参考CoyoteAdapter.service方法中的代码“req.getParameters().setQueryStringEncoding(connector.getURIEncoding());”

3.2.2)如果post那部分请求,此时浏览器如何编码,看见网上很多人这么说:
“html文件里如果有段<meta http-equiv="Content-Type" content="text/html; charset=字符集(GBK,utf-8等)"/>,

那么post就 会用此处指定的编码方式编码”
其实不完全是这样,浏览器编码取决获得此页面时的该页面字节编码,比如生成此页面的response流是utf-8,

就算页面有<meta http-equiv="Content-Type" content="text/html; charset=gbk"/> post还是会采用utf-8的编码,
一个很简单的测试方法就是,将某个html保存为utf-8格式,用浏览器打开此页面发起post,

你会看到http header 信息中post数 据的确是utf-8,而不管你得页面的那个<meta http-equiv ....../>
对付post的方式就是要正确的设置Request.setCharacterEncoding方法

3.3) 请必须确保Request.setEncoding在参数解析(request.getParameter/request.getParameterMap等)过程之前,

否则无效,因为通常情况下参数解析就发生一次

3.4)不要以为utf-8和gbk可以相互转化
网上经常会看到这样的代码new String(request.getParameter("name").getBytes("iso-8859-1"),"utf-8或者gbk之类"),

这个确实可行,但如果
new String(request.getParameter("name").getBytes("utf-8"),"gbk")或者反之就难说了,这个取决jdk的编码转化细节

可以做个小测试:

  • utf-8---->gbk

代码:

byte[] bytes="票沃尔".getBytes("utf-8");
System.out.println("byte-utf8:"+Arrays.toString(bytes));
System.out.println("byte-utf8:"+Arrays.toString(new String(bytes,"gbk").getBytes("gbk")));

输出:
[-25, -91, -88, -26, -78, -125, -27, -80, -108]
[-25, -91, -88, -26, -78, -125, -27, -80, 63]

当utf-8被误认为gbk后,再想还原就困难了,此时最后一个字节变成了63(ascii码对应?),导致无法还原,
这是因为对汉字来说,utf-8 是3字节,gbk 2字节,3个汉字本来是9个字节,被gbk传为字符后硬凑了一个字节,变成了5个字符,10个字节
有意思的是如果是偶数个汉字是可以复原的

  • gbk---->utf-8

还是那3个汉字,代码雷同
本来是[-58, -79, -50, -42, -74, -5],后来居然变成了[-58, -79, -17, -65, -67, -42, -74, -17, -65, -67]

所以如果你以为客户端gbk/utf-8,服务端utf-8/gbk,然后再做个恢复可以,那么就出问题了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值