feign调用接口参数可以为null吗_FeignClient调用POST请求时查询参数被丢失的情况分析与处理...

前言

本文没有详细介绍 FeignClient 的知识点,网上有很多优秀的文章介绍了 FeignCient 的知识点,在这里本人就不重复了,只是专注在这个问题点上。

查询参数丢失场景

业务描述: 业务系统需要更新用户系统中的A资源,由于只想更新A资源的一个字段信息为B,所以没有选择通过 entity 封装B,而是直接通过查询参数来传递B信息

文字描述:使用FeignClient来进行远程调用时,如果POST请求中有查询参数并且没有请求实体(body为空),那么查询参数被丢失,服务提供者获取不到查询参数的值。

代码描述:B的值被丢失,服务提供者获取不到B的值

@FeignClient(name = "a-service", configuration = FeignConfiguration.class)

public interface ACall {

@RequestMapping(method = RequestMethod.POST, value = "/api/xxx/{A}", headers = {"Content-Type=application/json"})

void updateAToB(@PathVariable("A") final String A, @RequestParam("B") final String B) throws Exception;

}

问题分析

背景

使用 FeignClient 客户端

使用 feign-httpclient 中的 ApacheHttpClient 来进行实际请求的调用

com.netflix.feign

feign-httpclient

8.18.0

直入源码

通过对 FeignClient 的源码阅读,发现问题不是出在参数解析上,而是在使用 ApacheHttpClient 进行请求时,其将查询参数放进请求body中了,下面看源码具体是如何处理的

feign.httpclient.ApacheHttpClient 这是 feign-httpclient 进行实际请求的方法

@Override

public Response execute(Request request, Request.Options options) throws IOException {

HttpUriRequest httpUriRequest;

try {

httpUriRequest = toHttpUriRequest(request, options);

} catch (URISyntaxException e) {

throw new IOException("URL '" + request.url() + "' couldn't be parsed into a URI", e);

}

HttpResponse httpResponse = client.execute(httpUriRequest);

return toFeignResponse(httpResponse);

}

HttpUriRequest toHttpUriRequest(Request request, Request.Options options) throws

UnsupportedEncodingException, MalformedURLException, URISyntaxException {

RequestBuilder requestBuilder = RequestBuilder.create(request.method());

//per request timeouts

RequestConfig requestConfig = RequestConfig

.custom()

.setConnectTimeout(options.connectTimeoutMillis())

.setSocketTimeout(options.readTimeoutMillis())

.build();

requestBuilder.setConfig(requestConfig);

URI uri = new URIBuilder(request.url()).build();

requestBuilder.setUri(uri.getScheme() + "://" + uri.getAuthority() + uri.getRawPath());

//request query params

List queryParams = URLEncodedUtils.parse(uri, requestBuilder.getCharset().name());

for (NameValuePair queryParam: queryParams) {

requestBuilder.addParameter(queryParam);

}

//request headers

boolean hasAcceptHeader = false;

for (Map.Entry> headerEntry : request.headers().entrySet()) {

String headerName = headerEntry.getKey();

if (headerName.equalsIgnoreCase(ACCEPT_HEADER_NAME)) {

hasAcceptHeader = true;

}

if (headerName.equalsIgnoreCase(Util.CONTENT_LENGTH)) {

// The 'Content-Length' header is always set by the Apache client and it

// doesn't like us to set it as well.

continue;

}

for (String headerValue : headerEntry.getValue()) {

requestBuilder.addHeader(headerName, headerValue);

}

}

//some servers choke on the default accept string, so we'll set it to anything

if (!hasAcceptHeader) {

requestBuilder.addHeader(ACCEPT_HEADER_NAME, "*/*");

}

//request body

if (request.body() != null) {

//body为空,则HttpEntity为空

HttpEntity entity = null;

if (request.charset() != null) {

ContentType contentType = getContentType(request);

String content = new String(request.body(), request.charset());

entity = new StringEntity(content, contentType);

} else {

entity = new ByteArrayEntity(request.body());

}

requestBuilder.setEntity(entity);

}

//调用org.apache.http.client.methods.RequestBuilder#build方法

return requestBuilder.build();

}

