从原理上搞定编码(二)-- Web编码
周末宅在家里睡完觉就吃饭,吃完饭接着睡觉,这日子过的实在是没劲啊。明明还有计划中的事情没有做, 为什么就是不想去做呢,这样的生活持续下去,必然会成为一个彻头彻尾的loser。上一篇写的 初识编码 ,这一篇把web编码写出来和菜鸟们分享一下。图片比较多,手机用户不要看,流量没了俺不负责。
一.html页面编码
当浏览器请求一个静态的html页面时,服务器会将html页面的字节流通过网络传输给浏览器。浏览器再将字节流解码成相应的html文本字符,然后将html元素渲染出来。在这个流程中浏览器有一个解码html字节流的过程。浏览器如何判断用什么编码格式去解码呢?在html页面的head标签中通常会包含一个指定页面编码的<meta/>标签,如果指定的是utf-8编码,浏览器就用utf-8解码html页面。问题是在浏览器解析<meta/>标签获得页面编码之前,浏览器是用什么编码来解析<meta/>标签的呢?因为html开头都是字母,基本不会存在ASCII码之外的字符,所以识别起来还是没有难度的。
如下是我的html代码,第5行和第6行,会根据不同的测试条件分别打开注释,具体打开哪个后边会有提示。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -->
<!-- <meta http-equiv="Content-Type" content="text/html; charset=GBK" /> -->
<title>utf-8 html</title>
</head>
<body>
<p>Get 请求</p>
<form action="TestServlet" method="get">
<input type="text" value="你好" name="txtKey"/>
<input type="submit" value="提交"/>
</form>
<p>Post 请求</p>
<form action="TestServlet" method="post">
<input type="text" value="你好" name="txtKey"/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
演示过程用的环境是tomcat+servlet,浏览器是chrome。项目结构如下,其中有两个html页面,index.htm页面用utf-8编码存储,lindexGBK.html页面用GBK编码存储。两个页面的html代码都是上边给出的html代码。

实验一: 在utf-8编码的html页面中指定不同的编码格式
启动项目,用浏览器打开index.html的url,如左下图所示,可以看出浏览器解析到<meta/>标签中的utf-8编码,并用utf-8解码html页面,页面正确显示。如果在<meta/>标签中指定页面编码为GBK,那么浏览器就用GBK解码,肯定就不能正确显示页面了,如右下图所示,页面中文出现乱码。如果想避免乱码的话,还是必须保证编解码格式统一。

实验二: 不指定页面编码的情况下测试不同的编码页面
这次index.html和indexGBK.html都没有指定页面编码。用浏览器打开index.html对应的url,如左下图所示,可以看出浏览器用utf-8解码html页面,页面正确显示。用浏览器打开indexGBK.html对应的url,浏览器也是用utf-8解码,肯定就不能正确显示页面了,如右下图所示,页面中文出现乱码。因为在没有指定页面编码的情况下,浏览器采用操作系统默认编码,我的系统就是utf-8编码,所以index.html页面就碰巧解析正确了。

实验三: 在一个html页面中指定两个不同的编码
用浏览器打开index.html对应的url,如左下图所示,有两个<meta/>标签指定了不同的页面编码。当utf-8编码在GBK编码上面时,可以看出浏览器用utf-8解码html页面,页面正确显示。如右下图所示,如果GBK编码在utf-8编码上面,那么浏览器就用GBK解码,页面中文出现乱码。所以如果出现多个编码以第一个出现的为主,当然这种情况是不可能出现的(出现了也是代码问题),为什么会出现这种情况我觉得需要解释一下,因为浏览器在解析<meta/>标签时一旦发现页面编码,立马就用这个编码解析页面。

html的form在没有指定accept-charset属性的情况下,html的表单提交采用的编码也是指定的页面编码,如果没有指定页面编码,也是采用操作系统的默认编码。总之,html页面的加载与表单提交采用的编码格式是一样的。因为浏览器认为你指定的页面编码就是html文件的实际编码,所以不管是否乱码,它都会照做。
与html页面编码相关的还有js文件编码,默认情况下,浏览器采用html的编码格式去解码js文件。如果js文件与html页面的编码不同怎么办?script标签有一个charset属性,这个属性就决定了浏览器去加载完js,解析字节流时用的编码。
<script type="text/javascript" src="myscripts.js" charset="UTF-8"/>
二.Web后台编码
示例代码是Java写的,其他语言也是一个道理,不耽误理解。请求后台无非是GET或POST方法,原理是一样的,我只介绍POST。
html页面是utf-8格式的, 页面编码也指定为utf-8。
实验一:后台解码
后台响应代码如下所示,页面以post方式请求时会触发doPost方法。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("START...");
String param=request.getParameter(PARAM);
System.out.println("原始参数");
System.out.println(param);
System.out.println("utf-8解码");
System.out.println(new String(param.getBytes("ISO-8859-1"),"utf-8"));
System.out.println("GBK解码");
System.out.println(new String(param.getBytes("ISO-8859-1"),"GBK"));
System.out.println("END...");
System.out.println("");
}
先是直接获取参数打印出来,然后又将获取的参数用ISO-8859-1编码,再用utf-8和GBK分别解码并打印出来。点击页面的Post表单提交,控制台结果如下:

