起因
在一次需求开发自测中遇到一个问题,一个接口的cateId3List参数中有中括号(’[‘、’]’),是url特殊字符,但请示时传参未做url编码转义,在测试环境会导致返回400,线上环境会概率性的400 Bad Request(和nginx层无关)。
ps.图书项目使用的是axios,这个接口在发出时也没做过多的封装(可以理解成和用axios发出效果是一样的),下面都是以axios.get的方式为例(版本0.19.2)
问题定位
当时为了不阻塞测试流程,就先让QA通过在fiddler配置规则绕过了这个问题,利用工具的rewrite功能。
这里做的事就是把请求参数的[
、]
给剔除掉。但是在开发debug时,我们的传入参数明明是不需要提前编码的呀,
const axios = require('axios');
const argString = '你好';
axios.get('/zzopen/sellbook/searchDefaultWord', {
params: {
arg: argString
}
}).then(function (response) {
console.log(response);
})
实际发出去的请求,你好
已经被编码成 %E4%BD%A0%E5%A5%BD
,看着都理所当然
接下来,我们修改一下代码:
const axios = require('axios');
const argString = JSON.stringify(["你", "好"]);//"["你","好"]"
axios.get('/zzopen/sellbook/searchDefaultWord', {
params: {
arg: argString
}
}).then(function (response) {
console.log(response);
})
在看下请求:
发现和我们预想的不太一样,原本认为应该编码成 %5B%22%E4%BD%A0%22%2C%22%E5%A5%BD%22%5D
,而实际上确是[%22%E4%BD%A0%22,%22%E5%A5%BD%22]
,这不符合我们的预期呀,只编码了[
、]
中间的内容,而中括号直接pass了。带着疑问我看了下源码(https://github.com/axios/axios),最终找到了关键性的代码buildURL.js中,
在get请求传的参数会在这里构造后拼接到url后,在进行encodeURIComponent后,会在把一些encode后的的通过正则的方式在还原成了原本的字符了。What ?
为什么
看了下提交历史,这块修改的代码首次提交时间2015年7月份,看来是个历史悠久的问题。
但是为什么要加上这些代码呢,提交注释里只有don’t escape square brackets。当问题无法在继续下手时,就需要去看看URI(URL只是URI的子集)的规范了(其实是谷歌的🤦)。这里要说的rfc3986(主要这个里原因),内容比较多感兴趣的可以到这里查看https://tools.ietf.org/html/rfc3986 ,也有中文的 http://wiki.jabbercn.org/RFC3986 。在规范中提到Url只允许包含四种大类的字符(规范里称非保留字符):
- 英文字母(a-zA-Z)
- 数字(0-9)
- -_.~ 4个特殊字符
- 保留字符,RFC3986中指定了保留字符(英文字符)为:
! * ' ( ) ; : @ & = + $ , / ? # [ ]
看到这里大概就明白了,[
、]
是可以不用在进行编码的(其实编码了也没啥问题)。
在说说这个规范谁制定的,ietf,主要任务是负责互联网相关技术标准的研发和制定,是国际互联网业界具有一定权威的网络相关技术研究团体。说白了互联网标准就是这货制定的,用前端的js场景比喻的话,就有点类似TC39,rfcxxx类似mdn的文档。这个规范在2005年就制定了,为啥到现在还没有完全普及,说到这就和前端的场景更像了,虽然都是标准,但是别人可以不实现,晚实现(eg.浏览器对各语法的支持情况)。
还有疑惑未解,上面还提到了概率性的400 Bad Reques,这个由于啥原因呢,想要得到答案只能从后端下手了,后面和运维同学沟通了解到 只对个别机器的tomcat做过临时的兼容配置,到这里答案就明了。
后端的处理就是修改Tomcat提供的配置字段(conf/catalina.properties
),让其兼容需要的字符:
我们要怎么改
这里撇开服务端去修改Tomcat的配置,来谈谈前端去如何做。
- 修改请求为post
当时遇到这个问题时,没时间过多的去追究原因,就改用post请求来规避了这个问题,现在回看下,post方式规避了axios对参数的encode。
- 修改axios源码
关于这一点,在GitHub上有人提了Pull request #2563 提了,但是最终被关闭未能合并到主分支上。如果要修改的话,可以这样写 commit id:
- 参数直接拼接URL上
从axios源码上看,把请求参数拼接到URL上,其不会再对处理,像这样
const axios = require('axios');
const argString = JSON.stringify(["你", "好"]);//"["你","好"]"
axios.post('/zzopen/sellbook/searchDefaultWord?arg=' + encodeURIComponent(argString))
.then(function (response) {
console.log(response);
})
可以看到参数正常编码了
- 使用paramsSerializer处理
axios给出的建议如果参数不满足默认的编码方式的话,可以通过paramsSerializer进行自定义编码(序列化),这种方式还是值得推荐的,能够一劳永逸。
const axios = require('axios');
axios.defaults.paramsSerializer = (params) => {
return Object.keys(params).filter(it => {
return params.hasOwnProperty(it)
}).reduce((pre, curr) => {
return params[curr] ? (pre ? pre + '&' : '') + curr + '=' + encodeURIComponent(params[curr]) : pre;
}, '');
};
const argString = JSON.stringify(["你", "好"]);//"["你","好"]"
axios.get('/zzopen/sellbook/searchDefaultWord', {
params: {
arg: argString,
}
}).then(function (response) {
console.log(response);
})
最后
纸上得来终觉浅,绝知此事要躬行。自勉!