SpringCloud Alibaba微服务实战之远程Feign请求头丢失问题解决方案

 更新时间:2024年02月20日 14:39:46   作者:竹林幽深  
这篇文章主要介绍了SpringCloud Alibaba微服务实战之远程Feign请求头丢失问题,对SpringCloud Alibaba Feign请求头问题感兴趣的朋友跟随小编一起看看吧

导读:在上一篇文章中我们讲解了如何利用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请求头丢失的文章就介绍到这了,更多相关SpringCloud Alibaba Feign请求头内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • idea创建springboot项目,Application.java不能运行问题及解决

    idea创建springboot项目,Application.java不能运行问题及解决

    这篇文章主要介绍了idea创建springboot项目,Application.java不能运行问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • Spring boot框架JWT实现用户账户密码登录验证流程

    Spring boot框架JWT实现用户账户密码登录验证流程

    这篇文章主要介绍了Springboot框架JWT实现用户账户密码登录验证,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06
  • Java实现简单日历小程序 Java图形界面小日历开发

    Java实现简单日历小程序 Java图形界面小日历开发

    这篇文章主要介绍了Java实现简单日历小程序,如何用Java swing开发一款简单的小日历,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-02-02
  • CyclicBarrier线程同步共享变量底层原理示例解析

    CyclicBarrier线程同步共享变量底层原理示例解析

    这篇文章主要为大家介绍了CyclicBarrier线程同步共享变量底层原理示例解析详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • SpringMVC拦截器实现登录认证

    SpringMVC拦截器实现登录认证

    这篇文章主要介绍了SpringMVC拦截器实现登录认证的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11
  • java正则替换sql中的参数实例代码

    java正则替换sql中的参数实例代码

    这篇文章主要给大家介绍了关于java正则替换sql中参数的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-10-10
  • SpringBoot整合mybatisPlus实现批量插入并获取ID详解

    SpringBoot整合mybatisPlus实现批量插入并获取ID详解

    这篇文章主要为大家详细介绍了SpringBoot如何整合mybatisPlus实现批量插入并获取ID,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-04-04
  • SpringBoot使用PageHelper插件实现Mybatis分页效果

    SpringBoot使用PageHelper插件实现Mybatis分页效果

    这篇文章主要介绍了SpringBoot使用PageHelper插件实现Mybatis分页效果,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作有一定的参考借鉴价值,需要的朋友可以参考下
    2024-02-02
  • Java中containsKey 、contains方法的使用及注意事项详解

    Java中containsKey 、contains方法的使用及注意事项详解

    这篇文章主要介绍了Java中containsKey 、 contains方法的使用及注意事项的相关资料,两者分别用于检查键和元素存在性,所属接口不同,性能差异显著,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-06-06
  • Lombok中@Accessors注解的使用及说明

    Lombok中@Accessors注解的使用及说明

    Lombok是一个Java库,旨在通过注解减少代码量,提高效率,@Accessors注解是其中一种工具,用于简化Java类的访问器方法,这个注解允许配置访问器的生成方式,包括是否使用fluent风格、是否忽略字段前缀以及是否支持链式调用,从而使代码更简洁、易读
    2025-10-10

最新评论