html提交普通表单时,参数以字节流的方式传输到web服务器,web服务器解码字节流得到参数。tomcat默认采用ISO-8859-1编解码,所以直接获取参数是乱码,以ISO-8859-1编码回原本的字节流再用utf-8重新解码就可以得到正确的参数。
为什么tomcat采用ISO-8859-1为默认的编码格式,导致解码流程这么复杂?在servlet规范中明确规定:如果客户端请求没有指定字符编码,web容器用来创建请求读取器和解析POST 数据的编码必须是“ISO-8859-1”。为什么会有这么奇葩的规定呢?我猜可能是适应早期的浏览器吧,早期的浏览器大多采用ISO-8859-1为默认编码,这个编码的应用是非常广泛的,觉得陌生或不可思议只是因为我们在中国。稍微捋一下,ASCII码是1967年的,ISO-8859-1是1987年的,unicode是1994定稿,浏览器是在94年底95年初发布,所以估计早期的浏览器来不及用unicode作为默认编码。我是用这么一套理论来说服自己的,对不对就不重要了,总之ISO-8859-1在很多国家是普遍采用的,在人家的圈子里做东西采用人家的编码也是正常的。
可以在获取任何参数前调用 request.setCharacterEncoding("utf-8") ,即可用utf-8解码参数,就不用那么麻烦了。其他语言在原理上也都是相同的。
实验二:响应编码
响应代码更改为如下代码。
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// response.setCharacterEncoding("utf-8"); ①
// response.setContentType("text/html; charset=UTF-8"); ②
// response.setHeader("Content-Type", "text/html; charset=UTF-8"); ③
PrintWriter writer = response.getWriter();
writer.print("<html><head><meta charset=\"utf-8\"/></head><body><font color=\"red\">你好</font></body></html>");
writer.close();
}
只在响应的html页面中指定了页面编码,显示是乱码,如左下图所示。因为在输出时并没有指定编码格式,默认采用ISO-8859-1编码,浏览器用utf-8解码自然就是乱码了。
代码中注释掉的三行,是java常用的设置响应编码方式。②跟③的效果是一样一样的,只讨论①和②。将注释①打开,响应如右下图所示,指定了响应编码为utf-8,浏览器用utf-8解码正确显示。
setCharacterEncoding只是把响应字符按指定格式编码,setContentType除此之外,还在header里添加了contentType属性。只把注释②打开也可以正常显示,在下图右侧ResponseHeaders中有Content-Type属性 “text/html; charset=utf-8” 。当header中出现Content-Type属性并出现指定编码,浏览器将忽略html里<meta/>标签中指定的编码,以header中的编码解码html页面。

当setCharacterEncoding和setContentType同时出现时,后边的编码会覆盖前边的编码。响应代码更改为如下所示。
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html; charset=utf-8");
response.setCharacterEncoding("GBK");
PrintWriter writer = response.getWriter();
writer.print("<html><head><meta charset=\"utf-8\"/></head><body><font color=\"red\">你好</font></body></html>");
writer.close();
}
响应编码设为GBK,contentType属性指定的编码却是utf-8,浏览器按照contentType指定的编码来解码岂不是乱码?如下图所示,答案是否定的,真实的contentType是“text/html; charset=GBK”,这是为什么,明明设置的“text/html; charset=utf-8”。因为contentType包含的其实是两部分,一部分是text/html,第二部分是charset=utf-8。设置了contentType之后,这两部分是分别存储的,setCharacterEncoding会把第二部分编码的值覆盖掉。这时contentType就变成了“text/html; charset=GBK”。

实验四:浏览器对动态页面与静态页面在默认编码上的区别
前边说过了,html静态页面在没有指定页面编码的时候是按照系统默认编码解码的。响应代码更改如下所示。
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();
writer.print("<html><head></head><body><font color=\"red\">你好</font></body></html>");
writer.close();
}
没有任何地方指定页面编码。按照静态html的规律,浏览器采用我的系统默认编码utf-8解码,不会出现乱码。但是实际情况并不是这样,如左下图所示,乱码还是出现了,因为浏览器使用的GBK去解码。这是为什么呢?打开chrome的设置-》显示高级设置-》自定义字体-》出现右下图所示的窗口,可以看到chrome的默认编码是GBK,也就说如果动态页面没有指定页面编码将会按照浏览器的默认编码来解码。为了验证我们的猜测,将chrome编码设置为utf-8,重新请求一遍,果然页面正确显示。这只是我的猜测,具体对错未知,但是未指定页面编码的情况是不多见的,大家在编码过程中一定要指定页面编码为页面实际的存储编码。

