解决FeignClient发送post请求异常的问题
作者:波子汽水yeah 发布时间:2022-08-08 15:55:58
FeignClient发送post请求异常
这个问题其实很基础。但是却难倒了我。记录一下
在发送post请求的时候要指定消息格式
正确的写法是这样
@PostMapping(value = "/test/post", consumes = "application/json")
String test(@RequestBody String name);
不生效的写法
@PostMapping(value = "/test/post", produces= "application/json")
关于这个区别
produces
:它的作用是指定返回值类型,不但可以设置返回值类型还可以设定返回值的字符编码;
consumes
:指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
基础真的很重要啊~
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>
来源:https://blog.csdn.net/maobois/article/details/109903809


猜你喜欢
- 前面写过一篇关于下拉刷新控件的文章下拉刷新控件终结者:PullToRefresh
- @Value("${xxx}")取properties时中文乱码(1)检查spring的配置文件中,properties
- 1.sax方式 /** * 使用sax解析 */ public class SaxParse{ /** * sax解析器 */ privat
- 这篇文章主要介绍了java通过Jsoup爬取网页过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- 要讲到C#源码的执行过程 首先要提下程序集,因为Clr并不是和托管摸块打交道的,而是和程序集(dll,exe)1、从哪里来程序集是由一个或者
- 同步代码块基本语句synchronized (任意对象) {操作共享代码}代码示例public class SellTicket imple
- 本文实例分析了C#反射内存的处理。分享给大家供大家参考。具体分析如下:这段时间由于公司的项目的要求,我利用c#的反射的机制做了一个客户端框架
- 一、C#正则表达式符号模式字符描述\转义字符,将一个具有特殊功能的字符转义为一个普通字符,或反过来^匹配输入字符串的开始位置$匹配输入字符串
- 本文实例讲述了Java Socket实现传输压缩对象的方法。分享给大家供大家参考,具体如下:前面文章《Java Socket实现的传输对象功
- delphi dll 源码:library dllres; type char1
- JavaWeb 使用DBUtils实现增删改查1、创建C3p0Utils类创建cn.itcast.jdbc.utils包代码如下:packa
- 在java的开发中,java开发人员建议,尽量少用内部类,要把内部类提出他所处的那个类,单独生成一个类。直接来代码:package com.
- 1.关于JSR-303JSR-303规范(Bean Validation规范)提供了对 Java EE 和 Java SE 中的 Java
- 本文实例讲述了WPF设置窗体可以使用鼠标拖动大小的方法。分享给大家供大家参考。具体实现方法如下:private void Window_Lo
- 安卓的三种本地的典型数据存储方式SharedPreferences以文件格式保存在本地存储中SQL数据库IDE : Android Stud
- 前言:页面静态化其实就是将原来的 * 页(例如通过ajax请求动态获取数据库中的数据并展示的网页)改为通过静态化技术生成的静态网页,这样用户
- 随着互联网公司的微服务越来越多,分布式事务已经成为了我们的经常使用的。所以我们来一步一步的实现基于RocketMQ的分布式事务。接下来,我们
- 最近研究了下Java socket通信基础,利用代码实现了一个简单的多人聊天室功能,现把代码共享下,希望能帮到有兴趣了解的人。目录结构:Ch
- 本文实例讲述了Android编程实现activity dialog透明背景的方法。分享给大家供大家参考,具体如下:首先查一下window&n
- 数据传输在Android开发过程中,我们常常通过Intent在各个组件之间传递数据。例如在使用startActivity(android.c