好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Feign调用服务时丢失Cookie和Header信息的解决方案

Feign调用服务丢失Cookie和Header信息

今天在使用Feign调用其他微服务的接口时,发现了一个问题:因为我的项目采用了无状态登录,token信息是存放在cookie中的,所以调用接口时,因为cookie中没有token信息,我的请求被拦截器拦截了。 

参考几篇文章,靠谱的解决方法是: 将cookie信息放到请求头中,再进行调用接口时,拦截器中可以对请求头进行解析,获取cookie信息

服务调用方

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

package top.codekiller.manager.upload.config;

import feign.RequestInterceptor;

import lombok.extern.slf4j.Slf4j;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.context.request.RequestContextHolder;

import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServletRequest;

import java.util.Enumeration;

/**

  * @author codekiller

  * @date 2020/5/26 14:22

  *

  *   自定义的请求头处理类,处理服务发送时的请求头;

  *   将服务接收到的请求头中的uniqueId和token字段取出来,并设置到新的请求头里面去转发给下游服务

  *   比如A服务收到一个请求,请求头里面包含uniqueId和token字段,A处理时会使用Feign客户端调用B服务

  *   那么uniqueId和token这两个字段就会添加到请求头中一并发给B服务;

  */

@Configuration

@Slf4j

public class FeignHeaderConfiguration {

     @Bean

     public RequestInterceptor requestInterceptor() {

         return requestTemplate -> {

             ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

             if (attrs != null ) {

                 HttpServletRequest request = attrs.getRequest();

                 // 如果在Cookie内通过如下方式取

                 Cookie[] cookies = request.getCookies();

                 if (cookies != null && cookies.length > 0 ) {

                     for (Cookie cookie : cookies) {

                         requestTemplate.header(cookie.getName(), cookie.getValue());

                         System.out.println( "信息" +cookie.getName()+cookie.getValue());

                     }

                 } else {

                     log.warn( "FeignHeadConfiguration" , "获取Cookie失败!" );

                 }

                

                 // 如果放在header内通过如下方式取

                 Enumeration<String> headerNames = request.getHeaderNames();

                 if (headerNames != null ) {

                     while (headerNames.hasMoreElements()) {

                         String name = headerNames.nextElement();

                         String value = request.getHeader(name);

                         /**

                          * 遍历请求头里面的属性字段,将jsessionid添加到新的请求头中转发到下游服务

                          * */

                         if ( "jsessionid" .equalsIgnoreCase(name)) {

                             log.debug( "添加自定义请求头key:" + name + ",value:" + value);

                             requestTemplate.header(name, value);

                         } else {

                             log.debug( "FeignHeadConfiguration" , "非自定义请求头key:" + name + ",value:" + value + "不需要添加!" );

                         }

                     }

                 } else {

                     log.warn( "FeignHeadConfiguration" , "获取请求头失败!" );

                 }

             }

         };

     }

}

服务接受方

?

1

2

3

4

5

6

7

8

9

10

11

12

//有些请求时从通过feign进行请求的,这一部分请求时不包含cookie信息的,因此我们要从请求头中获取

             Enumeration<String> headerNames = request.getHeaderNames();

             if (headerNames != null ) {

                 while (headerNames.hasMoreElements()) {

                     String name = headerNames.nextElement();

                     String value = request.getHeader(name);

                     System.out.println( "header的信息" +name+ "::::" +value);

                     if (name.equalsIgnoreCase( "MC_TOKEN" )){  //注意这里变成了小写

                         token=value;

                     }

                 }

             }

运行的时候,我发现请求还是被拦截了,看了下打印信息,发现我的MC_TOKEN变成了小写,所以在字符串进行比较的时候要忽略大小写。

以下为扩展,仅仅记录一下

这样仍然有个问题:

在开启熔断器之后,方法里的attrs是null,因为熔断器默认的隔离策略是thread,也就是线程隔离,实际上接收到的对象和这个在发送给B不是一个线程,怎么办?

有一个办法,修改隔离策略hystrix测试数据mand.default.execution.isolation.strategy=SEMAPHORE,改为信号量的隔离模式,但是不推荐,因为thread是默认的,而且要命的是信号量模式,熔断器不生效,比如设置了熔断时间。

另一个办法:重写Feign的隔离策略

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

import com.netflix.hystrix.HystrixThreadPoolKey;

import com.netflix.hystrix.HystrixThreadPoolProperties;

import com.netflix.hystrix.strategy.HystrixPlugins;

import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;

import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;

import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;

import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;

import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;

import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;

import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;

import com.netflix.hystrix.strategy.properties.HystrixProperty;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Component;

import org.springframework.web.context.request.RequestAttributes;

import org.springframework.web.context.request.RequestContextHolder;

 

import java.util.concurrent.BlockingQueue;

import java.util.concurrent.Callable;

import java.util.concurrent.ThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

 

