SpringCloud服务之间Feign调用不传header

前提:Feign调用不传header

项目采用微服务架构,项目现有微服务A与微服务B
前端请求微服务A,微服务A需要feign调用微服务B,发现请求到微服务B后,请求的header并没有携带

解决办法:Feign调用不传header

1、增加配置

import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Author :
 * @Date :Created in 2023/3/22 15:13
 * @Description:@Configuration如果加上此注解,所有的feign请求都会通过此拦截器
 *              由于服务间feign调用header不传,增加此配置类
 */
@Slf4j
///注释有提示作用
//@Configuration
public class ServiceFeignConfiguration implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        Map<String,String> headers = getHeaders(getHttpServletRequest());
        for(String headerName : headers.keySet()){
            requestTemplate.header(headerName, getHeaders(getHttpServletRequest()).get(headerName));
        }
    }

    /**
     * 获取请求的request
     * @return
     */
    private HttpServletRequest getHttpServletRequest() {
        try {
            return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            return null;
        }
    }

    /**
     * 获取header
     * @param request
     * @return
     */
    private Map<String, String> getHeaders(HttpServletRequest request) {
        Map<String, String> map = new LinkedHashMap<>();
        Enumeration<String> enumeration = request.getHeaderNames();
        while (enumeration.hasMoreElements()) {
            String key = enumeration.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }
}

配置说明:

如果配置上加@Configuration注解,所有的微服务之间feign请求都会拦截,例如:现有三个微服务,微服务A、微服务B、微服务C,在微服务A中加了此配置类,那么从微服务Afeign请求微服务B、微服务Afeign请求微服务C,都会经过此拦截器

如果不加@Configuration注解,需要与 @FeignClient的configuration 参数配合使用

 @FeignClient(value = "serviceB",fallback = FeignserviceBImpl.class,configuration = ServiceFeignConfiguration.class)

表示只有在微服务Afeign请求微服务B时,拦截请求

解决配置类中RequestContextHolder.getRequestAttributes()为null的方法

产生原因

出现空指针异常(null)的原因都是开启了线程池,feign异步调用,如果开启了hystrix,hystrix如果配置成Thread模式,本质也是开启了新的线程池

在配置类中,有如下方法:

   /**
     * 获取请求的request
     * @return
     */
    private HttpServletRequest getHttpServletRequest() {
        try {
            return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            return null;
        }
    }

发现有时候调用RequestContextHolder.getRequestAttributes()为null

解决办法1:关闭hystrix

feign:
  hystrix:
    enabled: false

解决办法2:hystrix的策略改为SEMAPHORE

修改hystrix配置,将hystrix的策略改为SEMAPHORE

feign:
  hystrix:
    enabled: true
  client:
    config:
      default:
        connectTimeout: 60000
        readTimeout: 60000

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          strategy: SEMAPHORE
          thread:
            timeoutInMilliseconds: 60000

所谓信号量模式就是不单独为每一个FeignClient分配线程池,而是限制每一个FeignClient调用的线程数,线程池还是用的http的线程池,feign调用线程不会变换就可以获取到request。但是Hystrix官方并不建议使用这种模式,特别是下游接口响应不快的时候会长时间http线程池影响性能。

解决办法3:自定义hystrix并发策略

注意:这个解决办法并没有实际测试过,来自:https://blog.csdn.net/weixin_42396189/article/details/129408707

继承HystrixConcurrencyStrategy类,覆写wrapCallable方法在callable外获取RequestContextHolder上下文信息,然后传入到callable内设置到对应上下文环境中。由于RequestContextHolder中的request可能会被http线程释放,所以建议创建自己的ContextHolder单独放head信息。
这里会遇到另一个问题,由于HystrixConcurrencyStrategy只能有一个,所以写个简单的HystrixConcurrencyStrategy注册到HystrixPlugins中,可能会导致以后引入Spring Security之类的依赖后被它覆盖。所以我参考了Spring Security写的SecurityContextConcurrencyStrategy写了一个类似的,它内部判断了当前已经存在的HystrixConcurrencyStrategy,然后在已存在的基础上进行包装,将多个HystrixConcurrencyStrategy层层嵌套解决兼容的问题。

public class RequestHeaderHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {

	private final HystrixConcurrencyStrategy existingConcurrencyStrategy;

	public RequestHeaderHystrixConcurrencyStrategy(HystrixConcurrencyStrategy existingConcurrencyStrategy) {
		this.existingConcurrencyStrategy = existingConcurrencyStrategy;
	}

