Feign踩坑源码分析 -- 请求参数分号变逗号

news2024/11/15 10:20:42

一.案例

  1.1.Post请求:

http://localhost:8250/xx/task/test
json格式参数:
{
  "string": "a;b;c;d"
}

  1.2.controller代码:

    @Autowired
    DataSourceClientService dataSourceClientService;
    @RequestMapping("/test")
    @ResponseBody
    public void test(@RequestBody String string) {
        System.out.println(string);
        String result = dataSourceClientService.test(string);
        System.out.println(result);
    }

  1.3.feign代码:

@FeignClient( value = "zz")
public interface DataSourceClientService {
    @RequestMapping(value = "/dataSource/test",method = RequestMethod.POST,produces = "text/plain;charset=UTF-8")
    String test(@RequestParam("str") String str);
}

  1.4服务提供方代码:

    @RequestMapping("/test")
    @ResponseBody
    public String test(@RequestParam String str) {
        System.out.println(str);
        String result = "success;";
        return result;
    }

  1.5发起请求后控制台打印结果:

请求方控制台:
 用户[null]开始调用web接口:http://localhost:8250/xx/task/test
{
  "string": "a;b;c;d"
}

服务提供方控制台:
用户[null]开始调用web接口:http://localhost:8247/zz/dataSource/test
{
  "string": "a,b,c,d"
}

二.解决办法

  2.1.在请求方对参数进行编码:

        string = UriUtils.encode(string, StandardCharsets.UTF_8);
        String test = dataSourceClientService.test(string);

   2.2.服务提供方@RequestParam改成@RequestBody;

三.分析

   3.1.需求

  服务提供方需要的字符串是包含“;”而不是“,”,因为实际传递的是一个JSONObject的字符串,导致JSONUtil.parse转换成对象失败,导致业务失败;

   3.2请求方法参数@RequestParam而传参不加密

     3.2.1 请求方源码探究

   通过一步步debug代码调试,发现调用 dataSourceClientService.test(string) 是jdk动态代理,调用链接到

    feign.ReflectiveFeign.FeignInvocationHandler#invoke

    -->feign.SynchronousMethodHandler#invoke

    -->feign.ReflectiveFeign.BuildTemplateByResolvingArgs#create

    -->feign.ReflectiveFeign.BuildTemplateByResolvingArgs#resolve

    -->feign.RequestTemplate#resolve(java.util.Map<java.lang.String,?>)

    -->feign.template.QueryTemplate#expand

    -->feign.template.QueryTemplate#queryString

 static final String COLLECTION_DELIMITER = ";"; 
 private String queryString(String name, String values) {
    if (this.pure) {
      return name;
    }

    /* covert the comma separated values into a value query string */
    List<String> resolved = Arrays.stream(values.split(COLLECTION_DELIMITER))
        .filter(Objects::nonNull)
        .filter(s -> !UNDEF.equalsIgnoreCase(s))
        .collect(Collectors.toList());

    if (!resolved.isEmpty()) {
      return this.collectionFormat.join(name, resolved, this.getCharset()).toString();
    }

    /* nothing to return, all values are unresolved */
    return null;
  }

   从上面可以看到,一个参数字符串“a;b;c;d”被分割处理成了一个数组,然后重新组合成字符串(Collection)传递给服务提供方,服务提供方接收参数是一个string字符串,会用逗号给拼接成字符串。

  也就是说,feign预设了使用者如果是用传参带了分号过来的,会认为你传的是Collection,而不是String。

    3.2.1 服务提供方源码探究

  下面是服务提供方接收的参数详情:  

org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument

   从上面可以看出,请求解析出来是一个字符串数组,在

