前言
在各种生产业务中,最常出现的 IO 通信之一就是 Http 访问,而其中常见的访问方法包括 Get 请求和 Post 请求。两者的基本概念和优缺点在很多文章都有说明,如果还没了解过的朋友,可以看下这篇文章:
https://blog.csdn.net/sinat_36645384/article/details/107171364
从两者的对比可以知道,两者形式上最大的区别就在于参数的传输方式不同。对于 Post 请求,参数可以封装成表单,也可以格式化成 json 格式;而 Get 请求需要将参数拼接到访问的 url 链接后,构建成一个统一的 url 对目标站点进行访问。正因为 Get 请求的这一特性,当参数中出现特殊字符的时候,就很有可能影响 url 的结构以及其解析的结果,从而导致一系列的请求错误。
以下记录了在开发过程中遇到的各种情况的报错,有兴趣、有需要的朋友可以了解一下,着急寻获解决方案的朋友,在这里我给一个最直接粗暴的解决方式:替换成 Post 请求吧(基本上可以规避大部分问题了)。
一、问题描述
场景描述:在一个生产业务场景中,需要调用一个类似于搜索功能的接口,通过用户输入的关键字进行筛选过滤,返回目标数据。这个接口的服务链路主要包含两个部分:接收前端请求,构建中间请求参数和处理最终结果;请求第三方接口获取原始数据。
开发过程中遇到过各种情况,解决一个之后又会出现另一个新的问题。经过一番整理,主要出现的问题如下(主要都是 Get 请求出现的问题):
- 参数以字母问号
?
、与运算符号&
、井号#
开头的 Get 请求,对应变量被解析为”参数为空“或者”参数不存在“; - 参数中出现若干个空格
,访问链接解析异常,无法访问;
- 参数中出现百分号
%
,某些场景下会出现参数值解析截断; - 参数中出现中括号
[
、]
任意的字符,直接报错 400 – Bad Request; - 上述问题在前端进行 Base64 编码后传输,拼接新的 url Get 请求访问第三方接口时,同样出现上述错误。
其实在上面的描述中已经透露出了问题发生的原因和可能的解决方案,就是由于没有对参数进行编码,直接进行拼接导致的报错,经过编码后基本都可以规避上述问题。下面将会对每一个问题以及其具体的原因进行分析。
二、问题分析
1. 参数为空/不存在
这种情况在后端(比如 Java)的表现就是,获取的参数 keyword = null 或者 keyword = “”(keyword 定义为 String 时),也就是前端的 url 并没有准确将参数的值完整地传输到后端。
在 url 中,诸如字母问号 ?
、与运算符号 &
、井号 #
这样的字符属于保留字符,即它们在 url 中具有特定意义:
- ?:分隔 url 和查询参数
- &:分隔参数
- #:指定书签和锚点
因此,当 url 中的参数以这些字符开头时,参数的解析会在这些字符的位置被中断,从而将对应变量传入的参数值解析为空。
2. 空格导致访问链接解析异常
很明显,如果 url 没有经过编码,空格会被参数携带并拼接到 url 链接后,这样对应服务器来说,这个空格的含义就很模糊了,到底是参数的一部分,还是将前半部分当作 url,后半部分当作下一组字串。
因此,在一些场景下空格会被编码成加号 +
或者其 url 编码 %20
,这样才能安全准确地传输到后端接口中。
3. 参数值解析截断
百分号 %
是 url 编码(也称百分号编码)的重要标识,通常使用百分号加上两位十六进制的字符表示特定字符。如果参数值中出现百分号加上两位十六进制数的形式正好与某个特殊字符的编码值对应,那么这部分值就会被解析成对应的字符,并按照其功能继续解析 url,从而造成对原来的 url 解析异常。
要避免这类问题,同样的需要对百分号 %
字符做编码。
4. 中括号[
、]
导致报错 400 – Bad Request
通常情况下,HTTP 400 报错是由于请求参数和服务器接收参数的格式不同而导致的。这类问题通常与客户端或者浏览器没有太大的关系,主要原因通常出现在服务器端。当前生产环境常用的 tomcat 版本基本都包含一个新的特性:
严格按照 RFC 3986规范进行访问解析,而 RFC 3986 规范定义了 Url 中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符。
因此,只要 Get 请求的参数包含中括号的任一字符,都会报错 400。
5. 进行 Base64 编码后传输,在服务内部仍出现上述问题
虽然参数经过编码后顺利完整地传输到后端服务当中,但是如果后端服务的处理中没有对参数进行参数,而是仍然通过简单拼接后发起 Http Get 请求,那么上面的问题依然会出现。
三、解决方案
经过上面的分析和描述,解决方案基本已经很明显了。整理一下,大概可以分为以下几种方案:
1. 传参前先转义或者编码
很多编程语言或者开发工具都自带 url 编码或者 uri 转义的接口,完成转移后再进行请求,就能避免上述问题。想要简单处理的话,也可以手动对特殊字符进行转换,只要满足 url 编码规则,传输后的转义字符也是能够被正确识别出来的。
url 的编码格式采用的是ASCII码,因此不能在 url 中出现其他编码的字符(如 unicode 编码的中文)。具体的编码规则见下面协议支持的编码表格。
Java 的一种简单编码方式如下:
void demoUrlEncode() {
String host = "www.demo.com";
String path = "/sub/resp";
String param = "keyword=test&user_id=admin"
String url = "https://" + host + path + "?" + params;
URL urlObj = new Url(url);
URI uri = new URI(urlObj.getProtocol(), urlObj.getHost(), urlObj.getPath(), urlObj.getQuery(), null);
}
完成上面的编码后,就可以直接通过传入 URI 对象而不是简单的 url 字符串发起 Http Get 请求,这样就能规避上述的问题了。
2. 避免简单拼接构造请求 url
相比于 Get 请求通过简单拼接构造完整的请求 url,Post 请求参数的传输方式就已经在形式上保证了参数字符的完整性。不管是采用 x-www-form-urlencoded 的表单形式,还是 application/json,都对传入的参数进行了编码,且保证了访问地址 url 的独立性,防止用户自由输入的参数值影响了 url 地址的解析。
四、协议支持
RFC 3986文档规定,url 中只允许包含以下四种:
- 英文字母(a-zA-Z)
- 数字(0-9)
- -_.~ 4个特殊字符
- 所有保留字符。
RFC 3986 中指定了以下字符为保留字符(英文字符): ! * ' ( ) ; : @ & = + $ , / ? # [ ]
包括这些保留字符在内的一些特殊字符的编码值如下表所示:
字符 | url 编码值 | 字符 | url 编码值 |
---|---|---|---|
空格 | %20 | < | %3C |
" | %22 | = | %3D |
# | %23 | > | %3E |
% | %25 | ? | %3F |
& | %26 | @ | %40 |
( | %28 | \ | %5C |
) | %29 | | | %7C |
+ | %2B | { | %7B |
, | %2c | } | %7D |
/ | %2F | ^ | %5E |
: | %3A | ~ | %7E |
; | %3B | ` | %60 |
五、总结
总的来说,Get 请求在一些简单获取数据的场景应用得还是比 Post 请求要多,当请求参数或者访问链接无法由开发者完全进行限定时,就必须要考虑到编码问题,以确保用户自由输入的内容不会对服务的正常进行产生影响。对于开发者来说,目前成熟的框架和最佳实现有很多,尽可能采用前人留下的稳定的方案,避免以自己的方式实现原生的逻辑,因为你不知道在这个过程中到底会踩多少坑。