大家好,我是一安~
导读:在上一篇文章中我们讲解了如何利用Spring Security OAuth2
实现微服务统一认证,今天继续讲解Feign
远程调用和异步调用请求头丢失问题。
简介
之前演示只是测试了调用用户微服务下查询接口,并没有在用户服务再调用其他微服务接口,调测看着一切都很正常,但今天测试了用户微服务调用商品微服务,出现了异常:Debug跟踪,发现用户微服务正常接收到token,而远程调用商品微服务token消失不见了:
原因

源码:ReflectiveFeign.class
:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!"equals".equals(method.getName())) {
if ("hashCode".equals(method.getName())) {
return this.hashCode();
} else {
return "toString".equals(method.getName()) ? this.toString() : ((InvocationHandlerFactory.MethodHandler)this.dispatch.get(method)).invoke(args);
}
} else {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return this.equals(otherHandler);
} catch (IllegalArgumentException var5) {
return false;
}
}
}
进入((InvocationHandlerFactory.MethodHandler)this.dispatch.get(method)).invoke(args)
:
public Object invoke(Object[] argv) throws Throwable {
//就是在这 构建了一个新的RequestTemplate ,而浏览器带给我们的请求头都会丢失
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
Request.Options options = this.findOptions(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
// 在这即将执行该方法
return this.executeAndDecode(template, options);
} catch (RetryableException var9) {
RetryableException e = var9;
try {
retryer.continueOrPropagate(e);
} catch (RetryableException var8) {
Throwable cause = var8.getCause();
if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
throw cause;
}
throw var8;
}
if (this.logLevel != Level.NONE) {
this.logger.logRetry(this.metadata.configKey(), this.logLevel);
}
}
}
}
进入this.executeAndDecode(template, options)
:
Object executeAndDecode(RequestTemplate template, Request.Options options) throws Throwable {
//这里 它会对我们的请求进行一些包装
Request request = this.targetRequest(template);
if (this.logLevel != Level.NONE) {
this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
}
long start = System.nanoTime();
Response response;
try {
response = this.client.execute(request, options);
response = response.toBuilder().request(request).requestTemplate(template).build();
} catch (IOException var12) {
if (this.logLevel != Level.NONE) {
this.logger.logIOException(this.metadata.configKey(), this.logLevel, var12, this.elapsedTime(start));
}
throw FeignException.errorExecuting(request, var12);
}
.......
}
进入this.targetRequest(template)
:
Request targetRequest(RequestTemplate template) {
//拿到对应的所有请求拦截器的迭代器
Iterator var2 = this.requestInterceptors.iterator();
//遍历所有的请求拦截器
while(var2.hasNext()) {
RequestInterceptor interceptor = (RequestInterceptor)var2.next();
//这里是每个请求拦截器 依次对该方法进行包装
interceptor.apply(template);
}
return this.target.apply(template);
}
进入interceptor.apply(template)
:
public interface RequestInterceptor {
void apply(RequestTemplate var1);
}
发现它是一个接口,所以可以重写一下这个方法对我们的请求做一些包装,借鉴一下别的实现方法:
方案
/**
* 微服务之间feign调用请求头丢失的问题
* @author yian
* @since 2023-04-05
*/
@Configuration
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
HttpServletRequest httpServletRequest = getHttpServletRequest();
if(httpServletRequest!=null){
Map<String, String> headers = getHeaders(httpServletRequest);
// 传递所有请求头,防止部分丢失
//此处也可以只传递认证的header
//requestTemplate.header("json-token", request.getHeader("json-token"));
for (Map.Entry<String, String> entry : headers.entrySet()) {
template.header(entry.getKey(), entry.getValue());
}
log.debug("FeignRequestInterceptor:{}", template.toString());
}
}
private HttpServletRequest getHttpServletRequest() {
try {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
} catch (Exception e) {
return null;
}
}
/**
* 获取原请求头
*/
private Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedHashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
if(enumeration!=null){
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
}
return map;
}
}

补充说明
实际开发中,在业务复杂情况下难免使用异步编排的方式实现,这个时候你会发现请求头又丢失了。源码:
RequestContextHolder.class
:
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
if (attributes == null) {
attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
}
return attributes;
}
查看requestAttributesHolder
变量:
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
查看NamedThreadLocal
:
public class NamedThreadLocal<T> extends ThreadLocal<T> {
private final String name;
public NamedThreadLocal(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
}
public String toString() {
return this.name;
}
}
ThreadLocal
是一个线程局部变量,在不同线程之间是独立的所以我们获取不到原先主线程的请求属性。
方案
//获取之前的请求
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> getAddress = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getId());
//每一个线程都来共享之前请求的数据
RequestContextHolder.setRequestAttributes(requestAttributes);
List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
confirmVo.setAddress(address);
}, executor);
至此我们已经解决了Feign远程以及异步编排下导致的请求头丢失问题。
如果这篇文章对你有所帮助,或者有所启发的话,帮忙 分享、收藏、点赞、在看,你的支持就是我坚持下去的最大动力!
原文始发于微信公众号(一安未来):SpringCloud Alibaba微服务实战之远程Feign请求头丢失
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/145141.html