一. 前言
在开发SpringCloud项目架构的项目时,服务与服务之间的调用通过Feign客户端实现,在做一个从Feign客户端到service服务端的POST请求时,Feign客户端为服务消费者觉得,service服务端为服务提供者角色,提供者角色中service服务端Controller类中方法测试正常,但却在Feign客户端返回时报错,大致异常如下:
Caused by: java.io.IOException: Incomplete output stream
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1523)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
at feign.Client$Default.convertResponse(Client.java:152)
at feign.Client$Default.execute(Client.java:74)
.........................................
二. 分析异常
关键异常: IOException: Incomplete output stream
, 从表面意思看,是IO异常,异常信息也是从Feign客户端源码报出来的,这样一步一步锁定异常范围,根据异常调用链得知最终异常出现的地方是:sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1523)
HttpURLConnection.getInputStream0方法1523行。经过搜索资料,原来feign采用jdk原生HttpURLConnection向下游服务发起http请求(源码详见feign.Client.Default),先到getInputStream0方法看看:
private synchronized InputStream getInputStream0() throws IOException {
if(!this.doInput) {
throw new ProtocolException("Cannot read from URLConnection if doInput=false (call setDoInput(true))");
} else if(this.rememberedException != null) {
if(this.rememberedException instanceof RuntimeException) {
throw new RuntimeException(this.rememberedException);
} else {
throw this.getChainedException((IOException)this.rememberedException);
}
} else if(this.inputStream != null) {
return this.inputStream;
} else {
if(this.streaming()) {
if(this.strOutputStream == null) {
this.getOutputStream();
}
this.strOutputStream.close();
if(!this.strOutputStream.writtenOK()) {
throw new IOException("Incomplete output stream"); // 1523行
}
}..........
// this.strOutputStream.writtenOK()方法
boolean writtenOK() {
return this.closed && !this.error;
}
看起来,this.error是true导致的,也就是说在OutputStream写入流时出错。
三. 解决方案
1. 加feign-httpclient依赖
没办法,只能用Apache HttpClient替换掉原生HttpURLConnection。加入依赖:
<!-- 使用Apache HttpClient替换Feign原生httpclient -->
<!-- feign-httpclient内含Apache HttpClient -->
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>8.17.0</version>
</dependency>
2. 开启Feign支持httpclient
然后在application.yml中添加如下:
feign:
httpclient:
enabled: true
再次重新调用,一切恢复正常。
3. 补充
引入Apache HttpClient包时,需要注意指定Content-Type,不然会产生以下异常:
Caused by: java.lang.IllegalArgumentException: MIME type may not contain reserved characters
解决办法 :
@RequestMapping(value = "/getUser", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE)
注解中指定: Content-Type 即 指定 consumes 的属性值
4. 其他异常情况
如果你又遇到了下面这个异常,可参考我对网友提出的问题回复的帖子,按照对应的依赖版本匹配上即可解决问题:openfeign里面配置httpclient出错。
异常日志如下:
Caused by: java.lang.NoSuchMethodError: feign.Response.create(ILjava/lang/String;Ljava/util/Map;Lfeign/Response$Body;)Lfeign/Response;
Caused by: java.lang.NoSuchMethodError: feign.Response.create(ILjava/lang/String;Ljava/util/Map;Lfeign/Response$Body;)Lfeign/Response;
网友的提问点醒了我:
然后我的解决方式:
四. 参考
参考: https://www.jianshu.com/p/6397e2cd1fae