问题
在微服务中,多线程异步+Feign调用会出现请求头丢失
解决
- 在主线程中先获取请求头参数
- 传入子线程中
- 由子线程将请求头参数设置到上下文中
- 最后在Feign转发处理中拿到子线程设置的上下文的请求头数据,转发到下游。
获取上下文请求参数工具类
@Slf4j
public class RequestContextUtil {
/**
* 获取请求头数据
*
* @return key->请求头名称 value->请求头值
* @author zhengqingya
* @date 2021/6/30 9:39 下午
*/
public static Map<String, String> getHeaderMap() {
Map<String, String> headerMap = Maps.newLinkedHashMap();
try {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return headerMap;
}
HttpServletRequest request = requestAttributes.getRequest();
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
headerMap.put(key, value);
}
} catch (Exception e) {
log.error("《RequestContextUtil》 获取请求头参数失败:", e);
}
return headerMap;
}
}
请求头上下文
@Slf4j
public class RequestHeaderHandler {
public static final ThreadLocal<Map<String, String>> THREAD_LOCAL = new ThreadLocal<>();
public static void setHeaderMap(Map<String, String> headerMap) {
THREAD_LOCAL.set(headerMap);
}
public static Map<String, String> getHeaderMap() {
return THREAD_LOCAL.get();
}
public static void remove() {
THREAD_LOCAL.remove();
}
}
Feign转发处理rpc调用传参
*/
@Slf4j
@Configuration
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
@SneakyThrows
public void apply(RequestTemplate requestTemplate) {
log.debug("========================== ↓↓↓↓↓↓ 《FeignRequestInterceptor》 Start... ↓↓↓↓↓↓ ==========================");
Map<String, String> threadHeaderNameMap = RequestHeaderHandler.getHeaderMap();
if (!CollectionUtils.isEmpty(threadHeaderNameMap)) {
threadHeaderNameMap.forEach((headerName, headerValue) -> {
log.debug("《FeignRequestInterceptor》 多线程 headerName:【{}】 headerValue:【{}】", headerName, headerValue);
requestTemplate.header(headerName, headerValue);
});
}
Map<String, String> headerMap = RequestContextUtil.getHeaderMap();
headerMap.forEach((headerName, headerValue) -> {
log.debug("《FeignRequestInterceptor》 headerName:【{}】 headerValue:【{}】", headerName, headerValue);
requestTemplate.header(headerName, headerValue);
});
log.debug("========================== ↑↑↑↑↑↑ 《FeignRequestInterceptor》 End... ↑↑↑↑↑↑ ==========================");
}
}
使用案例
@Slf4j
@RestController
@RequestMapping("/web/api/demo/test")
@Api(tags = "测试api")
@AllArgsConstructor
public class RpcController extends BaseController {
private SystemTaskThread systemTaskThread;
@GetMapping("getContextUserId")
@ApiOperation("rpc调用测试 - Async")
public void getContextUserId() {
Map<String, String> headerMap = RequestContextUtil.getHeaderMap();
log.info("主线程请求头值: {}", headerMap.get("userId"));
this.systemTaskThread.getRequestHeaderUserId(RequestContextUtil.getHeaderMap());
}
}
@Slf4j
@Component
@AllArgsConstructor
public class SystemTaskThread {
private ISystemClient systemClient;
@SneakyThrows
@Async(ThreadPoolConstant.SMALL_TOOLS_THREAD_POOL)
public void getRequestHeaderUserId(Map<String, String> headerMap) {
RequestHeaderHandler.setHeaderMap(headerMap);
log.info("子线程请求头值: {}", this.systemClient.getRequestHeaderUserId());
}
}
注:网上也有资料提到在主线程获取请求参数
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
传到子线程中,再重新赋值RequestContextHolder.setRequestAttributes(requestAttributes);
但是这种方式小编尝试无效,顺便记录在这里吧~
本文案例demo源码
https://gitee.com/zhengqingya/small-tools
今日分享语句:
在你失落时,千万不好失去对生活的信心;
在你受挫折时,千万不好埋怨上天的不公;
当你失败时,千万不好失去对成功的追求;
人,总要经受住各种考验。