feign调用第三方服务中部分特殊符号未转义问题
调用第三方部分特殊符号未转义
开发过程中,发现+(加号)这个符号没有转义,导致再调用服务的时候把加号转义成空格了。导致后台获取到的数据会不正确。
1. 问题发现过程
feign 解析参数的时候,使用的标准是 RFC 3986,这个标准的加号是不需要被转义的。其具体的实现是 feign.template.UriUtils#encodeReserved(String value, String reserved, Charset charset)
2. 解决办法
feign 调用过程
1. feign核心先将(定义好的feign接口)接口中的参数解析出来
2. 对接实际参数和接口参数(入参调用的参数)
3. 对入参的参数进行编码(UriUtils#encodeReserved)(问题出在这里)
4. 调用注册的 RequestInterceptor(自定义)
5. Encoder 实现类,这里是body里面的内容才会有调用(自定义)
6. 具体的http网络请求逻辑
依据上面的过程,我们可以实现一个 RequestInterceptor 拦截器,在这里对参数再次进行转义即可。
public void apply(RequestTemplate template) {
Map<String, Collection<String>> _queries = template.queries();
if (!_queries.isEmpty()) {
//由于在最新的 RFC 3986 规范,+号是不需要编码的,因此spring 实现的是这个规范,这里就需要参数中进行编码先,兼容旧规范。
Map<String, Collection<String>> encodeQueries = new HashMap<String, Collection<String>>(_queries.size());
Iterator<String> iterator = _queries.keySet().iterator();
Collection<String> encodeValues = null;
while (iterator.hasNext()) {
encodeValues = new ArrayList<>();
String key = iterator.next();
Collection<String> values = _queries.get(key);
for (String _str : values) {
_str = _str.replaceAll("\\+", "%2B");
encodeValues.add(_str);
}
encodeQueries.put(key, encodeValues);
}
template.queries(null);
template.queries(encodeQueries);
}
}
上面是代码片段,详细请查看 FeignRequestInterceptor.java
3. 疑问
3.1 是否可以使用 HTTPClient 的实现就可以解决问题?
也不行,如果不做上面的实现,直接改用HTTPClient实现的话,也只是在发送的过程中起到作用,还是需要在前进行处理。
@RequestParams & 符号未转义
feign-core 版本
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-core -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>10.4.0</version>
</dependency>
调用路径
源码分析
1.Template 类
package feign.template;
...
public class Template {
protected String resolveExpression(Expression expression, Map<String, ?> variables) {
String resolved = null;
Object value = variables.get(expression.getName());
// 1. 调用 SimpleExpression 的 expand() 方法
return expression.expand(value, this.encode.isEncodingRequired());
}
}
public final class Expressions {
static class SimpleExpression extends Expression {
private final FragmentType type;
String encode(Object value) {
// 2. 调用 UriUtils.encodeReserved() 方法,type 参数是 FragmentType.PATH_SEGMENT
return UriUtils.encodeReserved(value.toString(), type, Util.UTF_8);
}
@Override
String expand(Object variable, boolean encode) {
StringBuilder expanded = new StringBuilder();
expanded.append((encode) ? encode(variable) : variable);
String result = expanded.toString();
return result;
}
}
}
public class UriUtils {
public static String encodeReserved(String value, FragmentType type, Charset charset) {
return encodeChunk(value, type, charset);
}
private static String encodeChunk(String value, FragmentType type, Charset charset) {
byte[] data = value.getBytes(charset);
ByteArrayOutputStream encoded = new ByteArrayOutputStream();
for (byte b : data) {
if (type.isAllowed(b)) {
// 3.1 如果不需要转义,则不进行转义操作
encoded.write(b);
} else {
// 3.2 否则,进行编码
pctEncode(b, encoded);
}
}
return new String(encoded.toByteArray());
}
enum FragmentType {
URI {
@Override
boolean isAllowed(int c) {
return isUnreserved(c);
}
},
PATH_SEGMENT {
@Override
boolean isAllowed(int c) {
return this.isPchar(c) || (c == '/');
}
}
abstract boolean isAllowed(int c);
protected boolean isAlpha(int c) {
return (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z');
}
protected boolean isDigit(int c) {
return (c >= '0' && c <= '9');
}
protected boolean isSubDelimiter(int c) {
return (c == '!') || (c == '$') || (c == '&') || (c == '\'') || (c == '(') || (c == ')')
|| (c == '*') || (c == '+') || (c == ',') || (c == ';') || (c == '=');
}
protected boolean isUnreserved(int c) {
return this.isAlpha(c) || this.isDigit(c) || c == '-' || c == '.' || c == '_' || c == '~';
}
protected boolean isPchar(int c) {
return this.isUnreserved(c) || this.isSubDelimiter(c) || c == ':' || c == '@';
}
}
}
从源码上可以看出,& 字符属于 isSubDelimiter(),所以不会被转义。
测试
package feign.template;
import feign.Util;
public class UriUtilsDemo {
public static void main(String[] args) {
String str = "aa&aa";
// 输出:aa&aa
System.out.println(UriUtils.encodeReserved(str, UriUtils.FragmentType.PATH_SEGMENT, Util.UTF_8));
// 输出:aa%26aa
System.out.println(UriUtils.encodeReserved(str, UriUtils.FragmentType.URI, Util.UTF_8));
}
}
解决方案
1、升级 feign-core 版本,feign-core-10.12 已经没有这个问题。
2、使用 @RequestBody 替换 @RequestParam。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341