	@Override
	public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
		return existingConcurrencyStrategy != null ? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize) : super.getBlockingQueue(maxQueueSize);
	}

	@Override
	public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
		return existingConcurrencyStrategy != null ? existingConcurrencyStrategy.getRequestVariable(rv) : super.getRequestVariable(rv);
	}

	@Override
	public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize,
			HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
		return existingConcurrencyStrategy != null ?
				existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue) :
				super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
	}

	@Override
	public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
		return existingConcurrencyStrategy != null ?
				existingConcurrencyStrategy.getThreadPool(threadPoolKey, threadPoolProperties) :
				super.getThreadPool(threadPoolKey, threadPoolProperties);
	}

	@Override
	public <T> Callable<T> wrapCallable(Callable<T> callable) {
		Map<String, String> headers = AuthenticationContextHolder.getRequestHeaders();
		SysUser userInfo = AuthenticationContextHolder.getUserInfo();
		return existingConcurrencyStrategy != null ?
				existingConcurrencyStrategy.wrapCallable(new WrappedCallable<T>(callable, headers, userInfo)) :
				super.wrapCallable(new WrappedCallable<T>(callable, headers, userInfo));
	}

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

		private final Callable<T> callable;

		private final Map<String, String> headers;

		private final SysUser userInfo;

		public WrappedCallable(Callable<T> callable, Map<String, String> headers, SysUser userInfo) {
			this.callable = callable;
			this.headers = headers;
			this.userInfo = userInfo;
		}

		@Override
		public T call() throws Exception {
			try {
				AuthenticationContextHolder.setRequestHeaders(headers);
				AuthenticationContextHolder.setUserInfo(userInfo);
				return callable.call();
			} finally {
				AuthenticationContextHolder.remove();
			}
		}
	}
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "true")
@Slf4j
public class HystrixConcurrencyStrategyAutoConfiguration {

	@Autowired(required = false)
	private HystrixConcurrencyStrategy existingConcurrencyStrategy;

	@PostConstruct
	public void init() {
		HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
		HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
		HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy();
		HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook();
		HystrixConcurrencyStrategy concurrencyStrategy = detectRegisteredConcurrencyStrategy();

		HystrixPlugins.reset();

		// 注册Hystrix并发策略以外的插件
		HystrixPlugins.getInstance().registerConcurrencyStrategy(new RequestHeaderHystrixConcurrencyStrategy(concurrencyStrategy));
		HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
		HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
		HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
		HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
	}

	private HystrixConcurrencyStrategy detectRegisteredConcurrencyStrategy() {
		HystrixConcurrencyStrategy registeredStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
		if (existingConcurrencyStrategy == null) {
			return registeredStrategy;
		}
		if (registeredStrategy instanceof HystrixConcurrencyStrategyDefault) {
			return existingConcurrencyStrategy;
		}
		if (!existingConcurrencyStrategy.equals(registeredStrategy)) {
			log.warn("找到多个 HystrixConcurrencyStrategy, 使用已存在的HystrixConcurrencyStrategy");
		}
		return existingConcurrencyStrategy;
	}
}

@Component
public class FeignRequestInterceptor implements RequestInterceptor {
	@Override
	public void apply(RequestTemplate requestTemplate) {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		if (attributes != null) {
			HttpServletRequest request = attributes.getRequest();
			Enumeration<String> headerNames = request.getHeaderNames();
			if (headerNames != null) {
				while (headerNames.hasMoreElements()) {
					String name = headerNames.nextElement();
					String values = request.getHeader(name);
					requestTemplate.header(name, values);
				}
			}
		} else {
			// RequestContextHolder中获取不到request时,可能是当前线程在Hystrix线程池中,则需要从AuthenticationontextHolder中获取header信息
			Map<String, String> headers = AuthenticationContextHolder.getRequestHeaders();
			if (MapUtils.isNotEmpty(headers)) {
				headers.forEach(requestTemplate::header);
			}
		}
	}
}

其中HystrixConcurrencyStrategyAutoConfiguration类中加了条件注解,在关闭hystrix的时候就不需要装配自定义的并发策略了

解决办法4:增加如下代码

添加如下代码:

ServletRequestAttributes attribute = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(attribute, true);

可以在微服务Afeign请求微服务B之前加上如上两个代码

ServletRequestAttributes attribute = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(attribute, true);
//接下来在写feign调用的代码

或者在微服务A增加一个拦截,在访问controller之前过一下拦截


import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Author :
 * @Date :Created in 2023/3/22 17:41
 * @Description:前端请求调用接口,接口调用其它服务时,header传递失败,需要增加此拦截器
 */
public class FeignInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ServletRequestAttributes attribute = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        RequestContextHolder.setRequestAttributes(attribute, true);

        return true;
    }
}

posted @ 2023-03-24 10:12  KeepArlen  阅读(791)  评论(0)    收藏  举报