作者:msf6688 | 来源:互联网 | 2024-12-19 10:17
Feign远程调用请求头丢失问题分析
在微服务架构中,Feign是一个常用的声明式HTTP客户端,用于简化服务间的调用。然而,在实际开发过程中,经常会遇到Feign远程调用时请求头丢失的问题,尤其是在涉及用户认证和授权的场景中。本文将详细探讨这一问题,并提供解决方案。
单线程中Feign远程调用丢失请求头的情况
在单线程环境中,Feign远程调用可能会导致请求头丢失,从而影响后续的服务调用。具体表现为,当用户在购物车中选好商品并点击结算时,订单服务通过Feign调用购物车服务获取购物车详情,但由于请求头丢失,购物车服务无法识别用户是否已登录,从而无法正确返回购物车信息。
代码案例
订单服务 - Controller
@GetMapping("/toTrade")
public String toTrade(Model model) {
OrderConfirmVo orderCOnfirmVo= orderService.confirmOrder();
model.addAttribute("orderConfirmData", orderConfirmVo);
return "confirm";
}
拦截器
@Component
public class OrderLoginIntercepted implements HandlerInterceptor {
public static ThreadLocal threadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
MemberResponseVo attribute = (MemberResponseVo) request.getSession().getAttribute(AuthConstant.LOGIN_USER);
if (attribute != null) {
threadLocal.set(attribute);
return true;
} else {
request.getSession().setAttribute("msg", "请先进行登录");
response.sendRedirect("http://auth.grapesmail.com/login.html");
return false;
}
}
}
ServiceImpl
@Override
public OrderConfirmVo confirmOrder() {
OrderConfirmVo orderCOnfirmVo= new OrderConfirmVo();
MemberResponseVo memberRespOnseVo= OrderLoginIntercepted.threadLocal.get();
List address = memberFeignService.getAddress(memberResponseVo.getId());
orderConfirmVo.setMemberAddressVos(address);
// 远程调用购物车服务查询购物车所有购物项
List currentUserCarItems = cartFeignService.getCurrentUserCarItems();
orderConfirmVo.setItems(currentUserCarItems);
Integer integration = memberResponseVo.getIntegration();
orderConfirmVo.setIntegration(integration);
// TODO 接口幂等性,防重令牌
return orderConfirmVo;
}
购物车服务 - 拦截器部分代码
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
UserInfoTo userInfoTo = new UserInfoTo();
HttpSession session = request.getSession();
MemberResponseVo user = (MemberResponseVo) session.getAttribute(AuthConstant.LOGIN_USER);
if (user != null) {
userInfoTo.setUserId(user.getId());
}
}
源码解析丢失请求头原因
Feign在进行远程调用时,默认会构造一个新的请求对象,这个新的请求对象不会携带原有的请求头信息。因此,如果依赖于请求头中的某些信息(如Session、COOKIE等),这些信息将会丢失,导致被调用的服务无法正确识别用户身份。
解决方法
为了确保请求头信息在Feign远程调用中不丢失,可以在订单服务中添加一个自定义的RequestInterceptor拦截器,该拦截器会在每次远程调用前将请求头信息复制到新的请求中。
添加RequestInterceptor拦截器
@Configuration
public class MailFeignConfig {
@Bean(name = "requestInterceptor")
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
if (request != null) {
String COOKIE = request.getHeader("COOKIE");
requestTemplate.header("COOKIE", COOKIE);
}
}
};
}
}
异步调用中Feign远程调用丢失请求头的情况
在异步调用中,由于多线程环境的特性,请求头信息更容易丢失。为了解决这一问题,可以通过手动设置请求上下文,确保每个线程都能获取到正确的请求头信息。
代码案例
@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
OrderConfirmVo orderCOnfirmVo= new OrderConfirmVo();
MemberResponseVo memberRespOnseVo= OrderLoginIntercepted.threadLocal.get();
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture getAddress = CompletableFuture.runAsync(() -> {
RequestContextHolder.setRequestAttributes(requestAttributes);
List address = memberFeignService.getAddress(memberResponseVo.getId());
orderConfirmVo.setMemberAddressVos(address);
}, threadPoolExecutor);
CompletableFuture getCurrentUserCartItems = CompletableFuture.runAsync(() -> {
RequestContextHolder.setRequestAttributes(requestAttributes);
List currentUserCarItems = cartFeignService.getCurrentUserCarItems();
orderConfirmVo.setItems(currentUserCarItems);
}, threadPoolExecutor);
Integer integration = memberResponseVo.getIntegration();
orderConfirmVo.setIntegration(integration);
// 其他数据自动计算
// TODO 接口幂等性,防重令牌
CompletableFuture.allOf(getAddress, getCurrentUserCartItems).get();
return orderConfirmVo;
}
两者区别
单线程和异步调用中Feign远程调用丢失请求头的主要区别在于线程管理。单线程环境下,通过自定义RequestInterceptor即可解决问题;而在异步调用中,需要手动设置请求上下文,确保每个线程都能获取到正确的请求头信息。
详细视频教学:Feign远程调用丢失