异步调用feign请求头丢失数据问题解决 | 您所在的位置:网站首页 › feign异步调用 › 异步调用feign请求头丢失数据问题解决 |
一、RequestContextUtil
package com.yubin.cn.facade.client.utils;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Map;
/**
* @auhtor
* @create 2022-06-12-11:58
*/
@Slf4j
public class RequestContextUtil {
/**
* 获取请求头数据
*
* @return key->请求头名称 value->请求头值
* @author
* @date 2021/6/30 9:39 下午
*/
public static Map getHeaderMap() {
Map headerMap = Maps.newLinkedHashMap();
try {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return headerMap;
}
HttpServletRequest request = requestAttributes.getRequest();
Enumeration enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
if (StrUtil.equalsIgnoreCase("authorization", key)) {
headerMap.put(key, value);
break;
}
}
} catch (Exception e) {
log.error("《RequestContextUtil》 获取请求头参数失败:", e);
}
return headerMap;
}
}
二、RequestHeaderHandler
@Slf4j
public class RequestHeaderHandler {
public static final ThreadLocal THREAD_LOCAL = new ThreadLocal();
public static void setHeaderMap(Map headerMap) {
THREAD_LOCAL.set(headerMap);
}
public static Map getHeaderMap() {
return THREAD_LOCAL.get();
}
public static void remove() {
THREAD_LOCAL.remove();
}
}
三、FeignClientsConfiguration
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.yubin.cn.facade.client.utils.RequestContextUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
/**
* @auhtor
* @create 2022-05-20-10:05
*/
@Slf4j
@Configuration
public class FiberDemandFeignClientsConfiguration {
@Bean
public RequestInterceptor requestInterceptor() {
return (RequestTemplate requestTemplate) -> {
//ServletRequestAttributes attributes =
// (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//
//if (attributes != null) {
// HttpServletRequest request = attributes.getRequest();
// String authorization = request.getHeader("authorization");
// log.info("FiberDemandFeignClientsConfiguration请求头信息:{}", authorization);
// requestTemplate.header("authorization", authorization);
//}
log.info("========================== ↓↓↓↓↓↓ 《FeignRequestInterceptor》 Start... ↓↓↓↓↓↓ ====当前线程:{}======================", Thread.currentThread().getId());
Map threadHeaderNameMap = RequestHeaderHandler.getHeaderMap();
if (CollUtil.isNotEmpty(threadHeaderNameMap)) {
threadHeaderNameMap.forEach((headerName, headerValue) -> {
log.info("《FeignRequestInterceptor》 多线程 headerName:【{}】 headerValue:【{}】,当前线程:【{}】", headerName, headerValue, Thread.currentThread().getId());
// 跳过 content-length ,Exception message - too many bytes written executing POST ,四种解决办法,这是其中之一
//因为服务之间调用需要携带一些用户信息之类的 所以实现了Feign的RequestInterceptor拦截器复制请求头,
// 复制的时候是所有头都复制的,可能导致Content-length长度跟body不一致. 所以只需要判断如果是Content-length就跳过
if (headerName.equals("content-length")) {
return;
}
if (StrUtil.equalsIgnoreCase("authorization", headerName)) {
requestTemplate.header(headerName, headerValue);
}
});
}
Map headerMap = RequestContextUtil.getHeaderMap();
if (CollUtil.isNotEmpty(headerMap)) {
headerMap.forEach((headerName, headerValue) -> {
log.info("《FeignRequestInterceptor》 headerName:【{}】 headerValue:【{}】,当前线程:【{}】", headerName, headerValue, Thread.currentThread().getId());
// 跳过 content-length
if (headerName.equals("content-length")) {
return;
}
if (StrUtil.equalsIgnoreCase("authorization", headerName)) {
requestTemplate.header(headerName, headerValue);
}
});
}
log.info("========================== ↑↑↑↑↑↑ 《FeignRequestInterceptor》 End... ↑↑↑↑↑↑ =====当前线程:{}=====================", Thread.currentThread().getId());
};
}
}
四、feign调用的注解中配置
@FeignClient(url = "${feign.remote.${spring.profiles.active}}", name = "aaa", configuration = {FeignClientsConfiguration.class}, fallback = FeignClientFallback.class, contextId = "fiberDemandFeignClient")
public interface FeignClient {
// https://com.yubin.cn/gateway/yubin-getway/api/aaa/bbb
@PostMapping(value = "/demand-getway/api/docking/pushProjectInfo")
Map pushProjectInfo(@Validated @RequestBody FeignReq req);
@PostMapping(value = "/demand-getway/api/docking/pushConstructionInfo")
Map pushConstructionInfo(@Validated @RequestBody FeignReq req);
}
五、调用
方法a中调用接口feign,获取当前请求头信息headerMap (从RequestContextUtil工具中获取请求头),然后调用方法fiberDemandFeignClient.pushProjectInfo(),此时会调用feign配置类的方法FeignClientsConfiguration.requestInterceptor Map threadHeaderNameMap = RequestHeaderHandler.getHeaderMap();这个时候获取的是空的, Map headerMap = RequestContextUtil.getHeaderMap();这个会获取到上下文的请求头放入feign的请求中。 当调用失败之后,启动异步调用重试机制,asyncService.retryCallPushProjectInfo()将请求头信息传过去。 新线程(异步也许是同一个线程)存储在自己的ThreadLocal中,然后重新调用feign方法。 然后会调用配置类FeignClientsConfiguration中的方法requestInterceptor方法,首先从当前线程取出来放入到feign请求中,然后RequestContextUtil.getHeaderMap()获取当前的请求头信息(如果重试这里应该是获取的空的)加入到当前请求中。 |
CopyRight 2018-2019 实验室设备网 版权所有 |