下篇文章介绍一下应用程序交互时的字节流编码。
从原理上搞定编码(二)-- Web编码的更多相关文章
- 001_从原理上搞定编码-- Base64编码
开发者对 Base64编码肯定很熟悉,是否对它有很清晰的认识就不一定了.实际 上Base64已经简单到不能再简单了,如果对它的理解还是模棱两可实在不应该.大概介绍一下Base64的相关内容,花几分钟时 ...
- 从原理上搞定编码-- Base64编码
BASE64是一种编码方式,通常用于把二进制数据编码为可写的字符形式的数据.这是一种可逆的编码方式.编码后的数据是一个字符串,其中包含的字符为:A-Z.a-z.0-9.+./共64个字符:26 + 2 ...
- 从原理上搞定编码(四)-- Base64编码
开发者对Base64编码肯定很熟悉,是否对它有很清晰的认识就不一定了.实际上Base64已经简单到不能再简单了,如果对它的理解还是模棱两可实在不应该.大概介绍一下Base64的相关内容,花几分钟时间就 ...
- git从安装到多账户操作一套搞定(二)多账户使用
作者:良知犹存 转载授权以及围观:欢迎添加微信:Allen-Iverson-me-LYN 总述 GIT是当今热门代码管理技术,但是如此火的系统,竟然是大神林纳斯花了两周用C写出来的一个分布式版 ...
- 表格搞定 Asp.net Web 状态管理
最近在网上搜罗了 ASP.NET WEB 状态管理方面的一些内容,终于把这些内容整合总结了一下. 1. 希望自己通过整理,能够掌握一些,为自己投资. 2. 以便自己忘记,又要浪费时间搜罗. 3. 希望 ...
- ASP.NET Core 编码、web编码、网页编码 System.Text.Encodings.Web
System.Text.Encodings.Web 空间包含表示 Web 编码器的基类.表示 HTML.JavaScript 和 Url 字符编码的子类,以及表示仅允许编码特定字符.字符范围或码位的筛 ...
- [转]用GSON 五招之内搞定任何JSON数组
关于GSON的入门级使用,这里就不提了,如有需要可以看这篇博文 <Google Gson的使用方法,实现Json结构的相互转换> ,写的很好,通俗易懂. 我为什么写这篇文章呢?因为前几晚跟 ...
- [转] Android:用GSON 五招之内搞定任何JSON数组
[From] http://www.open-open.com/lib/view/open1472632967912.html 写在前面 关于GSON的入门级使用,这里就不提了,如有需要可以看这篇博文 ...
- WEB安全第二篇--用文件搞定服务器:任意文件上传、文件包含与任意目录文件遍历
零.前言 最近做专心web安全有一段时间了,但是目测后面的活会有些复杂,涉及到更多的中间件.底层安全.漏洞研究与安全建设等越来越复杂的东东,所以在这里想写一个系列关于web安全基础以及一些讨巧的pay ...
随机推荐
- 20155332 2006-2007-2 《Java程序设计》第3周学习总结
学号 2006-2007-2 <Java程序设计>第3周学习总结 教材学习内容总结 尽量简单的总结一下本周学习内容 尽量不要抄书,浪费时间 看懂就过,看不懂,学习有心得的记一下 教材学习中 ...
- sql语句-7-更新数据
- 新技能get,使用PHPStorm的deployment工具
1. 工具栏 Tools - Deployment - Configuration 2. 添加一个服务端的配置信息 type 类型可以选择:FTP.local等. 填完信息别忘了点"Test ...
- linux挂在samba服务器到本地(用于备份文件到nas或者windows的文件服务器)
1.安装工具 首先在linux上安装samba访问工具 sudo apt-get install smbclient sudo apt-get install cifs-utils 2.查看服务器目录 ...
- springboot入门之一:环境搭建(续)
在上篇博客中从springboot的入门到运行一个springboot项目进行了简单讲述,详情请查看“springboot入门之一”.下面继续对springboot做讲述. 开发springboot测 ...
- 函数parseQuery用于解析url查询参数
在百度上找的,以后忘了再看. 语法如下: var obj = parseQuery(query) query是被解析的查询参数,函数返回解析后的对象. 使用范例如下: var jerry = pars ...
- IDEA 配置Junit4
Junit4 主要用来执行java程序的单元测试: 1 安装junit4插件 因为我安装过了,没有安装的再输入框搜索,然后安装就行 2 选择默认使用Junit4 3 红框中的test去掉,变为“$en ...
- http-equiv=mobile-agent说明
Meta声明的格式:<meta http-equiv=”mobile-agent” content=”format=[wml|xhtml|html5]; url=url”> 比如: < ...
- 高可用Kubernetes集群-10. 部署kube-proxy
十二.部署kube-proxy 1. 创建kube-proxy证书 1)创建kube-proxy证书签名请求 # kube-proxy提取CN作为客户端的用户名,即system:kube-proxy. ...
- Tree - XGBoost with parameter description
In the previous post, we talk about a very popular Boosting algorithm - Gradient Boosting Decision T ...