org.springframework.core.convert.support.CollectionToStringConverter#convert 中用逗号拼接起来:

    3.2.1 调用服务提供方方法前参数编码:

    当没有编码前,org.springframework.core.convert.support.GenericConversionService#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) 调用链进入的是org.springframework.core.convert.support.CollectionToStringConverter#convert 中用逗号拼接起来,因为参数类型是字符串数组

   当编码后,进入的是org.springframework.core.convert.support.GenericConversionService.NoOpConverter#convert,直接返回字符串:

  参数值的获取在org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveName 中

   参数的解码方法在org.apache.tomcat.util.http.Parameters#processParameters(byte[], int, int, java.nio.charset.Charset)中进入

   在org.apache.tomcat.util.http.Parameters#urlDecode进行解码

   前面的是参数进行编码后解码,因为编码成一个字符串,所以解码的也是字符串,当没有进行编码,传递的会认为是一个collection,所以会遍历解码:

   然后加入到数组中:

  3.3请求方法参数改成@RequestBody

   3.3.1服务提供方

  当请求方法是@RequestBody时,获取参数在org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#resolveArgument:

   直接从httprequest请求中获取body参数值:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)

  3.3.2请求方

  参数直接封装到body中:

  feign.ReflectiveFeign.BuildTemplateByResolvingArgs#create

  --> feign.ReflectiveFeign.BuildEncodedTemplateFromArgs#resolve

    对比3.2.1流程会发现,body不会进行字符串的切割,当然拉,也跟参数的请求类型不一致有关,一个是query,一个是body,下面是3.2.1流程

    -->feign.ReflectiveFeign.BuildTemplateByResolvingArgs#create

    -->feign.ReflectiveFeign.BuildTemplateByResolvingArgs#resolve

    -->feign.RequestTemplate#resolve(java.util.Map<java.lang.String,?>)

    -->feign.template.QueryTemplate#expand

    -->feign.template.QueryTemplate#queryString

四.扩展 

  4.1.get请求

  get请求的参数类型是query,所以走的是3.2.1流程,所以string的传参中包含“;”也会被转换成“,”; 

@FeignClient( value = "zz")
public interface DataSourceClientService {

    @RequestMapping(value = "/dataSource/testGet",method = RequestMethod.GET,produces = "text/plain;charset=UTF-8")
    String testGet(@RequestParam("str") String str);
    
}
    @RequestMapping(value = "/testGet")
    @ResponseBody
    public String testGet(String str) {
        System.out.println(str);
        String result = "success";
        return result;
    }
服务提供方控制台输出:
用户[null]开始调用web接口:http://localhost:8247/zz/dataSource/testGet
a,b,c,d

  ps:当请求参数字符串存在特殊符号比如“+”时,会被转义为空格

http://localhost:8250/xx/task/test2?string=a+b

请求方控制台输出:
用户[null]开始调用web接口:用户[null]开始调用web接口:http://localhost:8250/xx/task/test2
a b

  通过debug调试发现当发起请求时在org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest中获取参数:

   继续调用链:

  -->org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument

  -->org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument

  -->org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveName

  -->org.springframework.web.context.request.ServletWebRequest#getParameterValues

  -->org.apache.catalina.connector.RequestFacade#getParameterValues

  -->org.apache.catalina.connector.Request#getParameterValues

  -->org.apache.coyote.Request#getParameters

   从上面可以得知参数在parameters中,并且特殊符号已经被处理过了,那接下来找下什么时候放进来和怎么被处理的;在org.apache.catalina.connector.Request中看见了parseParameters方法,可以看出是对参数的解析方法,在里面打下断点重新跑下:

   从下面可以看出这里是真正进行参数解析处理的:

   org.apache.tomcat.util.http.Parameters#handleQueryParameters:

   -->org.apache.tomcat.util.http.Parameters#processParameters(org.apache.tomcat.util.buf.MessageBytes, java.nio.charset.Charset)

  -->org.apache.tomcat.util.http.Parameters#processParameters(byte[], int, int, java.nio.charset.Charset)

  -->org.apache.tomcat.util.http.Parameters#urlDecode

   从上面可以看到,是在进行解码时将“+”去除掉的;

  继续debug发现在下面的代码中,可以清晰的看到对“+”替换成空格;

  -->org.apache.tomcat.util.buf.UDecoder#convert(org.apache.tomcat.util.buf.ByteChunk, boolean)

  解决办法为:

  1)对参数“a+b”进行编码;

  2)改成post请求;

  4.2.header参数 

  在网上看见一篇文章,在这里收藏下《FeignClient传入的header中带逗号引发的401问题》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/383304.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《计算机原理》——HelloWorld.cpp如何运行的

