feign 调用第三方服务中部分特殊符号未转义问题

 更新时间:2022年03月07日 09:38:49   作者:0D  
这篇文章主要介绍了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 {
        /* 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。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Springboot集成magic-api的详细过程

    Springboot集成magic-api的详细过程

    这篇文章主要介绍了Springboot集成magic-api的相关知识,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06
  • mybatisplus报Invalid bound statement (not found)错误的解决方法

    mybatisplus报Invalid bound statement (not found)错误的解决方法

    搭建项目时使用了mybatisplus,项目能够正常启动,但在调用mapper方法查询数据库时报Invalid bound statement (not found)错误。本文给大家分享解决方案,感兴趣的朋友跟随小编一起看看吧
    2020-08-08
  • Java编程rabbitMQ实现消息的收发

    Java编程rabbitMQ实现消息的收发

    RabbitMQ是一个在AMQP基础上完成的,可复用的企业消息系统,本文通过实例来给大家分享通过操作rabbitMQ实现消息的收发,感兴趣的朋友可以参考下。
    2017-09-09
  • SpringBoot2.0整合Redis自定义注入bean组件配置的实战教程

    SpringBoot2.0整合Redis自定义注入bean组件配置的实战教程

    这篇文章主要介绍了SpringBoot2.0整合Redis自定义注入bean组件配置,我们将基于SpringBoot2.0整合搭建的微服务项目为奠基,开启中间件Redis的实战之路,需要的朋友可以参考下
    2023-06-06
  • SpringBoot下使用自定义监听事件的流程分析

    SpringBoot下使用自定义监听事件的流程分析

    事件机制是Spring的一个功能,目前我们使用了SpringBoot框架,所以记录下事件机制在SpringBoot框架下的使用,同时实现异步处理,这篇文章主要介绍了SpringBoot下使用自定义监听事件,需要的朋友可以参考下
    2023-08-08
  • Java动态调用类中方法代码

    Java动态调用类中方法代码

    这篇文章主要介绍了Java动态调用类中方法代码,需要的朋友可以参考下
    2014-02-02
  • SpringBoot整合PageHelper实现分页查询功能详解

    SpringBoot整合PageHelper实现分页查询功能详解

    PageHelper是mybatis框架的一个插件,用于支持在mybatis执行分页操作。本文将通过SpringBoot整合PageHelper实现分页查询功能,需要的可以参考一下
    2022-03-03
  • Spring中bean的继承与抽象代码示例

    Spring中bean的继承与抽象代码示例

    这篇文章主要介绍了Spring中bean的继承与抽象代码示例,涉及abstract 属性,bean实例化,子bean 与普通bean等相关内容,代码示例中注释比较详细,需要的朋友可以参考下。
    2017-09-09
  • idea编写yml、yaml文件以及其优先级的使用

    idea编写yml、yaml文件以及其优先级的使用

    本文主要介绍了idea编写yml、yaml文件以及其优先级的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • 一文搞懂Spring循环依赖的原理

    一文搞懂Spring循环依赖的原理

    这篇文章将用实例来为大家详细介绍@Autowired解决循环依赖的原理,文中的示例代码讲解详细,对我们学习Spring有一定帮助,感兴趣的可以学习一下
    2022-07-07

最新评论