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;
}
}

浙公网安备 33010602011771号