学校《计算机原理》开课啦&#xff01;特此开辟专栏&#xff0c;将一些知识作为笔记&#xff0c;记录下来。 前言 本篇博客知识点来源于educoder的相关题目 1. 相关知识 1.1 计算机语言 计算机语言是人与计算机之间通讯的语言&#xff0c;计算机语言包括编写计算机程序的字符…

[MatLab]图像绘制

一、绘制二维图像 1.一张图上绘制一条线 绘制代码如下面所示&#xff1a; x 0:0.01:2*pi; y sin(x); figure %建立幕布 plot(x,y) %绘制图像 %设置图像属性 title(ysin(x)) xlabel(x) ylabel(y)xlim([0 2*pi]) %限制x轴的值域 自定义图线的颜色…

GB28181协议--SIP协议介绍

1、SIP协议简介 SIP&#xff08;Session Initiation Protocol&#xff0c;会话初始协议&#xff09;是一个用于建立、更改和终止多媒体会话的应用层控制协议&#xff0c;其中的会话可以是IP电话、多媒体会话或多媒体会议&#xff08;GB28181安防使用的是SIP协议&#xff09;。S…

lab备考第二步:HCIE-Cloud-Compute-第一题:FusionCompute

第一题 FusionCompute 一、题目介绍 1.1. 扩容CAN节点与对接共享存储&#xff08;必选&#xff09; 题目及【考生提醒关键点】 扩容一台CNA节点&#xff0c;配置管理地址设置为&#xff1a;192.168.100.212。密码设置为&#xff1a;Cloud12#$。【输入之前确认自己的大小写是否…

任务类风险漏洞挖掘思路

任务类风险定义&#xff1a; 大部分游戏都离不开任务&#xff0c;游戏往往也会借助任务&#xff0c;来引导玩家上手&#xff0c;了解游戏背景&#xff0c;增加游戏玩法&#xff0c;提升游戏趣味性。任务就像线索&#xff0c;将游戏的各个章节&#xff0c;各种玩法&#xff0c;…

docker上安装nacos

文章目录一、docker安装nacos简单版1.拉取镜像2、挂载目录&#xff0c;用于映射到容器&#xff0c;目录按自己的情况创建3、mysql新建nacos-config的数据库&#xff0c;并执行脚本 sql脚本地址如下&#xff1a;4、修改配置文件custom.properties5、启动容器6、访问二、docker安…

错误:PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。“+文件路径“的解决方案

最近在使用python进行筛选图片的时候&#xff0c;想到用python里面的os库进行图片的删除。 具体筛选方法就是&#xff0c;删除掉图片长度或宽度小于100像素的图片&#xff0c;示例代码如下所示&#xff1a; for file in os.listdir(img_path):if file .split( . )[ - 1 ] j…

深度强化学习DLR

1 强化学习基础知识 强化学习过程&#xff1a;⾸先环境(Env)会给智能体(Agent)⼀个状态(State)&#xff0c;智能体接收到环境给的观测值之后会做出⼀个动作(Action)&#xff0c;环境接收到智能体给的动作之后会做出⼀系列的反应&#xff0c;例如对这个动作给予⼀个奖励(Reward…

射频功率放大器基于纵向导波的杆状构件腐蚀诊断方法的研究

实验名称&#xff1a;基于纵向导波的杆状构件腐蚀诊断方法研究方向&#xff1a;无损探伤测试设备&#xff1a;信号号发生器、安泰ATA-8202功率放大器、数据采集卡、直流电源、超声探头、钢杆、前置放大器。实验过程&#xff1a;图&#xff1a;试验装置试验装置如图3.2所示。监测…

Android Handler机制(四) Message源码分析

