引言:
在进行Web開始时。乱码是我们最常常遇到也是最主要的问题。有经验的程序员非常easy能解决,刚開始学习的人则easy被泥潭困住。
并且非常多时候。我们即使攻克了乱码问题也是不明就里。往往云里雾里。
事实上乱码问题非常easy,就是client和server使用了不一样的字符集导致的。也就是我们发送文件时用的字符编码和解析文件的编码不一致。所以仅仅要搞清楚了我们的文件是怎么被编码和解码的解决乱码就非常easy了。分析乱码,我们从请求乱码和响应乱码来分析,请求乱码又须要依据GET和POST来单独分析。
请求乱码——GET
请求的编码是由浏览器发出的。使用GET方法请求server信息时。依据HTTP协议规定,Request包是没有请求体的(也就是Request Body不存在)。所以我们仅仅能把请求參数放在URL中。因此使用GET方式与server通信,编码方面我们关心的重点是浏览器对URL的编码方式,和server对URL的解码过程。
关于URL
URL是我们常常接触并不是常简单的一种技术,URL技术简单到它事实上就是一个字符串。
实际上URL的结构是非常复杂的,仅仅只是通常上使用方法比較简单而已。关于URL的具体介绍能够參考以下的文章:
URL的规范定义在RFC 1738文档中。通过URL我们能够获得通信协议、主机域名、处理port、应用路径、路径參数、查询參数、页面片段等信息。
比方:
http://user:pass@example.com/a/b;q=1/c?d=2;sessionid=qewfewrwer#2
依据上面的URL。我们能够得到例如以下信息:
Part | Data | serverAPI |
Scheme | http | 用req.getScheme |
user | user | 囧,不知道 |
pass | pass | 囧,不知道 |
host address | example.com | req.getServerName |
port | 80 | req.getServerPort |
path | /a/b;q=1/c | req.getContextPath |
query parameters | d=2;sessionid=qwefewrwer | req.getQueryString |
fragement | 2 |
|
开发时,我们经经常使用到的就是path和query parameters。这两个參数,剩下的參数使用的比較少。只是在RESTful API中,像路径參数的信息可能会用到。fragement用来在页面内进行锚点定位。(已经非经常见了)
浏览器对Path部分的编码
path信息被用来匹配处理路径。一般设计上非常少在path中包含中文參数。RFC文档对于path的编码也没有明白规定。可是据其它文章的介绍。浏览器对Path的编码一般都会採用UTF-8编码,最新的URI标准已经定义了URI的编码採用UTF-8编码。
定义:简单说path部分就是应用路径部分,就是URL去掉协议、域名、port和查询信息剩下的部分。
server对Path部分的解码:(三种方案)
通常上,我们的请求都会首先发给Web容器(以下以Tomcat为例),URL也会被Web容器解码,对于Tomcat容器来说,我们能够在conf/server.xml的connector标签中添加URL解码參数,默认容器对URL的使用ISO-8859-1解码。
上面的是Tomcat的默认设定,能够给标签加入URIEncoding属性来指定URL的解码方案。(PS:标签写法是URI不是URL)
假设不想使用这样的硬解码方案。还能够指定还有一个属性:useBodyEncodingForURI,这个属性用来告诉Web容器。假设request指定了解码方案,则使用request.setCharacterEncoding指定的编码来解码URL。
另外一种方案没有经过測试。假设有须要能够尝试下。
具体资料能够參考以下的Tomcat官方文档:
此外。假设不想改动容器的全局配置。毕竟有时候容器里可能不止我们一个应用,那么我们还能够採用以下的做法来提取參数:
String path = req.getServerPath();//自己手动提取,不适合配合框架path = new String(path.getBytes(“ISO8859-1”,”UTF-8”));//又一次拼装上面的做法。我们要确定Web容器对URL的解码用的是ISO8859-1,由于不排除其它人改动了容器配置或容器配置本身比較奇葩的可能。
浏览器对QueryParameter的编码
查询參数和Path是不一样的,缺少查询參数,web容器是能够定位到我们的处理程序的,可是缺少path就不行。
另外,path和查询參数的保留字符是不一样的。
定义:简单来说查询參数就是path后面紧跟的?后面的部分,用&来连接各个查询參数。
因为Path和查询參数的不同,有些浏览器对查询參数的编码和path部分的编码是不一致的。
详细使用怎么编码的比較混乱。能够參考下以下的文章:
依据上面的文章总结的规律:
(1)Path部分或者说除查询參数外的URL部分。各浏览器用UTF-8编码;
(2)查询參数,各浏览器依据操作系统编码决定;
上面的文章比較老了,规律可能不有用了,可是也能说明一定问题。对于某些文章说的,查询參数会依据页面编码来决定,我没有做实验。可是这样的结论肯定是片面的。原因例如以下:
页面的meta參数是用来向浏览器说明页面编码的,其次。在使用POST Method发送数据的时候,浏览器会依据meta的编码来编码Request Body。而Get方式。我们在没有页面的时候也能够发起。所以浏览器根本找不到Meta标签,也就没法參考页面编码。
浏览器对查询參数究竟使用哪种方式编码的,我没有找到专业、权威、可信的答案,可是我觉得这个还是详细情况详细分析。做个小实验即可了。
毕竟时代在进步。厂商们统一使用UTF-8编码的可能性比較大。并且后面有不依赖浏览器编码的解决方式。
server对QueryParameter的解码
查询參数也是URL的一部分。所以Web容器对查询參数的解码比較明智,解码和path使用的是一样的方案的编码,所以解决方式也是一样的。
出现乱码:
在处理查询參数时,我们经常使用req.getParameters();来获取某个參数。这种方法背后非常少有人关心它的工作原理。并且也不是必需。
这一部分是最easy出现乱码的,毕竟它里面的參数可能是用户输入的,并非我们设计的。
在GET方式下,出现这样的乱码不要慌张,首先我们要分析出,浏览器对查询參数究竟採用了哪种编码。
方法简单(也复杂),chrome下F12打开开发人员工具
找到network标签,能够看到Request URL中显示的是k= %E4%B8%AD%E5%9B%BD,把%去掉,能够得到6个16进制数,百度下unicode码表。能够看到他们正好是“中”和“国”的unicode编码。所以能够推測浏览器使用的是UTF-8编码。这样的推断方式须要对字符编码比較熟悉。只是也不算非常难,找点字符编码的文章学学非常easy就能看出规律来。
PS:不要通过浏览器的地址栏看URL编码,非常多浏览器的地址栏会对URL解码显示。
之后,server端,首先确定下。你的Web容器对URL使用的解码方案,然后对应的选择String(param.getBytes(“ISO8895-1”,”UTF-8”))或者是useBodyEncodingForURI、URIEncoding方案就好了。
总结:
使用GET方式出现乱码时,最基本的是找出浏览器对URL的编码方式,假设使用JS编程时。在浏览器能够使用encodeURIComponent函数对中文參数进行编码后再拼装參数。Java端使用URLDecoder.decode方法解码。JS端要进行两次编码,否则第一次的URL编码会被Web容器解码,获取的參数仍有可能是乱码。能够參考: