Feign踩坑源码分析 -- 请求参数分号变逗号
一.案例
1.1.Post请求:
http://localhost:8250/xx/task/test
json格式参数:
{"string": "a;b;c;d"
}
1.2.controller代码:
@AutowiredDataSourceClientService dataSourceClientService;@RequestMapping("/test")@ResponseBodypublic 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")@ResponseBodypublic 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")@ResponseBodypublic 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问题》
相关文章:
Feign踩坑源码分析 -- 请求参数分号变逗号
一.案例 1.1.Post请求: http://localhost:8250/xx/task/test json格式参数: {"string": "a;b;c;d" } 1.2.controller代码: AutowiredDataSourceClientService dataSourceClientService;RequestMapping("/test"…...
nginx通用history模式刷新
注:1.通用配置只支持二段路由,二段及以上依然需要单独进行配置 2.所有location后面的路径,都需要使用通配符进行配置 location ^~ /phdp/ {try_files $uri $uri/ /phdp/index.html;index ruoyi.html index.html index.htm;}location ^~ /phdp-api/ {client_max_body_size 20m;p…...
Linux系统安装:Zookeeper
目录 Zookeeper的安装 1、环境准备 2、上传 3、解压文件到opt/zookeeper目下 4、安装完后进入zookeeper,找到conf目录 5、复制zoo_sample.cfg 6、编辑zoo.cfg 7、复制一份会话,进入zookeeper安装目录,创建一个文件夹zkdata࿰…...
cocos2dx+lua学习笔记:UIPageView的使用
前言 本篇在讲什么 本篇简单介绍Lua篇cocos2dx中UIPageView的相关内容 仅介绍简单的应用,仅供参考 本篇适合什么 适合初学Cocos2dX的小白 适合想要在Cocos2dx-lua中使用UIPageView的人 本篇需要什么 对Lua语法有简单认知 对Cocos2dx-Lua有简单认知 Cocos2…...
MyBatis常见面试题汇总(超详细回答)
目录 1.什么是Mybatis? 2.Mybatis的优缺点? 3.#{} 和 ${} 的区别是什么? 4.xml 映射文件中有哪些标签? 5.模糊查询 like 语句该怎么写? 6.Mapper 接口的工作原理是什么?Mapper 接口里的方法,参数不同…...
Jvm调优实战笔记
一、基础命令jps 查看所有java进程jinfo 进程号 查看该线程相关信息3、jstat 统计信息(数据跟踪信息)jstat -gc 进程号 查看该线程在内存中每一块占用的大小jstat -gc 进程号 时间(毫秒) 更新频率4、jstack 跟踪线程jstack 进程号…...
JVM 全面了解
JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载器)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。 方法区:存储已被虚拟机加载的类元数据信息(元空间) 堆…...
阿里开源自研高性能核心搜索引擎 Havenask
去年12月,阿里开源了自研的大规模分布式搜索引擎 Havenask(内部代号 HA3)。  Havenask 是阿里巴巴内部广泛使用的大规模分布式检索系统,支持了淘宝、天猫、菜鸟、优酷、高德、饿了么等在内整个阿里的搜索业务&#…...
nginx日志服务之敏感信息脱敏
1. 创建实验资源 开始实验之前,您需要先创建实验相关资源。 日志服务之敏感信息脱敏与审计 2. 创建原始数据 本步骤将指导您如何创建NGINX模拟数据。 双击打开虚拟桌面的Firefox ESR浏览器。 在RAM用户登录框中单击下一步,并复制粘贴页面左上角的子…...
【uni-app教程】一、UniAPP 介绍
一、UniAPP 介绍 (1) 什么是 UniAPP? uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到 iOS,Android,HS,以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉》等多个平台&#…...
Splunk Real-time Search 的研究
最近客户想搞清Splunk real-time search 和related search 有啥区别,想两个都试试,看看效果。 为了更好的说明什么是real-time search, 先看一下: With real-time searches and reports, you can search events before they are indexed and preview reports as the event…...
SWM181 串口功能使用介绍
SWM181 串口功能使用介绍📌SDK固件包:https://www.synwit.cn/kuhanshu_amp_licheng/✨注意新手谨慎选择作为入门单片机学习。🌼开发板如下图: 📋SWM181描述上写了有4个串口,在数据手册上,将引脚…...
Stochastic Approximation 随机近似方法的详解之(三)Dvoretzky’s convergence theorem
定理内容 Theorem 6.2 (Dvoretzky’s Theorem). Consider a stochastic process wk1(1−αk)wkβkηkw_{k1}\left(1-\alpha_k\right) w_k\beta_k \eta_kwk1(1−αk)wkβkηk, 其中{αk}k1∞,{βk}k1∞,{ηk}k1∞\{\alpha_k\}^\infty_{k1},\{\beta_k\}^\infty_{k1},\…...
7个ES6解构技巧让代码更简洁
您是否厌倦了编写臃肿且难以阅读的代码?想要提升您的编码技能并使您的代码更具可读性和简洁性? 从解构对象和数组到使用默认值和展开运算符,我们将涵盖所有内容,现在,我们将准备好掌握干净简洁的编码艺术。 1.解构对…...
曾经被人们看成是异想天开的产业互联网,或许终将会实现
一波还未平息,一波又起。元宇宙的热度还未彻底散去,ChatGPT已经成为了名符其实的新风口。如果用一个概念来定义现在这样一个热点和风口频出的时代的话,我想,用产业互联网或许是再合适不过的了。对此,可能有人并不认同。…...
log4j控制台不打印日志的故障解决方案
前言 接管了别的项目组的一个代码,在IDAE调试程序的过程中,发现log4j日志居然没有打印在控制台上,日志相关代码也没有问题。 在网上搜索了一圈,总结了一下个人解决这个问题的流程。 流程 1. 判断用了什么配置文件 不知道是出…...
C# 序列化时“检测到循环引用”错误的彻底解决方案
目录 一,问题表现 二、没有技术含量的解决方案 三、本人彻底的解决方案 简要说明 贴代码 思路解析 思路 一,问题表现 示例代码如下: [Serializable] public class NodeTest {public NodeTest (){new List<NodeTest> ();}p…...
小红书“复刻”微信,微信“内造”小红书
配图来自Canva可画 随着互联网增长红利逐渐见顶,各大互联网平台对流量的争夺变得愈发激烈。而为了寻找新的业务可能性,各家都在不遗余力地拓宽自身边界。在此背景下,目前最为“吸睛”和“吸金”的社交、电商、种草、短视频等领域,…...
用arthas轻松排查线上问题
你是否在项目中会碰到以下一些问题: 在代码中打印各种日志来排查,比如方法的入参,出参,及在方法体中打印日志判断走哪行代码还有你觉得代码没问题,可是运行出现却是以前的bug,感觉代码没修改,或…...
mysql一explain结果分析
1. EXPLAIN简介 使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。 ➤ 通过EXPLAIN,我们可以分析出以下结果: 表的读取顺序数据读取操作的操作类型哪些索引可…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