一. 简介 接上一篇文章:Android Handler机制(三) Looper源码分析 ,我们来继续分析一下Message源码 这一系列文章都是为了深入理解Handler机制. Message 作为消息传递的载体&#xff0c;源码主要分为以下 几个部分: 1. 操作数据相关&#xff0c;类似 getter()和 setter()这种…

【JAVASE】注解

文章目录1.概述2.JDK内置注解2.1override注解2.2 Deprecated注解3.元注解4.注解中定义属性4.1 属性value4.2 属性是一个数组5. 反射注解6.注解在开发中的作用1.概述 注解&#xff0c;也叫注释&#xff0c;是一种引用数据类型。编译后也同样生成class字节码文件。 语法 [修饰…

QT获取dll库文件详细信息

一、需求背景获取软件下依赖的dll库的版本信息&#xff0c;如下图所示版本为1.0.7.1018二、实现方法2.1步骤windows下实现&#xff0c;基于version.lib(version.dll)提供的函数获取这些信息首先使用GetFileVersionInfoSizeA(W)获取VersionInfo的大小&#xff0c;申请缓冲区&…

团队API管理工具-YAPI

团队API管理工具-YAPI 推荐一款接口管理平台&#xff0c;操作简单、界面友好、功能丰富、支持markdown语法、可使用Postman导入、Swagger同步数据展示、LDAP、权限管理等功能。 YApi是高效、易用、功能强大的api管理平台&#xff0c;旨在为开发、产品、测试人员提供更优雅的接…

stm32智能家居+微信小程序接收控制

这里写目录标题项目介绍mqtt服务器相关知识![在这里插入图片描述](https://img-blog.csdnimg.cn/9ad065fb8fac48b1b975fc3a48b99763.png)下位机代码项目需要的一些开发工具项目介绍 本项目芯片使用STM32F103ZET6,微信小程序开发使用微信开发者工具。 stm32作为下位机&#xff…

【实现点击下载按钮功能 Objective-C语言】

一、实现点击下载按钮功能, 1.接下来,我们再实现另外一个功能,是什么,点击下载按钮吧: 点击下载按钮,是不是要有效果啊, 就是给大家实现这个功能, 首先,我们要实现单击这个效果,是不是要给按钮注册单击事件吧, 请问,这个按钮在哪里啊,是在控制器里面吗,不是,…

Spark性能优化一 概念篇

&#xff08;一&#xff09;宽依赖和窄依赖 窄依赖(Narrow Dependency)&#xff1a;指父RDD的每个分区只被子RDD的一个分区所使用&#xff0c;例如map、filter等 这些算子一个RDD&#xff0c;对它的父RDD只有简单的一对一的关系&#xff0c;也就是说&#xff0c;RDD的每个part…

Linux 系统 /var/log/journal/ 垃圾日志清理

CentOS系统中有两个日志服务&#xff0c;分别是传统的 rsyslog 和 systemd-journal systemd-journald是一个改进型日志管理服务&#xff0c;可以收集来自内核、系统早期启动阶段的日志、系统守护进程在启动和运行中的标准输出和错误信息&#xff0c;还有syslog的日志。systemd…

datax导入到hive的数据量翻倍

现象 mysql->hive 或者oracle->hdfs 源表数据100w 结果hive表数据200w。 这个现象很容易发生&#xff0c;只要你同一时间调度这个json两次。 原因 "writeMode" : "append", "nonconflict","truncate" * append&#xff…

无线WiFi安全渗透与攻防(二)之打造专属字典

系列文章 无线WiFi安全渗透与攻防(一)之无线安全环境搭建 打造专属字典 什么在破解之前先准备专用字典&#xff0c;因为对于一般家庭来说&#xff0c;常用 一个是预共享密钥PSK&#xff0c;一个是PIN码。 也不是所有的路由都开起了PIN码&#xff0c;一般都会开启域共享密钥…

【蓝桥杯嵌入式】定时器实现按键单击,双击,消抖以及长按的代码实现

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都在这儿哦&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 - 蓝…