文章目录
本文中示例,在fixfore浏览器下测试通过。
总结
URI路径
部分,不论是在浏览器地址栏中直接输入,还是在<form>
标签的action
属性中指定,其中文部分都会使用UTF-8
编码;
URI查询参数
部分,有所不同。在浏览器地址栏中直接输入时,会使用UTF-8
编码;在标签的action属性中指定时,取决于其content-type
中指定的编码格式。
POST请求主体
部分,取决于其content-type
中指定的编码格式。
服务器接收
下面进行具体测试。
浏览器地址栏中的中文
直接在浏览器地址栏中输入含中文网址,比如
http://localhost:8080/hello-user/greeting/山东?user=山东
时,浏览器请求头分析如下:
也就是说,对于网址中的非ASCII字符先使用UTF-8
编码,并以16进制字符串表示,然后再在每个字节前加上%
。比如,中文字符串山东
的十六进制形式的UTF-8
编码是E5B1B1E4B89C
。
http://localhost:8080/hello-user/greeting/%E5%B1%B1%E4%B8%9C?user=%E5%B1%B1%E4%B8%9C
form
表单POST
请求中的中文
比如,
<form action="greeting/山东?user=山东" method="POST">
Enter your name:<br>
<input type="text" name="user" value="山东"><br>
<input type="submit" value="Submit">
</form>
content-type指定ISO-8859-1
编码时
浏览器发送请求前,content-type
指定编码方式是ISO-8859-1
,请求头如下:
浏览器发送请求后,请求头如下:
也就是说,
对于网址中的URI
路径部分,处理方式与直接在浏览器地址栏中输入网址的处理方式一样。
对于网址中的URI
查询参数部分,先对其中的非ISO-8859-1
字符转成用&#...;
包裹的十进制形式的unicode
内码点,比如中文字符串山东
,会转成山东
,再将该内码点形式中的&#;
以16进制字符串表示,然后再在每个字节前加上%
,比如,%26%2323665%3B%26%2319996%3B
。
对于请求主体部分,编码方式与URI
查询参数部分一致。
content-type指定GBK
编码时
浏览器发送请求前,content-type
指定编码方式是GBK
,请求头如下:
发送POST请求后,请求头如下:
也就是说,
对于网址中的URI
路径部分,处理方式与直接在浏览器地址栏中输入网址的处理方式一样。
对于网址中的URI
查询参数部分,先使用GBK
编码,并以16进制字符串表示,然后再在每个字节前加上%
。比如,中文字符串山东
的十六进制形式的GBK
编码是C9BDB6AB
。
对于请求主体部分,编码方式与URI
查询参数部分一致。
content-type指定UTF-8
编码时
浏览器发送请求前,content-type
指定编码方式是UTF-8
,请求头如下:
发送POST请求后,请求头如下:
也就是说,
对于网址中的URI
路径部分,URI
查询参数部分,以及请求主体部分,三者的处理方式都与直接在浏览器地址栏中输入网址的处理方式一样。
form
表单GET
请求中的中文
与<form>
表单POST
请求中的中文处理方式一致。不再赘述。
附录
获得中文的指定编码格式的十六进制形式
- 引入jar包
<dependency>
<groupId>ru.d-shap</groupId>
<artifactId>hex</artifactId>
<version>1.2</version>
</dependency>
- 测试使用
String original = "山东";
System.out.println(HexHelper.toHex(original.getBytes("GB2312"), true));
System.out.println(HexHelper.toHex(original.getBytes("GBK"), true));
System.out.println(HexHelper.toHex(original.getBytes("UTF-8"), true));
System.out.println(HexHelper.toHex(original.getBytes("UTF-16"), true));
System.out.println(HexHelper.toHex(original.getBytes("UTF-32"), true));
第一个参数是字符串在不同编码格式下对应的字节数组,第二个参数用来选择是否将十六进制字符串转成大写形式。
结果如下,
C9BDB6AB <-GB2312
C9BDB6AB <-GBK
E5B1B1E4B89C <-UTF-8
FEFF5C714E1C <-UTF-16
00005C7100004E1C <-UTF-32
原理很简单,自己查jar包源码,不再赘述。
中文字符与其unicode内码点相互转换
- 引入jar包
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
- 字符转
\uxxx
形式十六进制内码点字符串
// 字符转`\uxxx`形式内码点字符串
System.out.println(CharUtils.unicodeEscaped('山'));
结果是,\u5c71
,\u4e1c
3. 字符串转int形式十进制内码点数组
int[] codePoints = StringUtils.toCodePoints("山东");
for (int i : codePoints) {
System.out.println(i);
}
结果是,23665
、19996
4. 内码点转字符
// 直接转
char s = (char)23665;
System.out.println(s);
char d = '\u4e1c';
System.out.println(d);
// 字符串转(有时候原始数据是字符串形式)
int s10 = Integer.parseInt("23665");
System.out.println((char)s10);
String d2 = "\u4e1c"
System.out.println(d2 );//也可以直接输出
模拟浏览器中请求体内中文参数
在ISO-8859-1
时编码
山东
转 山东
,再转%26%2323665%3B%26%2319996%3B
private static String String2Unicode(String str) throws UnsupportedEncodingException {
StringBuilder sb = new StringBuilder();
for (int i : StringUtils.toCodePoints(str)) {
sb.append("&#").append(i).append(";");
}
String encode = URLEncoder.encode(sb.toString(), "ISO-8859-1");
System.out.println(str+ " : " + sb + " : " + encode);
return encode;
}
%26%2323665%3B%26%2319996%3B
转 山东
,再转山东
private static String unicode2String(String str) throws UnsupportedEncodingException {
String decode = URLDecoder.decode(str, "ISO-8859-1");
StringBuilder sb = new StringBuilder();
for (String s : StringUtils.split(decode, ";")) {
int i = Integer.parseInt(StringUtils.substring(s, 2));
sb.append((char)i);
}
System.out.println(str + " : " + decode + " : " + sb);
return sb.toString();
}