feign调用错误 java.io.IOException Incomplete output stream

参考文献Feign请求头header转发下游和java.io.IOException: Incomplete output stream踩坑

问题描述

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
2022-06-23 18:37:57

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:750)
Caused by: feign.RetryableException: Incomplete output stream executing POST http://pumpkin-stem-tenant/api/v1/ucenter/user/socialUser/list
at feign.FeignException.errorExecuting(FeignException.java:268)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:129)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
at com.sun.proxy.$Proxy113.getSocialUserList(Unknown Source)
at com.amoros.message.acl.stem.service.SysUserApiService.qywxOpenUidList(SysUserApiService.java:42)
... 65 common frames omitted
Caused by: java.io.IOException: Incomplete output stream
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1531)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1500)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
at feign.Client$Default.convertResponse(Client.java:109)
at feign.Client$Default.execute(Client.java:105)
at org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(LoadBalancerUtils.java:56)
at org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLoadBalancerClient.lambda$execute$2(RetryableFeignBlockingLoadBalancerClient.java:166)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:329)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:225)
at org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLoadBalancerClient.execute(RetryableFeignBlockingLoadBalancerClient.java:113)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:119)
... 69 common frames omitted

解决方案

代码,注释了方法 headerCopy 解决的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 原理就是实现Feign的请求拦截器 在请求时从当前线程获取请求头并写入
很简单 将该类放在调用方 就可以实现功能了
接下来说一下踩的坑 也就是 标题的 java.io.IOException: Incomplete output stream
首先 吐槽一下 用这个错误搜索的全是不能用的东西 一模一样的让我替换类 一篇文章抄来抄去
这个错误导致的原因 就是代码中 headerCopy(template);

# 注意!注意!注意!坑之所在 此处建议只添加自己想要下发给下游的请求头

if (CONTENT_LENGTH.equalsIgnoreCase(name)) continue;
通过断点调试 发现 将请求头中的content-length转发下去后 会导致下游接收到数据后 InputStream 不能正确处理
像我这次请求 就是因为我的content-length长于真正的content-length 也就是明明没有了 还要读 当然 短了也会有读不出来的问题

# 结论 强烈建议只处理想要转发的请求头 而不要全部转发!!!
我的方案是 header 在 Servlet Filter放入了上下文,我这里只是从上下文中取就可以了。
或者你只取指定的 header,不要复制全部。
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package com.amoros.message.config;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.amoros.message.context.TenantIdHolder;
import com.amoros.message.context.UserIdHolder;
import com.amoros.message.util.MDCUtil;
import datart.api.config.DatartProperties;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

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

/**
* <pre>
* web -> me -> feign
* 把别人调用我的 header 放入 我调用别人的 feign 请求中
* </pre>
*/
@Slf4j
@Component
public class MessageFeignRequestInterceptor implements RequestInterceptor {

/**
* 租户ID
*/
public static final String TENANT_ID = "X-Change-Data-Source";

/**
* 用户ID
*/
public static final String USER_ID = "X-User-Id";

@Override
public void apply(RequestTemplate template) {
// datart 服务,直接跳过
String serviceName = template.feignTarget().name();
if (DatartProperties.SERVICE_NAME.equals(serviceName)) {
return;
}


try {
// 0、traceId
MDCUtil.set(template);

// 1、mvc.header => feign.header
// headerCopy(template);

// 2、Holder.get => headers.put
String tenantId = TenantIdHolder.get();
if (StrUtil.isNotBlank(tenantId)) {
log.info("feign 拦截:TenantIdHolder 设置租户id=[{}]", tenantId);
template.removeHeader(TENANT_ID);
template.header(TENANT_ID, tenantId);
template.header(USER_ID, UserIdHolder.get());
return;
}

// 3、TENANT_ID 未设置 => throw
Assert.notEmpty(template.headers().get(TENANT_ID), "请设置租户id");
} catch (Exception e) {
throw new RuntimeException("设置feign请求头信息异常", e);
}
}

/**
* 当前上下文中的请求header 放入 feign请求header
*/
private void headerCopy(RequestTemplate template) {
// 1、Job => return
// if (JobContext.getJobExecutionContext() != null) {
// return;
// }

// 2、Controller => mvc.headers => feign.headers
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Enumeration headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = (String) headerNames.nextElement();
String values = request.getHeader(name);
template.header(name, values);
}
}
}
}