问题产生过程
之前做安卓项目遗留下来的一个问题, 为什么我通过Http请求传输的中文到服务端(Java) 获取参数时, 总是出现各种问题, 莫名其妙就出现了乱码? 各种设置UFT-8 都不管用??
我们必须从去了解具体URL编码的过程.
浏览器环境
浏览器的导航栏输入包含中文URL后.
浏览器会自动将中文进行URL编码.(我们打开F12开发者工具查看)
那么我此时会产生好奇,URL编码是在什么阶段开始编码的?什么适合开始解码的?
安卓环境
我尝试在非浏览器环境下(比如Android的HttpURLConnection和OkHttp)用Get 请求访问 http://IP:port/getMS?name=邓洲
此时会发生什么?
结果在服务端接通过request.getParams()获取并打印了
好像也自动进行了URL编码
Nodejs环境
然后是nodejs环境使用fetch或者axios访问会怎么样?
const fetch = require('node-fetch')
fetch("http://localhost:8080/EncodeTest/URLEncode?name=邓大帅")
.catch(err => console.log(err))
.then(res => console.log(res))
好家伙,直接报错,请求根本发不出去
原因是URL不能携带中文,需要先手动URL编码才能访问
服务端也接收到了
URL编码特殊字符
字符 | 解释 | 转码 |
---|---|---|
+ | URL 中+号表示空格 | %2B |
空格 | URL中的空格可以用+号或者编码 | %20 |
/ | 分隔目录和子目录 | %2F |
? | 分隔实际的URL和参数 | %3F |
% | 指定特殊字符 | %25 |
# | 表示书签 | %23 |
& | URL 中指定的参数间的分隔符 | %26 |
= | URL 中指定参数的值 | %3D |
一次性测试这些符号(注意打印结果是服务器端的打印结果)
encodeURI("邓大+帅/?%=&#")
//邓大 帅/?%=
//+号变成了空格,最后两个不显示
encodeURI("邓大 帅/?%=#")
//邓大 帅/?%=
//去掉&后,#仍然不显示;+号换空格仍然是空格
encodeURI("邓&大+帅/?%=#")
//邓
//说明&会截断后面的内容
encodeURI("邓#大+帅/?%=")
//邓
//说明#也会截断后面的内容
发现只有 + & # 三个符号无法通过URL编码正常显示,因此在传输之前就得使用replace()来替换所有这三个字符.
const fetch = require('node-fetch')
fetch("http://localhost:8080/EncodeTest/URLEncode?name=" +
encodeURI("邓+大&帅#太 帅/?%=")
.replace("+", "%2B")
.replace("#", "%23")
.replace("&", "%26")
)
.then(res => res.text())
.then(res => console.log(res))
服务端接收并打印
完美!!!
不过我们还是得认真分析一下这中间发生了什么.
首先进行encodeURI(“邓+大&帅#太 帅/?%=”)
然后再进行替换的.
那么就得知道这三个符号在函数执行后输出什么.
console.log(encodeURI("+&#"))
// +&#
console.log(encodeURI("+&#/"))
// +&#/
console.log(encodeURI("%"))
// %25
// % 能够正常编码
console.log(encodeURI("+ %"))
// +%20%25
//空格也能正常编码
console.log(encodeURI("?"))
// ?
console.log(encodeURI("="))
// =
结论是在编码阶段
符号 | encodeURI编码 | 服务端接收(来自Node) | 服务端接收(来自浏览器) | 服务端接收(来自安卓) |
---|---|---|---|---|
+ | + | 空格 | 空格 | 空格 |
空格 | %20 | 空格 | 空格 | 空格 |
/ | / | / | / | / |
? | ? | ? | ? | ? |
% | %25 | % | null | null |
# | # | 当前及后面字符全部失效 | 当前及后面字符全部失效 | 当前及后面字符全部失效 |
& | & | 当前及后面字符全部失效 | 当前及后面字符全部失效 | 当前及后面字符全部失效 |
= | = | = | = | = |
可以看出服务端接收的内容与发送端环境几乎无关.
那为什么Node环境的 % 会不一样?
因为Node环境是事先通过encodeURI 将%编码成%25,
而其他环境(浏览器导航、Android的HttpUrlConneciton等未经encodeURI()) 的URL则会编码成
这样
没错,根本就没编码.
所以才会产生不同的结果.
那么针对两种不同的场景(Node的encodeURI 和 浏览器等直接传URL自动编码)所遇到的问题,我们需要区分讨论。
最佳解决方案
首先我们要了解一个概念,不是所有的字符都必须编码才能解码,比如 = $ @ / ? ( ) [ ] ~ - _* 和空格等等以及所有的数字.
以上这些默认不编码,也不用解码.
另外还有一些是我们必须认识的不安全的字符,必须进行编码(encodeURI会自动编码):
^ { } ` \ |
这六个,不编码直接报错400 Bad Request,发都发不出去.
还有一些虽然可传但是对数据完整性造成影响的: + # & ,同样也必须编码(并且这三个不会被encodeURI编码).
另外 % 与转移字符前缀相同,所以不可以重复编码。否则%转移成%25下一次就是%2525(对应服务器拿到的就是”%25“),所以有的人提出的先encodeURI()再escape()编码明显不可行,因为会对%重复编码。
使用encodeURI() 可以解决大部分问题,但是
有些字符仍然不编码,我们需要手动替换.
encodeURI(url)
.replaceAll("#", "%23")
.replaceAll("&", "%26")
.replaceAll("+", "%2B")
//注意Node环境没有replaceAll()函数,各位自行去百度怎么实现replaceAll(通过正则)
这样一来,你可以在URL中携带任何字符的参数了!