本文踩坑有二:
- not annotated with HTTP method type (ex. GET, POST)
- FeignException$BadRequest: status 400
1.not annotated with HTTP method
報錯內容如下:
java.lang.IllegalStateException:
Method XXX not annotated with HTTP method type (ex. GET, POST)
Feign可以使用自帶註解@RequestLine以及spring的註解@RequestMapping、@GetMapping等
幾個需要注意的點:
- 使用spring的註解@RequestMapping需要指定method屬性以及value(路徑信息)
- 看了很多其他博客說不可以使用@GetMapping等這類註解,本人親測,可以使用
- 第三點比較重要的:
如果使用Feign自帶註解@RequestLine,需要修改默認配置。spring cloud netflix默認爲feign提供的默認bean如下:
Decoder feignDecoder:ResponseEntityDecoder(其中包含SpringDecoder)
Encoder feignEncoder:SpringEncoder
Logger feignLogger:Slf4jLogger
Contract feignContract:SpringMvcContract
Feign.Builder feignBuilder:HystrixFeign.Builder
Client feignClient:如果Ribbon啓用,則爲LoadBalancerFeignClient,否則將使用默認的feign客戶端。
看上方的Contract feignContract:SpringMvcContract,說明feign默認使用springmvc的協議或者約定,使用@RequestMapping的註解,若需要使用@RequestLine,需要修改約束配置,如下
@Configuration
public class Configuration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
}
在feignclient註解裏配置
@FeignClient(value = "client", url = "${XXX}", configuration = Configuration .class)
2.status 400
報錯內容如下:
FeignException$BadRequest: status 400 reading xxService#xxmethod(String, Interger)
看錯誤碼400,http錯誤碼400主要有兩種形式:
1、bad request 意思是 “錯誤的請求”;
2、invalid hostname 意思是 “不存在的域名”。
400 Bad Request 是由於明顯的客戶端錯誤(例如,格式錯誤的請求語法,太大的大小,無效的請求消息或欺騙性路由請求),服務器不能或不會處理該請求。
原因
查找其他博客得到結果有一下幾類:
- 傳遞的參數爲空
- 參數長度過長
- header過長
- RequestMapping沒有指定method屬性
- 本人遇到的情況,header參數錯誤(下面講解錯誤原因)
解決
對於第一種傳參爲空,可以指定參數require=false,如下
@RequestParam(value = "param",required = false) String param
還要注意的是如果使用rest風格的請求,路徑有佔位符,一定要使用@PathVariable註解,這也是很多人踩過的坑。
對於第二種問題,是由於springboot內嵌tomcat對參數長度限制是8K,可以修改配置進行解決
server.max-http-header-size=20480
第三種情況與第二種情況一致。
第四種情況直接指定方法就好。
本人踩坑復現
本人遇到的問題是header設置的問題。由於需要使用token進行驗證,而且不同的請求對應不同的token,導致無法使用靜態header進行指定,於是使用修改配置的方式進行,直接修改restTemplate.
@Configuration
public class AuthorityConfig implements RequestInterceptor{
@Override
public void apply(RequestTemplate template) {
SecurityContext securityCxt = SecurityContextHolder.getContext();
XXX token = XXX securityCxt.getAuthentication();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//添加token
template.header(Constants.TOKEN, request.getHeader(token.getToken()));
//template.headers().remove("Content-Type");
// Map<String, Collection<String>> headers = new HashMap<>();
// ArrayList<String> headerToken = new ArrayList<>();
// headerToken.add(request.getHeader(token.getToken()));
// headers.put(Constants.TOKEN, headerToken);
// headers.put("Content-Type", new ArrayList(){{add("application/x-www-form-urlencoded");}});
template.headers(new HashMap<>());
template.header("Content-Type", "application/x-www-form-urlencoded");
template.header(Constants.TOKEN_TEACHER, token.getToken());
System.out.println("headers is : "+template.headers());
}
}
上方代碼看倒數第四行,我設置了一個空的map,之後才進行的其他header的添加。這是踩坑之後才發現得這麼做的。
看我註釋掉的代碼發現,template.header()需要一個string和一個集合,也就是說,template會把key相同的屬性放到一個集合裏。這似乎也很正常,坑的是它本身有一些默認的header,如果你添加了同名的header後只是加入到了一個集合而不是覆蓋,導致我註釋的代碼添加Content-Type後,該key對應的header是
[application/json,application/x-www-form-urlencoded]
這就很尷尬,服務器因爲這個拒絕了我。查看其源碼實現
public RequestTemplate headers(Map<String, Collection<String>> headers) {
if (headers != null && !headers.isEmpty()) {
headers.forEach(this::header);
} else {
this.headers.clear();
}
return this;
}
發現傳入null或者空可以實現clear(),所以我傳了空先進行清除後再逐個添加,這樣解決了400的問題。
推薦閱讀:feign實戰中header的巧妙處理
以上是這兩天的踩坑集錦,有誤之處請指正,不勝感激~