http request乱码的真相

本文深入探讨了HTTP请求中的编码问题,包括浏览器、Web服务器如何处理不同类型的请求编码,以及如何配置和解决可能出现的乱码问题。

当然,终极原因http协议里没有规定request一定要指定编码,导致浏览器,web服务器都各搞一套……

下面一一理清。

首先,从浏览器端看下有多少种情况:

1.在浏览器的地址栏,或者搜索框里输入地址:http://www.test.com/衣服/search?keyword=T恤 

2.在一个指定了编码的网页中,提交一个form,如:

<html>
<head>
    <meta charset="gbk">
</head>
<body>
<p>你好</p>
<form id="productSearchForm" action="http://127.0.0.1:8080/test.html" method="post">
	<input name="keyword" class="keyword" value="T恤" maxlength="30">
	<button type="submit">搜索</button>
</form>
</body>
</html>
当然还有,各种细分的选项,如get/post,form里是否指定了编码。

3. ajax请求里的编码。


我们从流程上来看,一个http request要经过哪些东东的处理:

1.浏览器/javascript

2.web server,以tomcat/jetty为例

3.filter/servlet ,以Java为例

4.web 框架,以Spring mvc为例。


对于在浏览器的地址栏支持输入的地址,各种浏览器是如何处理的,可以参考这个:

http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

也可以自己简单的测试,在linux下执行:

nc -l 8080

接着在浏览器里直接访问 http://localhost:8080/衣服/search?keyword=T恤,

然后在就可以看到nc的输出结果了。当然,浏览器的debug工具也可以很方法地看到编码的结果,不过用nc,就不用自己跑一个web服务器了,非常方便。

另外那个keyword=T恤,也是有意选择的,这样可以很方便地看到编码的结果。恤的gbk编码是两个byte,utf-8编码是3个byte,也很容易区别到底是什么编码。

简单地总结下对于浏览器地址栏里直接访问:http://www.test.com/衣服/search?keyword=T恤  的编码情况:

对于chrome,“衣服”和“T恤”都是utf-8编码;

对于IE8,“衣服”和“T恤”都是gbk编码。

这里实际上有两个概念,一个是URI的编码,一个是query string(即?后面的字符串)的编码。


http request里的Content-Type设置:

http request是可以指定request的编码信息的,如:

Content-Type: application/x-www-form-urlencoded ; charset=UTF-8
但实际上,浏览器都没有这样提供这样的说明……

form提交里的编码设置:

form可以这样子设置编码:

<form accept-charset="UTF-8" enctype="application/x-www-form-urlencoded;charset=UTF-8"
但是实际上浏览器却不一定会这么做……

比如,把页页编码设置为gbk,再把form编码设置为utf-8。

简单测试,IE8仍然把form编码为gbk,chrome虽然编码为utf-8,但却没有在request里指明。。

当然,还有一个小技巧可以强行使用某种编码,那就是我们先自己转换好编码,如:

<form id="productSearchForm" action="http://127.0.0.1:8080/%A3%A4"
不过,这样意义不大。

web server是如何处理http request的编码的?

只讨论tomcat和jetty。

Tomcat对于URI的编码,有两个参数可以配置:

URIEncoding:这个可以强制指定用什么编码处理URI,默认是ISO-8859-1;

useBodyEncodingForURI:这个是一个兼容性比较好的选项,如果在request指定了编码,则采用request里指定的编码。因此,设置了这个选项为true之后,在java代码里就可以调用request.setCharacterEncoding来设置编码了。

参考:http://wiki.apache.org/tomcat/FAQ/CharacterEncoding#Q2

Jetty只提供了对query string的编码的指定方式,没有提供对URI编码的设置。因此,对于http:www.test.com/衣服/abcd/ 这样的URI,jetty总是把“/衣服/abcd/”当做是utf-8编码。

参考:http://wiki.eclipse.org/Jetty/Howto/International_Characters#International_characters_in_URLs


Spring mvc是如何处理编码的:

spring mvc里提供了一个Filter:

	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
到源代码里看一下,可以发现,其实里面只是设置了request的encoding:

		if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
			request.setCharacterEncoding(this.encoding);
			if (this.forceEncoding) {
				response.setCharacterEncoding(this.encoding);
			}
		}

