- 今天接到新需求 需要在内部调用的服务(也就是使用feign进行调用的时候) 将请求头的内容一并转发 以供下游服务使用
- 先上代码
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@Configuration
public class FeignInterceptorConfiguration implements RequestInterceptor {
public static final String CONTENT_LENGTH = "content-length";
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) return;
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames == null) return;
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
if (CONTENT_LENGTH.equalsIgnoreCase(name)) continue;
String values = request.getHeader(name);
template.header(name, values);
}
}
}
- 原理就是实现Feign的请求拦截器 在请求时从当前线程获取请求头并写入
- 很简单 将该类放在调用方 就可以实现功能了
- 接下来说一下踩的坑 也就是 标题的 java.io.IOException: Incomplete output stream
- 首先 吐槽一下 用这个错误搜索的全是不能用的东西 一模一样的让我替换类 一篇文章抄来抄去
- 这个错误导致的原因 就是代码中标记注释的那行
- 最开始是没有这行代码的
// 注意!注意!注意!坑之所在 此处建议只添加自己想要下发给下游的请求头
if (CONTENT_LENGTH.equalsIgnoreCase(name)) continue;
- 通过断点调试 发现 将请求头中的
content-length
转发下去后 会导致下游接收到数据后 InputStream
不能正确处理 - 像我这次请求 就是因为我的
content-length
长于真正的content-length
也就是明明没有了 还要读 当然 短了也会有读不出来的问题 - 结论 强烈建议只处理想要转发的请求头 而不要全部转发!!!
- 可以参考我的实现方案 将需要转发的请求头 写入配置中心(也可以是配置文件、缓存等等)中
- 下面贴个大概意思 供兄弟们参考
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@Configuration
public class FeignInterceptorConfiguration implements RequestInterceptor {
private final PassHeaderProperties passHeaderProperties;
public FeignInterceptorConfiguration(PassHeaderProperties passHeaderProperties) {
this.passHeaderProperties = passHeaderProperties;
}
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) return;
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames == null) return;
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
if (!passHeaderProperties.getHeaders().contains(name)) continue;
String values = request.getHeader(name);
template.header(name, values);
}
}
}
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import java.util.HashSet;
import java.util.Set;
@Configuration(proxyBeanMethods = false)
@RefreshScope
@ConfigurationProperties(prefix = "pass")
public class PassHeaderProperties {
private Set<String> headers = new HashSet<>();
public Set<String> getHeaders() {
return headers;
}
public void setHeaders(Set<String> headers) {
this.headers = headers;
}
}
pass:
headers:
- bbb
- aaa
- xxx
- ...