gpt4 book ai didi

解决FeignClient发送post请求异常的问题

转载 作者:qq735679552 更新时间:2022-09-29 22:32:09 30 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章解决FeignClient发送post请求异常的问题由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

FeignClient发送post请求异常

这个问题其实很基础。但是却难倒了我。记录一下 。

在发送post请求的时候要指定消息格式 。

正确的写法是这样

?
1
2
@PostMapping (value = "/test/post" , consumes = "application/json" )
  String test( @RequestBody String name);

不生效的写法

?
1
@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的值 。

?
1
2
3
4
5
6
@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 来进行实际请求的调用
?
1
2
3
4
5
< dependency >
     < groupId >com.netflix.feign</ groupId >
     < artifactId >feign-httpclient</ artifactId >
     < version >8.18.0</ version >
</ dependency >

直入源码

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@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方法 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 依赖,截取部分源码说明原因如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
   }

推荐的依赖 。

?
1
2
3
4
5
< dependency >
     < groupId >io.github.openfeign</ groupId >
     < artifactId >feign-httpclient</ artifactId >
     < version >9.5.1</ version >
</ dependency >

或者 。

?
1
2
3
4
5
< 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 来进行维护了.

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我.

原文链接:https://blog.csdn.net/maobois/article/details/109903809 。

最后此篇关于解决FeignClient发送post请求异常的问题的文章就讲到这里了,如果你想了解更多关于解决FeignClient发送post请求异常的问题的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com