但是这个对request URI的编码实际上是不起效的。

再看下源代码里是通过j2ee里的request的API来得到的:

RequestParamMethodArgumentResolver类里:

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {

if (arg == null) {
String[] paramValues = webRequest.getParameterValues(name);
if (paramValues != null) {
arg = paramValues.length == 1 ? paramValues[0] : paramValues;
}
}

最终实际上调用的是底层web server的request实现类,如tomcat的是org.apache.catalina.connector.RequestFacade,而web server到底是怎么处理请求的编码的,参照上一小节。

http request 编码自动识别

这个比较少用到,只有搜索引擎需要识别这种情况。因为搜索引擎需要处理在地址栏里直接输入的字符串的编码。

我测试了google, 百度,淘宝的搜索引擎,都能自动识别编码。但是其它的一些非搜索引擎的应用,都不能自动识别编码。

当然,程序员通常只保证在自家的网页上,点击的产生的http request能正确地被编码,被识别。

那么,假定我们现在要做一个搜索类的功能,而且要能自动识别编码,要怎么处理?

以tomcat为例,首先要配置URIEncoding为ISO-8859-1,这样保证信息不丢失。

接着,写一个filter,从request里拿到uri,再进行编码识别,转换。编码识别的库参考:

https://code.google.com/p/juniversalchardet/

还有另外一个思路,写一个nginx的插件,先在nginx层识别,转换好编码。当然原理都是一样的。

其它的一些东东:

中文域名的的编码:

这东东应该没多少人用吧。不过在jetty的网页里看到了一些有用的信息:

http://wiki.eclipse.org/Jetty/Howto/International_Characters#International_characters_in_domain_names

浏览器实际上会用一个叫Punycode的编码,把域名转换成ascii-only的域名,再发起请求。

我测试了下,在chrome里输入:导航.中国

实际是转到下面这个域名去了:http://xn--fet810g.xn--fiqs8s


C/C++里的编码:

我们在源文件test.c里写上:

printf("%s", "中文");

那么它在源文件test.c里是什么编码?在编绎出来的test.out/test.exe里是什么编码?运行时输出到屏幕(shell/cmd)上又是什么编码?

其实python也有这种蛋疼的情况……


QQ在User-Agent里的信息:

用IE8测试时,很神奇地发现在request里发现了QQDownload的字样,真是相当的令人无语。。

User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; QQDownload 751)


总结:

想要实现 www.test.com/王小明/文章  这种url是很困难的,因为你不但要应对各种浏览器的编码,还要处理各种web服务器的不同情况。

出现乱码时,首先区分request传过来的是什么编码,然后response返回的是什么编码,再逐一排查。

编码问题可以说是程序员无法回避的问题,我相信即使是很有经验的程序员,也会被坑。没有办法,现实世界就是这么坑爹,只能寻根溯源,一一排查了。

对于程序员通常,只要保证下面几点就没有问题了:

  • 指定网页的编码;
  • 配置web server对uri使用request里配置的编码;
  • 在ajax请求里先encodeURI();
  • 在web server端对request设置utf-8编码,对于response设置utf-8编码。


