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来进行实际请求的调用
-
<dependency>
-
<groupId>com.netflix.feign</groupId>
-
<artifactId>feign-httpclient</artifactId>
-
<version>8.18.0</version>
-
</dependency>
直入源码
通过对 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<NameValuePair> queryParams = URLEncodedUtils.parse(uri, requestBuilder.getCharset().name());
-
for (NameValuePair queryParam: queryParams) {
-
requestBuilder.addParameter(queryParam);
-
}
-
-
//request headers
-
boolean hasAcceptHeader = false;
-
for (Map.Entry<String, Collection<String>> 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();
-
}
推荐的依赖
-
<dependency>
-
<groupId>io.github.openfeign</groupId>
-
<artifactId>feign-httpclient</artifactId>
-
<version>9.5.1</version>
-
</dependency>
或者
-
<dependency>
-
<groupId>io.github.openfeign</groupId>
-
<artifactId>feign-okhttp</artifactId>
-
<version>9.5.1</version>
-
</dependency>
总结
目前绝大部分的介绍 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 来进行维护了。
参考资料:
作者:vincent_ren
链接:https://www.jianshu.com/p/7cfa4250d5ab
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
FeignClient调用POST请求时查询参数被丢失的情况分析与处理的更多相关文章
- 为什么返回的数据前面有callback? ashx/json.ashx?的后面加 callback=? 起什么作用 js url?callback=xxx xxx的介绍 ajax 跨域请求时url参数添加callback=?会实现跨域问题
为什么返回的数据前面有callback? 这是一个同学出现的问题,问到了我. 应该是这样的: 但问题是这样的: 我看了所请求的格式和后台要求的也是相同的.而且我也是这种做法,为什么他的就不行呢? ...
- 网络请求 get 请求时, 如果参数中的字符带有+号
网络请求 get 请求时, 如果参数中的字符带有+号, 今天前端在调用我的API时, 发现有个参数一直没法通过我后台的验证, 但是在前端查看时, 该参数结构又没有什么异常, 又是一番查找, 直到在后端 ...
- feignclient发送get请求,传递参数为对象
feignclient发送get请求,传递参数为对象.此时不能使用在地址栏传递参数的方式,需要将参数放到请求体中. 第一步: 修改application.yml中配置feign发送请求使用apache ...
- 解决爬虫浏览器中General显示 Status Code:304 NOT MODIFIED,而在requests请求时出现403被拦截的情况。
在此,非常感谢 “完美风暴4” 的无私共享经验的精神 在Python爬虫爬取网站时,莫名遇到 浏览器中General显示 Status Code: 304 NOT MODIFIED 而在req ...
- url请求时,参数中的+在服务器接收时为空格,导致AES加密报出javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
报错的意思的是使用该种解密方式出入长度应为16bit的倍数,但实际的错误却不是这个,错误原因根本上是因为在http请求是特殊字符编码错误,具体就是base64生成的+号,服务器接收时成了空格,然后导致 ...
- spring boot:用cookie保存i18n信息避免每次请求时传递参数(spring boot 2.3.3)
一,用cookie保存i18n信息的优点? 当开发一个web项目(非api站)时,如果把i18n的选择信息保存到cookie, 则不需要在每次发送请求时都传递所选择语言的参数, 也不需要增加heade ...
- ajax 跨域请求时url参数添加callback=?会实现跨域问题
例如: 1.在 jQuery 中,可以通过使用JSONP 形式的回调函数来加载其他网域的JSON数据,如 "myurl?callback=?".jQuery 将自动替换 ? 为正确 ...
- ASP.NET MVC API与JS进行POST请求时传递参数 -CHPowerljp原创
在API前添加 [HttpPost] 表示只允许POST方式请求 [HttpPost] public IHttpActionResult Get_BIGDATA([FromBody]Datas ...
- angularjs中ajax请求时传递参数的方法
method1方法使用的是params参数,该用法会把参数直接附加到url中 method2方法使用的是data参数,该参数会把页面参数类型从默认的multipart/form-data改为appli ...
随机推荐
- Matplotlib学习---用matplotlib画折线图(line chart)
这里利用Jake Vanderplas所著的<Python数据科学手册>一书中的数据,学习画图. 数据地址:https://raw.githubusercontent.com/jakevd ...
- Codeforces Round #541 (Div. 2) D(并查集+拓扑排序) F (并查集)
D. Gourmet choice 链接:http://codeforces.com/contest/1131/problem/D 思路: = 的情况我们用并查集把他们扔到一个集合,然后根据 > ...
- git 提交报错 : The file will have its original line endings in your working directory.
报错现象 git add . 的时候发现此报错 报错分析 看情况应该是不同系统对换行的识别不到位导致的 就常识来说文件是在windows下生成的所以换行和 linux 确实不同可能是因为这个导致的 ...
- 微信小程序原生开发简介
简介: 总结: 1. 逻辑层使用js引擎,视图层使用webview渲染 2. 微信小程序已经支持了绝大部分的 ES6 API 3. 可以自动补全css的兼容语法 文档:https://develope ...
- Hdoj 2602.Bone Collector 题解
Problem Description Many years ago , in Teddy's hometown there was a man who was called "Bone C ...
- Hdoj 2044.一只小蜜蜂... 题解
Problem Description 有一只经过训练的蜜蜂只能爬向右侧相邻的蜂房,不能反向爬行.请编程计算蜜蜂从蜂房a爬到蜂房b的可能路线数. 其中,蜂房的结构如下所示. Input 输入数据的第一 ...
- ftp利用脚本添加本地用户
指定用户名,家目录,密码,顺序不可颠倒.eg: sh 脚本名 用户名 家目录 密码 #!/bin/bash # set -e ] //判断给定参数是否为三个 homepath=$ password=$ ...
- rt-thread之stm32系列BSP制作方法
@2019-01-24 [小记] bsp制作方法: 官网下载 rt-thread 源码,将路径 bsp/stm32/libraries/templates/ 下的模板文件,Copy至路径 bsp/st ...
- [WC2014]紫荆花之恋(动态点分治+替罪羊思想)
题目描述 强强和萌萌是一对好朋友.有一天他们在外面闲逛,突然看到前方有一棵紫荆树.这已经是紫荆花飞舞的季节了,无数的花瓣以肉眼可见的速度从紫荆树上长了出来.仔细看看的话,这个大树实际上是一个带权树.每 ...
- hdu 2859 Phalanx (最大对称子矩阵)
Problem Description Today is army day, but the servicemen are busy with the phalanx for the celebrat ...