解决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;
}

问题分析

背景

  1. 使用 FeignClient 客户端

  2. 使用 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;
   }

解决方案

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

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

  2. 使用 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

标签:FeignClient,发送,post请求
0
投稿

猜你喜欢

  • Android 高仿微信转账金钱输入框规则

    2022-03-06 19:54:20
  • 详谈异步log4j2中的location信息打印问题

    2023-12-22 05:53:31
  • Java基础之创建虚拟机对象的过程详细总结

    2022-07-07 14:24:29
  • 关于Scanner对象的输入结束标记问题

    2022-02-20 08:02:11
  • Apache Commons fileUpload实现文件上传之一

    2022-12-06 12:36:48
  • Android编程应用风格和主题详解

    2022-12-31 08:26:51
  • Spring中自定义数据类型转换的方法详解

    2022-10-09 02:56:51
  • 浅谈java封装

    2023-05-05 22:48:20
  • 将Java的List结构通过GSON库转换为JSON的方法示例

    2023-02-13 20:33:52
  • textView 添加超链接(两种实现方式)

    2021-08-12 12:58:27
  • 用JAVA实现单链表,检测字符串是否是回文串

    2021-07-20 07:07:45
  • MyBatis控制台显示SQL语句的方法实现

    2021-10-18 04:10:00
  • idea输入sout无法自动补全System.out.println()的问题

    2023-11-28 21:34:03
  • 零基础学Java:Java开发工具 Eclipse 安装过程创建第一个Java项目及Eclipse的一些基础使用技巧

    2022-09-05 03:46:03
  • Android中的全局变量与局部变量使用小结

    2023-12-07 10:11:49
  • 微信公众平台(测试接口)准备工作

    2023-05-24 15:51:06
  • C#值类型和引用类型的深入理解

    2021-07-13 07:45:11
  • C#精确计算年龄的方法分析

    2021-06-13 14:06:36
  • Java中两个字符串进行大小比较的方法

    2023-10-12 13:39:26
  • IDEA 端口占用的解决方法(推荐)

    2023-09-05 06:05:15
  • asp之家 软件编程 m.aspxhome.com