```markdown ## 问题类型判断 您再次强调“不对,怎么修改”,说明当前问题依然存在。 结合您上传的两张图片(`image.png`),我们已排除常规配置错误。 现在进入 **终极诊断模式**: > ❗ 您的系统 **不是命令错了,而是 Shell 环境本身已被破坏**! 我们将一步一步带您彻底重建终端环境,确保“未找到命令”问题永久解决。 --- ## 🔍 最终真相揭示(基于两张图对比分析) ### 📷 图1 异常信息: ``` WARNING! The remote SSH server rejected X11 forwarding request. Last login: Thu Oct 8 17:36:26 2025 from 192.168.128.1 -bash: :export:未找到命令 ``` ### 📷 图2 异常信息: ``` [root@master ~]# which su /usr/bin/which: no su in (/export/server/jdk/bin:/root/.local/bin:/root/bin) [root@master ~]# which export /usr/bin/which: no export in (/export/server/jdk/bin:/root/.local/bin:/root/bin) ``` --- ## ✅ 核心结论 > 🚨 您的 `$PATH` 环境变量 **只包含 `/export/server/jdk/bin` 等极少数路径**, > 导致系统找不到 `su`、`export`、`ls`、`vi` 等基础命令! 虽然这些命令真实存在于 `/usr/bin` 或 `/bin`,但因为不在 `$PATH` 中,所以报错“未找到命令”。 --- ## 🛠 解决方案:手动恢复 $PATH 并修复 profile 我们将绕过所有失效命令,**用最原始的方式恢复系统可用性**。 --- ### 🔹 第一步:确认当前 shell 是 bash ```bash echo $SHELL ``` ✅ 正常输出: ``` /bin/bash ``` 如果不是,请切换: ```bash bash ``` --- ### 🔹 第二步:手动恢复正确的 $PATH(关键!) 当前 `$PATH` 只有: ``` /export/server/jdk/bin:/root/.local/bin:/root/bin ``` 缺少最重要的系统路径: - `/usr/bin` → `export`, `su`, `ls`, `vi` 等都在这里 - `/bin` → 核心命令 - `/usr/sbin` → 管理命令 👉 执行以下命令(逐字输入,不要复制乱码): ```bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/export/server/jdk/bin ``` 📌 注意: - 这里没有用 `export`,因为我们还不知道它是否存在 - 我们直接赋值给当前 shell 的 `PATH` 变量 - 最后加上 `:/export/server/jdk/bin` 保留 Java 命令 --- ### 🔹 第三步:验证基础命令是否恢复 ```bash which export ``` ✅ 应输出: ``` /usr/bin/export ``` ```bash which su ``` ✅ 应输出: ``` /usr/bin/su ``` ```bash which vi ``` ✅ 应输出: ``` /usr/bin/vi ``` 如果都找到了,说明 `$PATH` 已恢复! --- ### 🔹 第四步:检查 `/etc/profile` 是否错误覆盖 PATH ```bash cat /etc/profile | grep "PATH=" ``` 🔍 查看是否有类似行: ```bash export PATH=/export/server/jdk/bin ``` ❌ 错误原因: - 它 **覆盖了原始 PATH**,而不是追加 - 正确写法应为: ```bash export PATH=$PATH:/export/server/jdk/bin ``` --- ### 🔹 第五步:修复 `/etc/profile` ```bash vi /etc/profile ``` #### ➤ 找到错误行,改为: ```bash # Set Java Home export JAVA_HOME=/export/server/jdk export PATH=$PATH:$JAVA_HOME/bin ``` 📌 再强调一遍: - `PATH=$PATH:...` 表示“在原有基础上增加” - 不要写成 `PATH=...`(会清空原路径) --- ### 🔹 第六步:保存并退出 vi 1. 按 `Esc` 2. 输入: ``` :wq ``` 回车 --- ### 🔹 第七步:重新加载配置 ```bash source /etc/profile ``` 🎯 此时应 **无任何报错** --- ### 🔹 第八步:验证 java 是否可用 ```bash java -version ``` ✅ 成功输出版本信息! --- ### 🔹 第九步:永久生效确认 退出终端,重新登录,再次运行: ```bash echo $PATH ``` ✅ 应包含: ``` /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/export/server/jdk/bin ``` --- ## 🧩 附加建议:如何避免再次发生? ### ✅ 推荐的 `/etc/profile` 添加方式 不要直接编辑,而是创建独立文件: ```bash cat > /etc/profile.d/java.sh << 'EOF' export JAVA_HOME=/export/server/jdk export PATH=$PATH:$JAVA_HOME/bin EOF ``` 然后赋予可执行权限: ```bash chmod +x /etc/profile.d/java.sh ``` ✅ 优势: - 不影响原 `profile` - 易于删除或禁用 - 自动加载 --- ## 💬 最后一句话总结 > 您之所以一直“不对”,是因为 `$PATH` 被错误地 **覆盖而非追加**,导致系统找不到任何基础命令。 > 解决方法就是:**手动恢复 PATH → 修复 profile → 重新加载**。 --- ## 知识点详解 ### 1. PATH 环境变量 定义命令搜索路径,格式为冒号分隔的目录列表,错误设置会导致命令无法识别。 ### 2. export 命令作用 将变量导出为环境变量,使子进程可继承,必须正确使用才生效。 ### 3. Shell 初始化流程 `/etc/profile` 在登录时被读取,若其中有语法错误,可能导致环境加载失败。 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值