/**

  * 自定义Feign的隔离策略;

  * 在转发Feign的请求头的时候,如果开启了Hystrix,Hystrix的默认隔离策略是Thread(线程隔离策略),因此转发拦截器内是无法获取到请求的请求头信息的,可以修改默认隔离策略为信号量模式:hystrix测试数据mand.default.execution.isolation.strategy=SEMAPHORE,这样的话转发线程和请求线程实际上是一个线程,这并不是最好的解决方法,信号量模式也不是官方最为推荐的隔离策略;另一个解决方法就是自定义Hystrix的隔离策略,思路是将现有的并发策略作为新并发策略的成员变量,在新并发策略中,返回现有并发策略的线程池、Queue;将策略加到Spring容器即可;

  *

  */

@Component

public class FeignHystrixConcurrencyStrategyIntellif extends HystrixConcurrencyStrategy {

 

     private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategyIntellif. class );

     private HystrixConcurrencyStrategy delegate;

 

     public FeignHystrixConcurrencyStrategyIntellif() {

         try {

             this .delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();

             if ( this .delegate instanceof FeignHystrixConcurrencyStrategyIntellif) {

                 // Welcome to singleton hell...

                 return ;

             }

             HystrixCommandExecutionHook commandExecutionHook =

                     HystrixPlugins.getInstance().getCommandExecutionHook();

             HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();

             HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();

             HystrixPropertiesStrategy propertiesStrategy =

                     HystrixPlugins.getInstance().getPropertiesStrategy();

             this .logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy);

             HystrixPlugins.reset();

             HystrixPlugins.getInstance().registerConcurrencyStrategy( this );

             HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);

             HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);

             HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);

             HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);

         } catch (Exception e) {

             log.error( "Failed to register Sleuth Hystrix Concurrency Strategy" , e);

         }

     }

 

     private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,

                                                  HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) {

         if (log.isDebugEnabled()) {

             log.debug( "Current Hystrix plugins configuration is [" + "concurrencyStrategy ["

                     + this .delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher ["

                     + metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]" );

             log.debug( "Registering Sleuth Hystrix Concurrency Strategy." );

         }

     }

 

     @Override

     public <T> Callable<T> wrapCallable(Callable<T> callable) {

         RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

         return new WrappedCallable<>(callable, requestAttributes);

     }

 

     @Override

     public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,

                                             HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize,

                                             HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {

         return this .delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,

                 unit, workQueue);

     }

 

     @Override

     public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,

                                             HystrixThreadPoolProperties threadPoolProperties) {

         return this .delegate.getThreadPool(threadPoolKey, threadPoolProperties);

     }

 

     @Override

     public BlockingQueue<Runnable> getBlockingQueue( int maxQueueSize) {

         return this .delegate.getBlockingQueue(maxQueueSize);

     }

 

     @Override

     public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {

         return this .delegate.getRequestVariable(rv);

     }

 

     static class WrappedCallable<T> implements Callable<T> {

         private final Callable<T> target;

         private final RequestAttributes requestAttributes;

 

         public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {

             this .target = target;

             this .requestAttributes = requestAttributes;

         }

 

         @Override

         public T call() throws Exception {

             try {

                 RequestContextHolder.setRequestAttributes(requestAttributes);

                 return target.call();

             } finally {

                 RequestContextHolder.resetRequestAttributes();

             }

         }

     }

}

然后使用默认的熔断器隔离策略,也可以在拦截器内获取到上游服务的请求头信息了;

Feign调用存在的问题

① feign远程调用丢失请求头

问题描述:

当远程调用其他服务时,设置了拦截器判断用户是否登录,但是结果是即使用户登录了,也会显示用户没登录,原因在于远程调用时,发送的请求是一个新的情求,请求中并不存在cookie,而原始请求中是携带cookie的。

解决方案如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

@Configuration

public class MallFeignConfig {

     @Bean ( "requestInterceptor" )

     public RequestInterceptor requestInterceptor() {

         RequestInterceptor requestInterceptor = template -> {

             //1、使用RequestContextHolder拿到刚进来的请求数据

             ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

             if (requestAttributes != null ) {

                 //老请求

                 HttpServletRequest request = requestAttributes.getRequest();

                 if (request != null ) {

                     //2、同步请求头的数据(主要是cookie)

                     //把老请求的cookie值放到新请求上来,进行一个同步

                     String cookie = request.getHeader( "Cookie" );

                     template.header( "Cookie" , cookie);

                 }

             }

         };

         return requestInterceptor;

     }

}

② 异步调用Feign丢失上下文问题

问题描述:

由于feign请求拦截器为新的request设置请求头底层是使用ThreadLocal保存刚进来的请求,所以在异步情况下,其他线程并不能获取到主线程的ThreadLocal,所以也拿不到请求。

解决:

先获取主线程的requestAttributes,再分别向其他线程中设置

?

1

2

3

4

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

CompletableFuture.runAsync(() ->{

    RequestContextHolder.setRequestAttributes(requestAttributes);

});

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

原文链接:https://blog.csdn.net/qq_44766883/article/details/106358839

查看更多关于Feign调用服务时丢失Cookie和Header信息的解决方案的详细内容...

  阅读:20次