feign 调用第三方服务中部分特殊符号未转义问题
作者:0D
调用第三方部分特殊符号未转义
开发过程中,发现+(加号)这个符号没有转义,导致再调用服务的时候把加号转义成空格了。导致后台获取到的数据会不正确。
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 { /* percent encode the byte */ // 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。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。