好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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 拦截器,在这里对参数再次进行转义即可。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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 版本

?

1

2

3

4

5

6

<!-- https://mvnrepository测试数据/artifact/io.github.openfeign/feign-core -->

< dependency >

     < groupId >io.github.openfeign</ groupId >

     < artifactId >feign-core</ artifactId >

     < version >10.4.0</ version >

</ dependency >

调用路径

源码分析

1.Template 类

?

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

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

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(),所以不会被转义。

测试

?

1

2

3

4

5

6

7

8

9

10

11

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。

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

原文链接:https://blog.csdn.net/M_drm/article/details/106135728

查看更多关于feign 调用第三方服务中部分特殊符号未转义问题的详细内容...

  阅读:29次