亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

解決FeignClient發(fā)送post請求異常的問題

 更新時間:2021年07月08日 15:26:41   作者:波子汽水yeah  
這篇文章主要介紹了FeignClient發(fā)送post請求異常的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

FeignClient發(fā)送post請求異常

這個問題其實很基礎。但是卻難倒了我。記錄一下

在發(fā)送post請求的時候要指定消息格式

正確的寫法是這樣

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

不生效的寫法

@PostMapping(value = "/test/post", produces= "application/json")

關于這個區(qū)別

produces:它的作用是指定返回值類型,不但可以設置返回值類型還可以設定返回值的字符編碼;

consumes:指定處理請求的提交內容類型(Content-Type),例如application/json, text/html;

基礎真的很重要啊~

FeignClient調用POST請求時查詢參數被丟失的情況分析與處理

本文沒有詳細介紹 FeignClient 的知識點,網上有很多優(yōu)秀的文章介紹了 FeignCient 的知識點,在這里本人就不重復了,只是專注在這個問題點上。

查詢參數丟失場景

業(yè)務描述: 業(yè)務系統需要更新用戶系統中的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 的源碼閱讀,發(fā)現問題不是出在參數解析上,而是在使用 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;
    }

解決方案

既然已經知道原因了,那么解決方法就有很多種了,下面就介紹常規(guī)的解決方案:

  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>

總結

目前絕大部分的介紹 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為空時就會發(fā)生丟失查詢參數的問題。

這里推薦大家使用 feign-httpclient 或者是 feign-okhttp的時候不要依賴 com.netflix.feign,而應該選擇 io.github.openfeign,因為看起來 Netflix 很久沒有對這兩個組件進行維護了,而是由 OpenFeign 來進行維護了。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • Java實現的對稱加密算法3DES定義與用法示例

    Java實現的對稱加密算法3DES定義與用法示例

    這篇文章主要介紹了Java實現的對稱加密算法3DES定義與用法,結合實例形式簡單分析了Java 3DES加密算法的相關定義與使用技巧,需要的朋友可以參考下
    2018-04-04
  • Spring事件監(jiān)聽機制使用和原理示例講解

    Spring事件監(jiān)聽機制使用和原理示例講解

    Spring事件監(jiān)聽機制是一個很不錯的功能,我們在進行業(yè)務開發(fā)的時候可以引入,在相關的開源框架中也是用它的身影,比如高性能網關ShenYu中就使用了Spring事件監(jiān)聽機制來發(fā)布網關的更新數據,它可以降低系統的耦合性,使系統的擴展性更好
    2023-06-06
  • Java8的Lambda遍歷兩個List匹配數據方式

    Java8的Lambda遍歷兩個List匹配數據方式

    這篇文章主要介紹了Java8的Lambda遍歷兩個List匹配數據方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • spring boot微服務自定義starter原理詳解

    spring boot微服務自定義starter原理詳解

    這篇文章主要介紹了spring boot微服務自定義starter原理詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-12-12
  • 模擬Mybatis的實現方法

    模擬Mybatis的實現方法

    本文通過實例代碼給大家分享了模擬Mybatis的實現方法,需要的朋友參考下吧
    2017-09-09
  • IntelliJ IDEA中折疊所有Java代碼,再也不怕大段的代碼了

    IntelliJ IDEA中折疊所有Java代碼,再也不怕大段的代碼了

    今天小編就為大家分享一篇關于IntelliJ IDEA中折疊所有Java代碼,再也不怕大段的代碼了,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • VSCode新手教程之配置Java環(huán)境的詳細教程

    VSCode新手教程之配置Java環(huán)境的詳細教程

    這篇文章主要給大家介紹了關于VSCode新手教程之配置Java環(huán)境的詳細教程,工欲善其事必先利其器,想要工作順利我們先搭建好JAVA的開發(fā)環(huán)境,需要的朋友可以參考下
    2023-10-10
  • 詳解使用Spring快速創(chuàng)建web應用的兩種方式

    詳解使用Spring快速創(chuàng)建web應用的兩種方式

    這篇文章主要介紹了詳解使用Spring快速創(chuàng)建web應用的兩種方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-11-11
  • Spring MVC攔截器和跨域請求使用詳解

    Spring MVC攔截器和跨域請求使用詳解

    SpringMVC的攔截器也是AOP思想的一種實現方式,主要用于攔截用戶的請求并做相應的處理,通常應用在權限驗證、記錄請求信息的日志、判斷用戶是否登錄等功能上,這篇文章主要介紹了Spring MVC攔截器和跨域請求,需要的朋友可以參考下
    2023-07-07
  • SpringBoot實現Excel文件批量上傳導入數據庫

    SpringBoot實現Excel文件批量上傳導入數據庫

    這篇文章主要為大家詳細介紹了SpringBoot實現Excel文件批量上傳導入數據庫,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-11-11

最新評論