org.apache.http.client.methods.RequestBuilder 此类是 HttpUriRequest 的Builder类,下面看build方法

public HttpUriRequest build() {

final HttpRequestBase result;

URI uriNotNull = this.uri != null ? this.uri : URI.create("/");

HttpEntity entityCopy = this.entity;

if (parameters != null && !parameters.isEmpty()) {

// 这里:如果HttpEntity为空,并且为POST请求或者为PUT请求时,这个方法会将查询参数取出来封装成了HttpEntity

// 就是在这里查询参数被丢弃了,准确的说是被转换位置了

if (entityCopy == null && (HttpPost.METHOD_NAME.equalsIgnoreCase(method)

|| HttpPut.METHOD_NAME.equalsIgnoreCase(method))) {

entityCopy = new UrlEncodedFormEntity(parameters, charset != null ? charset : HTTP.DEF_CONTENT_CHARSET);

} else {

try {

uriNotNull = new URIBuilder(uriNotNull)

.setCharset(this.charset)

.addParameters(parameters)

.build();

} catch (final URISyntaxException ex) {

// should never happen

}

}

}

if (entityCopy == null) {

result = new InternalRequest(method);

} else {

final InternalEntityEclosingRequest request = new InternalEntityEclosingRequest(method);

request.setEntity(entityCopy);

result = request;

}

result.setProtocolVersion(this.version);

result.setURI(uriNotNull);

if (this.headergroup != null) {

result.setHeaders(this.headergroup.getAllHeaders());

}

result.setConfig(this.config);

return result;

}

解决方案

既然已经知道原因了,那么解决方法就有很多种了,下面就介绍常规的解决方案:

使用 feign-okhttp 来进行请求调用,这里就不列源码了,感兴趣大家可以去看, feign-okhttp 底层没有判断如果body为空则把查询参数放入body中。

使用 io.github.openfeign:feign-httpclient:9.5.1 依赖,截取部分源码说明原因如下:

HttpUriRequest toHttpUriRequest(Request request, Request.Options options) throws

UnsupportedEncodingException, MalformedURLException, URISyntaxException {

RequestBuilder requestBuilder = RequestBuilder.create(request.method());

//省略部分代码

//request body

if (request.body() != null) {

//省略部分代码

} else {

// 此处,如果为null,则会塞入一个byte数组为0的对象

requestBuilder.setEntity(new ByteArrayEntity(new byte[0]));

}

return requestBuilder.build();

}

推荐的依赖

io.github.openfeign

feign-httpclient

9.5.1

或者

io.github.openfeign

feign-okhttp

9.5.1

总结

目前绝大部分的介绍 feign 的文章(本人所看到的,包括本人之前写的一篇文章也是)中都是推荐的 com.netflix.feign:feign-httpclient:8.18.0 和 com.netflix.feign:feign-okhttp:8.18.0 ,如果不巧你使用了 com.netflix.feign:feign-httpclient:8.18.0,那么在POST请求时并且body为空时就会发生丢失查询参数的问题。

这里推荐大家使用 feign-httpclient 或者是 feign-okhttp的时候不要依赖 com.netflix.feign,而应该选择 io.github.openfeign,因为看起来 Netflix 很久没有对这两个组件进行维护了,而是由 OpenFeign 来进行维护了。

参考资料:

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

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

相关文章

定时器相关

