主要内容
浏览器使用HTTP协议传输数据,HTTP协议要求请求的请求行和消息头都是字符串,且使用的文字对应的字符集只能是ISO8859-1.这是一个欧洲的字符集,不支持中文字符。在GET提交表单时可以从URL中的参数看到中文字符对应的形式:
GET /myweb/login?username=%E8%8C%83%E4%BC%A0%E5%A5%87&password=123456
这里的 % 表示这是一个中文字符,百分号后面对应的是一个中文字符对应的16进制数。
服务器获取到后按照以上规则反向编译得到相应字符集对应的中文!
详解
以登录页面为例,当我们在输入框中输入中文后点击登录,浏览器地址栏上可能看到的参数还是中文,但实际传递到服务端时,我们
解析请求行的抽象路径得到参数时,样子如下:
/myweb/login?username=%E8%8C%83%E4%BC%A0%E5%A5%87&password=123456
注:我们在用户名输入框里输入的是中文"范传奇"
为什么?
原因:提交表单时,抽象路径会被包含在请求的请求行中。
例如
GET /myweb/login?username=%E8%8C%83%E4%BC%A0%E5%A5%87&password=123456 HTTP/1.1
HTTP协议要求请求的请求行和消息头都是字符串,且使用的文字对应的字符集只能是ISO8859-1.这是一个欧洲的字符集。不支持中文字符。
怎么办?
曲线救国,想办法将不支持的中文用ISO8859-1支持的字符描述并传递给服务端。
首先:
ISO8859-1支持数字。
那么我们可以在浏览器中先将中文用UTF-8编码转换为2进制。
GET /myweb/login?username=范&password=123456 HTTP/1.1 不要允许!!!
例如:
“范”----UTF-8---->11101000 10001100 10000011(3个字节)
将2进制的1和0表达中文进行传递:
GET /myweb/login?username=111010001000110010000011&password=123456 HTTP/1.1
虽然问题得以解决。副作用是:太长!!! 1中文字符就需要24个1或0的字符来表达。
怎么缩短长度?
解决办法:将2进制用16进制表达,仍然可以满足需求。因为16进制可以用字符’0’-‘9’和’A’-‘F’
2进制 10进制 16进制
0000 0 0
0001 1 1
0010 2 2
0011 3 3
0100 4 4
0101 5 5
0110 6 6
0111 7 7
1000 8 8
1001 9 9
1010 10 A
1011 11 B
1100 12 C
1101 13 D
1110 14 E
1111 15 F
换成16进制后的样子:
11101000 10001100 10000011 "范"的UTF-8编码内容
E8 8C 83
GET /myweb/login?username=E88C83&password=123456 HTTP/1.1
原来需要用24个’0’或’1’字符的组合,现在仅需要6个字符就可以表达一个中文了
长度问题尽可能解决了,但是新的问题:如何与实际的英文数组组合区分开呢?
例如:
一个用于注册时取名字就叫:E88C83
此时,提交表单,地址为:GET /myweb/login?username=E88C83&password=123456 HTTP/1.1
服务端接收到该内容时:如何理解:E88C83
是当它为16进制数据,换算为2进制后用UTF-8还原为"范"
还是本来就是E88C83
为了解决混淆问题,URL地址格式要求,如果英文数字组合表达的是16进制内容,则每两位16进制前必须添加一个’%’
因此,实际传递中文名时应该如下:
GET /myweb/login?username=%E8%8C%83&password=123456 HTTP/1.1
如果时本来的英文数字组合应该如下:
GET /myweb/login?username=E88C83&password=123456 HTTP/1.1
揣测一个问题.如果改用输入的名字就叫:%E8%8C%83
这里实际上像:‘%’,‘?’,‘=’,'&'这些字符都属于URL的关键字符。当我们输入该字符传递时,浏览器会将该字符对应的1字节内容也以"%XX"转换。
在服务端进行反向还原时,直接使用java提供的API:
URLDecoder.decode()就可以转换了。
decode:解码
代码中我们在HttpServletRequest的parseParameters方法中将参数解码即可