编解码
JavaScript 内置编码函数
escape
Javascript 语言用于编码的函数,一共有三个,最古老的一个就是 escape()。该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码: - _ . ! ~ _ ’ ( )。其他所有的字符都会被转义序列替换。所有的空格符、标点符号、特殊字符以及其他非 ASCII 字符都将被转化成 %xx 格式的字符编码(xx 等于该字符在字 符集表里面的编码的 16 进制数字)。比如,空格符对应的编码是 %20。不会被此方法编码的字符: @ _ / +。实际上,escape() 不能直接用于 URL 编码,它的真正作用是返回一个字符的 Unicode 编码值。比如 " 王下邀月熊 " 的返回结果 是 %u738B%u4E0B%u9080%u6708%u718A,也就是说在 Unicode 字符集中," 王 " 是第 738B 个(十六进制)字符,后面的以此类推。
> escape("王下邀月熊")
'%u738B%u4E0B%u9080%u6708%u718A'
其对应的解码函数为 unescape:
> unescape('%u738B%u4E0B%u9080%u6708%u718A')
'王下邀月熊'
encodeURI
encodeURI() 是 Javascript 中真正用来对 URL 编码的函数。它着眼于对整个 URL 进行编码,因此除了常见的符号以外,对其他一些在网址中有特殊含义的符号 “; / ? : @ & = + $, #",也不进行编码。编码后,它输出符号的 utf-8 形式,并且在每个字节前加上 %。
> encodeURI("http://王下邀月熊.com")
'http://%E7%8E%8B%E4%B8%8B%E9%82%80%E6%9C%88%E7%86%8A.com'
它对应的解码函数是 decodeURI()。
> decodeURI('http://%E7%8E%8B%E4%B8%8B%E9%82%80%E6%9C%88%E7%86%8A.com')
'http://王下邀月熊.com'
DOM 下 GBK 编码
在 node 环境下我们可以使用node-urlencode方便地进行各种格式的编解码,但是在 DOM 下 GBK 的编码却是个小麻烦。另一方面,如果看过笔者之前的浏览器跨域方法与基于 Fetch 的 Web 请求最佳实践这篇文章会发现,因为希望能在 Node 环境下测试,而后在 Browser 环境中无缝运行,所以笔者封装了isomorphic-urlencode,其保证了接口风格是与node-urlencode保持一致,但是因为基于 DOM 的解码是异步进行的,因此最后是设置了 Promise 作为异步的返回对象。在 Browser 中其核心的对于 GBK 的编码方式分为两步,首先是在当前的页面中创建隐藏的 form 表单与隐藏的 iframe:
//创建form通过accept-charset做encode
const form = document.createElement("form");
form.method = "get";
form.style.display = "none";
form.acceptCharset = "gbk";
//创建伪造的输入
const input = document.createElement("input");
input.type = "hidden";
input.name = "str";
input.value = url;
//将输入框添加到表单中
form.appendChild(input);
form.target = "_urlEncode_iframe_";
document.body.appendChild(form);
//隐藏iframe截获提交的字符串
if (!window["_urlEncode_iframe_"]) {
const iframe = document.createElement("iframe");
//iframe.name = '_urlEncode_iframe_';
iframe.setAttribute("name", "_urlEncode_iframe_");
iframe.style.display = "none";
iframe.width = "0";
iframe.height = "0";
iframe.scrolling = "no";
iframe.allowtransparency = "true";
iframe.frameborder = "0";
iframe.src = "about:blank";
document.body.appendChild(iframe);
}
//
window._urlEncode_iframe_callback = callback;
//设置回调编码页面的地址,这里需要用户修改
form.action = window.location.href;
//提交表单
form.submit();
//定时删除两个子Element
setTimeout(function () {
form.parentNode.removeChild(form);
iframe.parentNode.removeChild(iframe);
}, 100);
即将 form 表单的提交结果异步显示在 iframe 中,因为笔者是基于 React 进行的开发,因此只有一个 HTML 文件作为入口,因此笔者是提交到了自身,并且需要在 HTML 文件首部添加如下回调控制代码
if (parent._urlEncode_iframe_callback) {
parent._urlEncode_iframe_callback(location.search.split("=")[1]);
//直接关闭当前子窗口
window.close();
}
在原文中还有关于 IE 的 Bug 的讨论,这里暂时不做详细介绍。总结而言,isomorphic-urlencode 简单的用法为
const urlencode = require("isomorphic-urlencode");
urlencode("王下邀月熊").then(function (data) {
console.log(data);
});
urlencode("王下邀月熊", "gbk").then(function (data) {
console.log(data);
});
在笔者自己以流式风格基于 fetch 封装的fluent-fetch中,建议是将所有的非 UTF-8 编码的操作提取到网络请求之外,即可以如下使用:
//测试需要以GBK编码方式发起的请求
const urlencode = require("isomorphic-urlencode");
urlencode("左盼", "gbk").then((data) => {
fluentFetcher = new FluentFetcher({
host: "ggzy.njzwfw.gov.cn",
responseContentType: "text",
});
//http://ggzy.njzwfw.gov.cn/njggzy/consultant/showresault.aspx?ShowLsh=0&Mlsh=123456&Name=%D7%F3%C5%CE
//测试以代理模式发起请求
fluentFetcher
.parameter({ ShowLsh: "0", Mlsh: "123456", Name: data })
.get({ path: "/njggzy/consultant/showresault.aspx" })
.proxy({ proxyUrl: "http://app.truelore.cn:11499/proxy" })
.build()
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
});