let timer null;setInterval(() > {let date new Date();let hours date.getHours();if (hours < 1 || hours > 7) { // 1-8点不更新if(timer null){timer setInterval(()>{this.$store.dispatch(GetUnreadMessageCount) // 每隔10分钟更新TOKEN}, 10 * 60 * …

它将改变一切:AI解决了生物学50年来的重大难题

图片来源&#xff1a;Pixabay来源 公众号“机器之心”CASP14 组织者、年近七旬的 UC Davis 科学家 Andriy Kryshtafovych 在大会上感叹道&#xff0c;I wasnt sure that I would live long enough to see this&#xff08;我活久见了&#xff09;[1]。11 月 30 日&#xff0c;…

教你彻底学会动态规划——入门篇

动态规划相信大家都知道&#xff0c;动态规划算法也是新手在刚接触算法设计时很苦恼的问题&#xff0c;有时候觉得难以理解&#xff0c;但是真正理解之后&#xff0c;就会觉得动态规划其实并没有想象中那么难。网上也有很多关于讲解动态规划的文章&#xff0c;大多都是叙述概念…

linux终端密码星星,如何获得您的sudo密码在Ubuntu中显示为星号 | MOS86

我的一个朋友最近从Windows切换到Ubuntu Linux。在新操作系统花了一个星期左右的时间&#xff0c;他提出了一个问题109mh1112虽然这确实导致了这里和那里的一些打字错误&#xff0c;一次输入正确的sudo密码已被证明是非常有挑战性的&#xff0c;主要是因为在输入密码时没有显示…

正则 null_正则表达式exec、match、test的区别

一、定义的不同RegExp.prototype.test()RegExp.prototype.exec()String.prototype.match()从MDN的定义可以看出&#xff0c;test和exec是正则实例的API&#xff0c;match是String的&#xff0c;这一点决定了调用方式的不同。二、应用场景的不同如果只是想要判断正则表达式和字符…

Gartner发布2021年重要战略科技趋势!

来源&#xff1a;Gartner不久前&#xff0c;全球领先的信息技术研究和顾问公司Gartner发布企业机构在2021年需要深挖的重要战略科技趋势。分析师们在举行的Gartner IT Symposium/Xpo大会美洲站虚拟会议上展示了自己的发现。Gartner研究副总裁Brian Burke表示&#xff1a;“各企…

linux at24测试程序,linux 2.6下eeprom at24c08 i2c设备驱动(new style probe方式)

1 修改bsp_以便支持probe1.1 AT24C08地址的确定原理图上将A2、A1、A0都接地了&#xff0c;所以地址是0x50。注意到是7位(bit).1.2 修改bsp采用友善之臂的, 2.6.32.2内核[rootlocalhost mach-s3c2440]# vim/opt/FriendlyARM/mini2440/linux-2.6.32.2/arch/arm/mach-s3c2440/mach…

公众号 接收规则 消息_微信公众平台 发送模板消息(Java接口开发)

前言&#xff1a;最近一直再弄微信扫码推送图文消息和模板消息发送&#xff0c;感觉学习到了不少东西。今天先总结一下微信公众平台模板消息的发送。因为这个自己弄了很久&#xff0c;开始很多地方不明白&#xff0c;所以今天好好总结一下。微信公众平台技术文档&#xff1a;模…

Java中注释的使用

如何在Java中使用注释 在编写程序时&#xff0c;经常需要添加一些注释&#xff0c;用以描述某段代码的作用。 一般来说&#xff0c;对于一份规范的程序源代码而言&#xff0c;注释应该占到源代码的 1/3 以上。因此&#xff0c;注释是程序源代码的重要组成部分&#xff0c;一定要…

关于动态规划,你想知道的都在这里了!

作者 | Your DevOps Guy翻译| 火火酱~&#xff0c;责编 | 晋兆雨出品 | AI科技大本营头图 | 付费下载于视觉中国什么是动态规划&#xff1f;它又有什么重要的呢&#xff1f;在本文中&#xff0c;我将介绍由Richard Bellman在20世纪50年代提出的动态规划&#xff08;dynamic pro…

linux修改永久ip地址,centos设置IP地址,永久修改ipv4

# ifconfig #查看下本机的IP地址。eth0Link encap:Ethernet HWaddr 00:50:56:0A:0B:0Cinet addr:192.168.0.3 Bcast:192.168.0.255 Mask:255.255.255.0inet6 addr: fe80::250:56ff:fe0a:b0c/64 Scope:LinkUP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1RX packets:172220…

详细介绍MySQL/MariaDB的锁

官方手册&#xff1a;https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-transaction-model.html 1.事务提交的方式 在MariaDB/MySQL中有3种事务提交的方式。 1.显式开启和提交。 使用begin或者start transaction来显式开启一个事务&#xff0c;显式开启的事务必须使用c…

filter导致跨域失效_【SpringMVC】与权限拦截器冲突导致的Cors跨域设置失效问题...

问题描述前端域名FE.com向后端域名BE.com分别请求访问优惠券的列表和提交新增的优惠券&#xff0c;API设计所用的Method分别为Get和Post&#xff0c;结果为前一次访问成功而后一次访问失败。这两次请求都是跨域请求&#xff0c;其中请求1包含一个Get请求&#xff0c;请求2本应该…

美国专利商标局发布人工智能专利扩散分析报告

以下文章来源&#xff1a;中科院知识产权信息&#xff0c;2020-11-23报告显示&#xff0c;从2002到2018年&#xff0c;美国人工智能专利的年申请量增长超过100%&#xff0c;从每年3万件增加到6万多件&#xff0c;含人工智能的专利申请所占份额从9%上升到近16%。同时&#xff0c…

java 开发环境的搭建

这里主要说的是在windows 环境下怎么配置环境。 1.首先安装JDK java的sdk简称JDK &#xff0c;去其官方网站下载最近的JDK即可。。http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html点击下载好的exe文件安装即可。 2.接下来我们需要配置环…

linux内核等价多路径路由,Linux内核分析 - 网络[四]:路由表

路由表的创建inet_init() -> ip_init() -> ip_fib_init() -> fib_net_init() -> ip_fib_net_init()[net\ipv4\fib_frontend.c]首先为路由表分配空间&#xff0c;这里的每个表项hlist_head实际都会链接一个单独的路由表&#xff0c;FIB_TABLE_HASHSZ表示了分配多少个…

2017级面向对象程序设计 作业二

以下均以扫描方式为例&#xff0c;即电梯只会在最底层和最高层选择掉头&#xff0c;路途中遇到路径方向相同的乘客将他带上电梯。 文字描述面向过程实现的步骤&#xff1a; 一. 定义有关电梯的变量&#xff0c;如&#xff1a;1.电梯当前所在楼层.&#xff0c;2. 电梯内的人数&a…

server sql top速度变慢解决方案_SQL Server数据库查询速度慢的原因和解决方法

SQL Server数据库查询速度慢的原因有很多&#xff0c;常见的有以下几种&#xff1a;1、没有索引或者没有用到索引(这是查询慢最常见的问题&#xff0c;是程序设计的缺陷)2、I/O吞吐量小&#xff0c;形成了瓶颈效应。3、没有创建计算列导致查询不优化。4、内存不足5、网络速度慢…

新型支架状电极允许人类思想操作计算机

Illustration: Synchron来源&#xff1a;IEEE电气电子工程师据悉&#xff0c;两名患有神经肌肉疾病的澳大利亚人在他们的大脑中植入了支架状的电极&#xff0c;使他们能够利用自己的思想操作电脑&#xff0c;从而恢复了一些个人独立性。据发明者介绍&#xff0c;这是这种被称为…

java中的foreach

foreach 并不是java中的关键字&#xff0c;是for语句的特殊简化版&#xff0c;在比那里数组&#xff0c;集合时&#xff0c;foreach更加简单快捷&#xff0c;从字面上的意思理解 foreach 也就是 “ for每一个 ”的意思&#xff0c;那么到底怎么使用 foreach语句呢&#xff1f; …