js 处理 url 数组参数

js 处理 url 数组参数

场景

使用 axios.get 时遇到的问题,axios 在 get 请求时会将参数转换为 url 上,这本是正常的逻辑,然而 Spring MVC 却无法接收,会抛出错误。

使用 Axios 发送的请求代码

1
2
3
4
5
axios.get('/api/index/array', {
params: {
list: ['1', '2', '3'],
},
})

Spring MVC 接口代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@RequestMapping("/api/index")
public class IndexTestController {
@GetMapping("/array")
public IndexVo array(IndexVo indexVo) {
return indexVo;
}

public static class IndexVo {
private List<String> list;

public List<String> getList() {
return list;
}

public IndexVo setList(List<String> list) {
this.list = list;
return this;
}
}
}

此处为了简单演示使用了内部类

请求如下

1
2
3
4
5
6
7
8
9
10
11
12
GET /api/index/array?list[]=1&list[]=2&list[]=3 HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
DNT: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
Referer: http://localhost:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: JSESSIONID=F8E42F1AC8B9CD46A0F6678DFEB3E9F3

抛出的错误

1
java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986

说是参数中包含 RFC 7230 and RFC 3986 未定义的字符,所以说 RFC 7230 and RFC 3986 是个什么东西?

去 Google 上一搜,好吧,果然吾辈不是第一个被坑的人。没想到不是 Spring 的问题,而是新版 Tomcat(7) 的问题。Tomcat 要求 URL 中的字符必须符合 RFC 3986。
即:只能包含英文字符(a-zA-Z),数字(0-9),特殊字符(-_.~),保留字符(!*'();:@&=+$,/?#[])。

然后,作为一个 URI 的数据与作为保留字符的分隔符发生冲突了,自然是要使用 % 进行编码的。

解决

既然 Axios 本身的 get 函数中对参数进行编码有问题,那么吾辈就自己手动将 params 转换到 URL 上好了。
本以为是个很简单的功能,所以最初吾辈直接使用了 rx-util 中之前写的 spliceParams 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// @ts-check
/**
* 拼接参数字符串
* @param {Object} params 参数对象
* @returns {String} 拼接后的字符串
*/
export function spliceParams(params) {
if (!params) {
throw new Error(`参数对象不能为空:${params}`)
}
var res = ''
for (const k in params) {
if (params.hasOwnProperty(k)) {
const v = params[k]
res += `${encodeURIComponent(k)}=${encodeURIComponent(v)}&`
}
}
return res
}

然而之前没有处理的边界情况 Array 和 Date 却出现了问题,修改如下

注: 此处的 dateFormat 亦来自于 rx-util

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// @ts-check
import { dateFormat } from './../date/dateFormat'

const deteFormatter = 'yyyy-MM-ddThh:mm:ss.SSSZ'
const encode = (k, v) => encodeURIComponent(k) + '=' + encodeURIComponent(v)

/**
* 拼接参数字符串
* @param {Object} params 参数对象
* @returns {String} 拼接后的字符串
*/
export function spliceParams(params = {}) {
if (!(params instanceof Object)) {
throw new Error(`The parameter type must be Object: ${params}`)
}
return Array.from(Object.entries(params)).reduce((res, [k, v]) => {
if (v === undefined || v === null) {
return res
} else if (v instanceof Date) {
res += encode(k, dateFormat(v, deteFormatter))
} else if (v instanceof Array) {
res += v
.map(item =>
encode(
k,
item instanceof Date ? dateFormat(item, deteFormatter) : item,
),
)
.join('&')
} else {
res += encode(k, v)
}
return (res += '&')
}, '')
}

现在,spliceParams 可以正常使用了,对空值,Date 与 Array 都是友好的了!

使用的话,直接在将 axios 包装一下即可,类似于下面这样

1
2
3
4
5
6
7
8
const rxAjax = (axios => {
return {
...axios,
get(url, params, config) {
return axios.get(`${url}?${rx.spliceParams(params)}`, config)
},
}
})(axios.create())

现在,再次发送请求,参数会被正确的处理

1
2
3
4
5
axios.get('/api/index/array', {
params: {
list: ['1', '2', '3'],
},
})

请求如下

1
2
3
4
5
6
7
8
9
10
11
12
GET /api/index/array?list=1&list=2&list=3& HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
DNT: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
Referer: http://localhost:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: JSESSIONID=F8E42F1AC8B9CD46A0F6678DFEB3E9F3

或许,吾辈应该向 axios 提出这个 bug?