使用 feign 調用服務時,Post 變 Get 請求的解決方案

1. 問題

使用的是 2.1.1 版本的 feign,進過大量的測試,無論是標準是 @PostMapping 還是 @GetMapping,只要參數標註 @RequestParam,調用的時候就一律都用 Get 請求,也就是說把參數拼接到 URL 上。如果想使用Post 請求,需要在參數標記 @RequestBody,這樣無論是 Get 還是 Post 都一律使用 Post

以下有幾種的實現方式可以做到配置區分 PostGet 請求。下列方案可能有缺陷,請慎用。

2. 解決辦法

2.1 增加 feign 過濾器

增加的 feign 攔截器,此攔截器是在 RequestTemplate 構建完成後執行的,我們手動再進行加工一下。把Post 相關的參數寫入到 Body 裏面,從而達到目的。

1、增加feign的攔截器 FeignRequestInterceptor

import feign.*;
import feign.template.QueryTemplate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;extHolder;
import org.springframework.web.context.request.ServletRequ
import org.springframework.web.context.request.RequestContestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;

@Slf4j
@Component
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {

        /處理POST請求的時候轉換成了GET的bug
        if (HttpMethod.POST.name().equals(template.method()) && template.requestBody().length() == 0 && !template.queries().isEmpty()) {
            Object object = MapUtils.getObject(template.headers(), HttpHeaders.CONTENT_TYPE);
            if (null != object) {

                if (((Collection) object).contains(APPLICATION_FORM_URLENCODED_VALUE)) {

                    StringBuilder builder = new StringBuilder();
                    Map<String, Collection<String>> queries = template.queries();
                    Iterator<String> queriesIterator = queries.keySet().iterator();

                    while (queriesIterator.hasNext()) {
                        String field = queriesIterator.next();
                        Collection<String> strings = queries.get(field);
                        //由於參數已經做了url編碼處理,這裏直接拼接即可
                        builder.append(field + "=" + StringUtils.join(strings, ","));
                        builder.append("&");
                    }

                    template.body(Request.Body.encoded(builder.toString().getBytes(), template.requestCharset()));
                    template.queries(null);
                }
            }
        }
        log.debug("FeignRequestInterceptor:{}", template.toString());
    }
}

2、feign 接口定義

// consumes = "application/x-www-form-urlencoded" 是必須設置的,否則不會進入上面寫的處理過程
@PostMapping(value = "/youUrl", consumes = "application/x-www-form-urlencoded")
ResultBody<Map<String,Object>> youMethod(@RequestParam("a") String a, @RequestParam("b") String b);

2.2 使用 httpClient 代替默認實現

使用 httpClient 的實現,雖然 httpClient 裏面的處理是會把URL裏面的參數挪動到Body裏面,但是由於ApacheHttpClient 的實現上和後面的處理有衝突。詳細分析請看另外一篇分析 《Spring boot 使用 feign 調用參數過長(Post變Get)》,但是需要修改 feign 的源碼,這裏直接採用在項目覆蓋默認的實現。從而達到目的。

1、增加 maven 依賴:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.11</version>
</dependency>

<!--<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>10.8</version>
</dependency>-->

2、在yml文件中增加配置

feign:
  httpclient:
    enabled: true

這個是 FeignAutoConfiguration 自動配置

在根目錄增加 feign.httpclient package,把 feign-httpclient 中的 ApacheHttpClient 直接複製到此目錄,然後註釋掉此句話即可。

w500

代碼詳細實例可以查看 https://github.com/JerryDai90/sping-boot-experiment/blob/master/feign/src/main/java/fun/lsof/feign/fix/urlencode/FeignRequestInterceptor.java

3. 思考

這幾天看了比較多的源碼,feign 相關源碼不應該犯這種錯誤,把 POST 參數放到 URL 上。這個原因我的猜想是由於無論是放到 body 還是 header 上面,其實效果都是一樣的(放到 url和在body編碼方式和組合都是一樣的),header 的默認大小限制就小一點(8K或者更少),而 Post 則大一點(2M+)。換句話來說,其實可以忽略這個問題,直接在配置文件中加大限制即可

server:
    port: 4450
    # 增加請求頭接受大小
    max-http-header-size: 10485760
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章