1. 程式人生 > >[SpringCloud-Feign] Feign轉發請求頭,(防止session失效)

[SpringCloud-Feign] Feign轉發請求頭,(防止session失效)

Feign自動轉發HTTP請求頭,(防止session失效)

微服務開發中經常有這樣的需求,公司自定義了通用的請求頭,需要在微服務的呼叫鏈中轉發,比如在請求頭中加入了token,或者某個自定義的資訊uniqueId,總之就是自定義的一個鍵值對的東東,A服務呼叫B服務,B服務呼叫C服務,這樣通用的東西如何讓他在一個呼叫鏈中不斷地傳遞下去呢?以A服務為例:

方案1

  • 最傻的辦法,在程式中獲取,呼叫B的時候再轉發,怎麼獲取在Controller中國通過註解獲取,或者通過request物件獲取,這個不難,在請求B服務的時候,通過註解將值放進去即可;簡程式碼如下:
獲取:
@RequestMapping(value = "/api/test", method = RequestMethod.GET)
public String testFun(@RequestParam String name, @RequestHeader("uniqueId") String uniqueId) {
    if(uniqueId == null ){
         return "Must defined the uniqueId , it can not be null";
    }
    log.info(uniqueId, "begin testFun... ");
 return uniqueId;
}

然後A使用Feign呼叫B服務的時候,傳過去:

@FeignClient(value = "DEMO-SERVICE")
public interface CallClient {

   /**
 * 訪問DEMO-SERVICE服務的/api/test介面,通過註解將logId傳遞給下游服務
  */
 @RequestMapping(value = "/api/test", method = RequestMethod.GET)
    String callApiTest(@RequestParam(value = "name") String name, @RequestHeader(value = "uniqueId") String uniqueId);

}

方案弊端:毫無疑問,這方案不好,因為對程式碼有侵入,需要開發人員沒次手動的獲取和新增,因此捨棄

方案2

  • 服務通過請求攔截器,在請求從A傳送到B之後,在攔截器內將自己需要的東東加到請求頭:
 import com.intellif.log.LoggerUtilI;
import feign.RequestInterceptor;
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.HttpServletRequest;
import java.util.Enumeration;

/**
 * 自定義的請求頭處理類,處理服務傳送時的請求頭;
 * 將服務接收到的請求頭中的uniqueId和token欄位取出來,並設定到新的請求頭裡面去轉發給下游服務
 * 比如A服務收到一個請求,請求頭裡麵包含uniqueId和token欄位,A處理時會使用Feign客戶端呼叫B服務
 * 那麼uniqueId和token這兩個欄位就會新增到請求頭中一併發給B服務;
 *
 * @author mozping
 * @version 1.0
 * @date 2018/6/27 14:13
 * @see FeignHeadConfiguration
 * @since JDK1.8
 */
@Configuration
public class FeignHeadConfiguration {
    private final LoggerUtilI logger = LoggerUtilI.getLogger(this.getClass().getName());

    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attrs != null) {
                HttpServletRequest request = attrs.getRequest();
                Enumeration<String> headerNames = request.getHeaderNames();
                if (headerNames != null) {
                    while (headerNames.hasMoreElements()) {
                        String name = headerNames.nextElement();
                        String value = request.getHeader(name);
                        /**
                         * 遍歷請求頭裡面的屬性欄位,將logId和token新增到新的請求頭中轉發到下游服務
                         * */
                        if ("uniqueId".equalsIgnoreCase(name) || "token".equalsIgnoreCase(name)) {
                            logger.debug("新增自定義請求頭key:" + name + ",value:" + value);
                            requestTemplate.header(name, value);
                        } else {
                            logger.debug("FeignHeadConfiguration", "非自定義請求頭key:" + name + ",value:" + value + "不需要新增!");
                        }
                    }
                } else {
                    logger.warn("FeignHeadConfiguration", "獲取請求頭失敗!");
                }
            }
        };
    }

}
 

網上很多關於這種方法的博文或者資料,大同小異,但是有一個問題,在開啟熔斷器之後,這裡的attrs就是null,因為熔斷器預設的隔離策略是thread,也就是執行緒隔離,實際上接收到的物件和這個在傳送給B不是一個執行緒,怎麼辦?有一個辦法,修改隔離策略hystrix.command.default.execution.isolation.strategy=SEMAPHORE,改為訊號量的隔離模式,但是不推薦,因為thread是預設的,而且要命的是訊號量模式,熔斷器不生效,比如設定了熔斷時間hystrix.command.default.execution.isolation.semaphore.timeoutInMilliseconds=5000,五秒,如果B服務裡面sleep了10秒,非得等到B執行完畢再返回,因此這個方案也不可取;但是有什麼辦法可以在預設的Thread模式下讓攔截器拿到上游服務的請求頭?自定義策略:程式碼如下:

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.command.default.execution.isolation.strategy=SEMAPHORE,這樣的話轉發執行緒和請求執行緒實際上是一個執行緒,這並不是最好的解決方法,訊號量模式也不是官方最為推薦的隔離策略;另一個解決方法就是自定義Hystrix的隔離策略,思路是將現有的併發策略作為新併發策略的成員變數,在新併發策略中,返回現有併發策略的執行緒池、Queue;將策略加到Spring容器即可;
 *
 * @author mozping
 * @version 1.0
 * @date 2018/7/5 9:08
 * @see FeignHystrixConcurrencyStrategyIntellif
 * @since JDK1.8
 */
@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();
            }
        }
    }
}