对Feign的请求url 重写
需求:对当前请求的 url 重新构建
debug feign 的执行可知,重写 LoadBalancerFeignClient 类中的 execute 方法即可控制当前请求的url
代码分析
当引入 spring-cloud-sleuth-stream 时, seluth也重写了feign 的 feign.Client 查看 TraceFeignClientAutoConfiguration 类,
@Configuration
@ConditionalOnProperty(name = "spring.sleuth.feign.processor.enabled", matchIfMissing = true)
protected static class FeignBeanPostProcessorConfiguration { @Bean
FeignContextBeanPostProcessor feignContextBeanPostProcessor(BeanFactory beanFactory) {
return new FeignContextBeanPostProcessor(beanFactory);// 对 FeignContext 进行了重新包装
}
}
@Bean // 对 LoadBalancerFeignClient bean 的包装类
TraceFeignObjectWrapper traceFeignObjectWrapper(BeanFactory beanFactory) {
return new TraceFeignObjectWrapper(beanFactory);
}
@Bean // 对 feign.Client 的所有方法进行拦截,如果 执行的 bean 不是 TraceFeignClient ,则返 TraceFeignClient bean,执行该类中的方法,该类也实现了 feign的 Client 接口
TraceFeignAspect traceFeignAspect(BeanFactory beanFactory) {
return new TraceFeignAspect(beanFactory);
}
final class FeignContextBeanPostProcessor implements BeanPostProcessor { private final BeanFactory beanFactory;
private TraceFeignObjectWrapper traceFeignObjectWrapper; FeignContextBeanPostProcessor(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
} @Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof FeignContext && !(bean instanceof TraceFeignContext)) { //如果bean 是 FeignContext,则返回 TraceFeignContext 的bean
return new TraceFeignContext(getTraceFeignObjectWrapper(), (FeignContext) bean);
}
return bean;
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
} private TraceFeignObjectWrapper getTraceFeignObjectWrapper() {
if (this.traceFeignObjectWrapper == null) {
// 查看 TraceFeignObjectWrapper 类
this.traceFeignObjectWrapper = this.beanFactory.getBean(TraceFeignObjectWrapper.class);
}
return this.traceFeignObjectWrapper;
}
}
查看 TraceFeignObjectWrapper 类,代码如下:
final class TraceFeignObjectWrapper { private final BeanFactory beanFactory; private CachingSpringLoadBalancerFactory cachingSpringLoadBalancerFactory;
private SpringClientFactory springClientFactory; TraceFeignObjectWrapper(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
} Object wrap(Object bean) {
if (bean instanceof Client && !(bean instanceof TraceFeignClient)) {
if (bean instanceof LoadBalancerFeignClient) { //如果 bean 是 LoadBalancerFeignClient 的 bean,则返回 TraceLoadBalancerFeignClient 的 bean
LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean);
return new TraceLoadBalancerFeignClient(
client.getDelegate(), factory(),
clientFactory(), this.beanFactory);
}
return new TraceFeignClient(this.beanFactory, (Client) bean);
}
return bean;
} CachingSpringLoadBalancerFactory factory() {
if (this.cachingSpringLoadBalancerFactory == null) {
this.cachingSpringLoadBalancerFactory = this.beanFactory
.getBean(CachingSpringLoadBalancerFactory.class);
}
return this.cachingSpringLoadBalancerFactory;
} SpringClientFactory clientFactory() {
if (this.springClientFactory == null) {
this.springClientFactory = this.beanFactory
.getBean(SpringClientFactory.class);
}
return this.springClientFactory;
}
}
当请求执行时,查看 LoadBalancerFeignClient类可知如图所示, delegate 显示为TraceFeignClient 的bean ,说明feign 的 负载均衡客户端现在用的是 TraceLoadBalancerFeignClient
查看 AbstractLoadBalancerAwareClient 类,获取当前server 的ip,端口,请求url,如图所示
feign 的默认 Client 为 Default 类,根据请求服务的ip,端口和url ,发起请求,所以最终请求的执行,如下面所示
由上面的流程可知,引入sleuth 时,sleuth 中的feign相关类重写了 feign 的负载均衡类,所以要关闭 这个功能,当 配置文件中有 spring.sleuth.feign.enabled=false 时,则
sleuth 重写 feign 负载均衡器 ,则不生效。
当配置文件中有 ribbon.eureka.enabled=false 属性时,才会使用 ribbon.listOfServers 中配置的信息,所有要 实现 EnvironmentPostProcessor 类,将属性放入
defaultProperties 配置文件级别中,方便当项目中有相同属性时,覆盖已添加的属性信息
@Slf4j
public class MockEnvironmentPostProcessor implements EnvironmentPostProcessor { private static final String PROPERTY_SOURCE_NAME = "defaultProperties"; @Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
//获取mock属性配置,并绑定到MockProperties 对象中
MockProperties target = new MockProperties();
RelaxedDataBinder binder = new RelaxedDataBinder(target,
MockProperties.MOCK_PREFIX);
binder.bind(new PropertySourcesPropertyValues(environment.getPropertySources())); boolean enabled = target.isEnabled();
if (true == enabled) {
System.out.println("mock server 构建环境属性");
Map<String, Object> map = new HashMap<>(); //对单个服务mock的处理
if (StringUtils.isNotBlank(target.getServices())) {
String[] services = target.getServices().split(",");
for (String service : services) {
map.put(service.toUpperCase() + ".ribbon.listOfServers", target.getIpAddress());
System.out.println(String.format("对[%s]服务配置 mock地址[%s]", service, target.getIpAddress()));
}
} // 自定义 每个服务的ip地址 服务直连情况
if (!target.getServicesMap().isEmpty()) {
Map<String, String> servicesMap = target.getServicesMap();
for (String key : servicesMap.keySet()) {
String ip = servicesMap.get(key);
map.put(key.toUpperCase() + ".ribbon.listOfServers", ip);
System.out.println(String.format("对[%s]服务配置直连地址[%s]", key, ip));
}
} // 服务重试切换次数
map.put("ribbon.MaxAutoRetriesNextServer", 0);
// 服务重试次数
map.put("ribbon.MaxAutoRetries", 0);
// ribbon 连接时间
map.put("ribbon.connectTimeoutMillis", 10000);
// ribbon 读取时间
map.put("ribbon.readTimeoutMillis", 60000);
// 对所有操作请求都不重试
map.put("ribbon.OkToRetryOnAllOperations", false); //ribbon不使用eureka上的服务信息
map.put("ribbon.eureka.enabled", false);
// 关闭 sleuth 对 feign client 的包装
map.put("spring.sleuth.feign.enabled", false);
// 设置全局的超时时间 hystrix 熔断超时时间
map.put("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 60000);
addOrReplace(environment.getPropertySources(), map); } } private void addOrReplace(MutablePropertySources propertySources,
Map<String, Object> map) {
MapPropertySource target = null;
if (propertySources.contains(PROPERTY_SOURCE_NAME)) {
PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME);
if (source instanceof MapPropertySource) {
target = (MapPropertySource) source;
for (String key : map.keySet()) {
if (!target.containsProperty(key)) {
target.getSource().put(key, map.get(key));
}
}
}
}
if (target == null) {
target = new MapPropertySource(PROPERTY_SOURCE_NAME, map);
}
if (!propertySources.contains(PROPERTY_SOURCE_NAME)) {
propertySources.addLast(target);
}
}
}
在 spring.factories 文件中配置
# Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=\
MockEnvironmentPostProcessor
继承 LoadBalancerFeignClient 类,重写 execute 方法
@Slf4j
public class MockLoadBalancerFeignClient extends LoadBalancerFeignClient { private MockProperties mockProperties; private DiscoveryClient discoveryClient; private Client delegate;
private CachingSpringLoadBalancerFactory lbClientFactory;
private SpringClientFactory clientFactory; public MockLoadBalancerFeignClient(Client delegate,
CachingSpringLoadBalancerFactory lbClientFactory,
SpringClientFactory clientFactory, MockProperties mockProperties, DiscoveryClient discoveryClient) {
super(delegate, lbClientFactory, clientFactory);
this.delegate = delegate;
this.lbClientFactory = lbClientFactory;
this.clientFactory = clientFactory; this.mockProperties = mockProperties;
this.discoveryClient = discoveryClient;
log.info("mock feign 负载均衡器初始化");
} /**
* 1. 如果配置 mock全局属性(默认false),则请求的所有服务都走 mock 服务器
* 2. 请求的服务在mock服务列表中,则请求走mock服务器
* 3. 请求的服务不在 mock 服务列表中,则先从直连配置获取服务信息,没有则从注册心上获取服务信息,请求转发到注册中心上的服务
* 4. 请求的服务不在 mock 服务列表中,也没有开启全局 mock 功能,则请求服务走 直连配置和注册中心相结合
*
* @param request
* @param options
* @return
* @throws IOException
*/
@Override
public Response execute(Request request, Request.Options options) throws IOException {
String url = request.url();
URI uri = URI.create(url);
String clientName = uri.getHost();
String[] mockServer = mockProperties.getIpAddress().split(":");
//请求的客户名称转为小写
clientName = clientName.toUpperCase();
String newUrl = null; // 从全局中获取该服务名称的配置信息
IClientConfig clientConfig = this.clientFactory.getClientConfig(clientName);
if (null != clientConfig && mockProperties.getGlobal()) {
// 配置当前服务的ip地址信息
// clientConfig.set(CommonClientConfigKey.ListOfServers, mockProperties.getIpAddress());
// 获取当前服务的负载均衡器,对当前服务的负载均衡器添加服务ip地址信息
if (this.clientFactory.getLoadBalancer(clientName).getAllServers().isEmpty()) {
this.clientFactory.getLoadBalancer(clientName).
addServers(Arrays.asList(new Server(mockServer[0], Integer.parseInt(mockServer[1]))));
} // 重新构建请求 URL
newUrl = this.getNewRequestUrl(url, clientName);
log.info("请求的 {} 服务已开启全局 mock 功能,服务地址:{}", clientName, this.clientFactory.getLoadBalancer(clientName).getAllServers());
} else {
//获取 配置 mock 服务的列表
String services = this.mockProperties.getServices();
if (StringUtils.isNotBlank(services)) {
// 配置 mock 服务的列表转为小写
services = services.toUpperCase();
// mock 服务列表
List<String> service = Arrays.asList(services.split(","));
// 当前服务是否在 mock 服务列表中
if (service.contains(clientName)) {
log.info("请求的 {} 服务在 mock 服务列表中,服务地址:{}", clientName, clientConfig.get(CommonClientConfigKey.ListOfServers));
newUrl = this.getNewRequestUrl(url, clientName);
} else {
// 服务直连情况 加 注册中心 处理
newUrl = getServiceInfoFromDiscoveryClient(url, clientName, clientConfig);
}
} else {
if (mockProperties.getServicesMap().isEmpty()) {
String msg = "没有配置 mock 服务列表,也没有开启全局mock功能,也没有配置服务直连,请检查配置或关闭mock功能";
throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.BAD_REQUEST_EXCEPTION, null, msg, null, null);
} // 服务直连情况 加 注册中心 处理
newUrl = getServiceInfoFromDiscoveryClient(url, clientName, clientConfig);
}
}
return this.getResponse(request, options, newUrl);
} /**
* 1.如果有服务直接配置,则直接返回url
* 2.如果不是直连,则从注册中上获取服务信息,并返回url
*
* @param url
* @param clientName
* @param clientConfig
* @return
*/
private String getServiceInfoFromDiscoveryClient(String url, String clientName, IClientConfig clientConfig) {
String newUrl; //服务直连处理
if (mockProperties.getServicesMap().size() > 0) {
// 处理一些自定义的服务和ip地址,服务的直连情况
Set<String> customServiceInfo = mockProperties.getServicesMap().keySet();
if (customServiceInfo.contains(clientName.toUpperCase())) {
log.info("请求的 {} 服务在直连列表中,服务地址:{}", clientName, mockProperties.getServicesMap().get(clientName));
newUrl = url;
return newUrl;
}
} if (null == this.discoveryClient) {
String format = String.format("%s 服务没有配置在mock列表中,并且也没有开启注册中心功能,请检查配置", clientName);
throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, null, format, null, null);
} // 获取 服务名的 服务信息
List<ServiceInstance> instances = this.discoveryClient.getInstances(clientName);
String host;
if (null == instances || instances.isEmpty()) {
host = String.format("%s 服务没有配置在mock列表中,也没有注册在住册中心上,请检查配置", clientName);
throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, null, host, null, null);
} // 获取 服务在 注册中心的地址信息
host = instances.get(0).getHost() + ":" + instances.get(0).getPort();
log.info("请求的 {} 服务在注则中心上,服务地址:{}", clientName, host);
newUrl = url; if (null != clientConfig) {
// clientConfig.set(CommonClientConfigKey.ListOfServers, host);
// 获取当前服务的负载均衡器,对当前服务的负载均衡器添加服务ip地址信息
if (this.clientFactory.getLoadBalancer(clientName).getAllServers().isEmpty()) {
this.clientFactory.getLoadBalancer(clientName).
addServers(Arrays.asList(new Server(instances.get(0).getHost(), instances.get(0).getPort())));
} }
return newUrl;
} /**
* 请求响应
*
* @param request
* @param options
* @param newUrl
* @return
* @throws IOException
*/
private Response getResponse(Request request, Request.Options options, String newUrl) throws IOException {
//重新构建 request 对象
Request newRequest = Request.create(request.method(),
newUrl, request.headers(), request.body(),
request.charset()); return super.execute(newRequest, options);
} /**
* 修改请求 url
*
* @param url
* @param clientName
* @return
*/
private String getNewRequestUrl(String url, String clientName) {
StringBuilder sb = new StringBuilder();
sb.append(clientName); String mockServerUrl = mockProperties.getMockServerUrl();
if (mockServerUrl.endsWith("/")) {
sb.append(mockServerUrl);
} else {
sb.append(mockServerUrl).append("/");
}
sb.append(clientName.toLowerCase()); String newUrl = url.replaceFirst(clientName, sb.toString()); log.info("mock 服务重新构建请求 URL 地址:{}", newUrl);
return newUrl;
}
}
配置自化配置类
@Slf4j
@ConditionalOnProperty(prefix = MockProperties.MOCK_PREFIX, name = "enabled", havingValue = "true")
@EnableConfigurationProperties(value = {MockProperties.class})
@Configuration
public class MockAutoConfiguration { @Bean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory, @Autowired(required = false) DiscoveryClient discoveryClient, MockProperties mockProperties) { return new MockLoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory, mockProperties, discoveryClient);
} @Bean
public MockFeignInterceptor mockFeignInterceptor() {
return new MockFeignInterceptor();
}
}
在 spring.factories 中配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
MockAutoConfiguration
自定义相关属性配置和提示,关于mock的属性配置
@ConfigurationProperties(prefix = MockProperties.MOCK_PREFIX, ignoreUnknownFields = true)
public class MockProperties {
public static final String MOCK_PREFIX = "mock.server"; /**
* 开启mock ,true:开启 false:关闭
*/
@Getter
@Setter
private boolean enabled = false; /**
* mock 服务的ip地址或域名 例:10.12.141.146:18081
*/
@Getter
@Setter
private String ipAddress; /**
* 如果每个服务的 mock server 的地址都一样的使用该配置,多个服务以 ,号 隔开 例:order-service,user-service
*/
@Getter
@Setter
private String services; /**
* 如果每个服务的 mock server 地址不一样,使用该配置,key:服务名 value: ip地址 ,例
*/
@Getter
@Setter
private Map<String, String> servicesMap = new ConcurrentHashMap<>(); /**
* mock server 服务url
*/
@Getter
@Setter
private String mockServerUrl; /**
* 是否需要所有服务都用 mock
*/ private boolean global = false; public boolean getGlobal() {
return global;
} public void setGlobal(boolean global) {
this.global = global;
}
}
Mock server 相关属性
当开启mock 功能时,所有的 feign 请求都会带上请求头: X-Mock-Application: true
mock.server.enabled=true
# mock 服务器ip地址和端口
mock.server.ip-address=123.90.8.1:18820
# mock 服务器的 url
mock.server.mock-server-url=/admin/mock
# mock 服务的列表,这里填写 feign 服务的服务名,多个以 ,号隔开,并大写
mock.server.services=PLATFORM-SERVICE,HELLO-SERVICE-1
如果项目中需要所有服务都要使用 mock 功能,则添加下面的属性
# 开启全局 mock 功能,也就是项目中所有的 feign服务都需要 mock
mock.server.global=true
如果项目中需要服务直连,则添加下面的属性
# user-service 服务的直连ip地址和端口
mock.server.services-map.user-service=192.168.111.10:9010
# cache-service 服务的直连ip地址和端口
mock.server.services-map.cache-service=10.10.90.23:9090
关闭全局 mock 功能
mock.server.global=false
对Feign的请求url 重写的更多相关文章
- 【思路】-URL重写
URL重写 重写原理 过程分析 疑惑地方 lookfor app.Request.ApplicationPath如果有子目录的话 这个地方可能会起到作用,暂时不确定 bool flag = url. ...
- 伪命题:PHP识别url重写请求
手上有一个网站,然后启用了伪静态,因为一些设置上的原因,一段时间后,发现收录的都是.php的文件,而启用的伪静态地址则收录很少,在更改设置后,想尽快去掉.php的收录,然后想将.php的地址转向.ht ...
- IIS 7 反向代理 URL重写 转发动态请求
一.反向代理是什么 有一篇文章说的挺好的 Nginx 反向代理.负载均衡.页面缓存.URL重写及读写分离详解 http://www.server110.com/nginx/201402/5534.ht ...
- Feign 自定义编码器、解码器和客户端,Feign 转发请求头(header参数)、Feign输出Info级别日志
Feign 的编码器.解码器和客户端都是支持自定义扩展,可以对请求以及结果和发起请求的过程进行自定义实现,Feign 默认支持 JSON 格式的编码器和解码器,如果希望支持其他的或者自定义格式就需要编 ...
- Nginx 反向代理、负载均衡、页面缓存、URL重写及读写分离详解
转载:http://freeloda.blog.51cto.com/2033581/1288553 大纲 一.前言 二.环境准备 三.安装与配置Nginx 四.Nginx之反向代理 五.Nginx之负 ...
- 用.htaccess文件实现URL重写
注:第一部分来自 http://www.cnblogs.com/wangkongming/archive/2012/11/13/2768251.html 这位博主的个人网站简洁 还有诗歌 ...
- 微软的坑:Url重写竟然会引起IIS内核模式缓存不工作
万万没有想到!当初为了解决使用负载均衡时记录客户端IP地址的问题,在IIS URL Rewrite Module中增加了一条URL重写规则(详见迁入阿里云后遇到的Request.UserHostAdd ...
- asp.net mvc 中 一种简单的 URL 重写
asp.net mvc 中 一种简单的 URL 重写 Intro 在项目中想增加一个公告的功能,但是又不想直接用默认带的那种路由,感觉好low逼,想弄成那种伪静态化的路由 (别问我为什么不直接静态化, ...
- nginx的URL重写应用实例
1,NGINx的URL重写 NGINX 的URL重写模块用的比较多,主要使用的命令有if rewrite set break 2 if命令 语法如下"" 语法:if(conditi ...
随机推荐
- 在MySQL中如何使用覆盖索引优化limit分页查询
背景 今年3月份时候,线上发生一次大事故.公司主要后端服务器发生宕机,所有接口超时.宕机半小时后,又自动恢复正常.但是过了2小时,又再次发生宕机. 通过接口日志,发现MySQL数据库无法响应服务器.在 ...
- TCP建立连接和断开连接过程
假设Client端发起中断连接请求,也就是发送FIN报文.Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着 ...
- Winform程序在XP系统上双击运行无反应解决方法
右键程序,打开属性栏,在兼容性选项里以兼容模式运行该程序即可解决.
- MVC、MVP、MVVM 模式
一.前言 做客户端开发.前端开发对MVC.MVP.MVVM这些名词不了解也应该大致听过,都是为了解决图形界面应用程序复杂性管理问题而产生的应用架构模式.网上很多文章关于这方面的讨论比较杂乱,各种MV* ...
- 第81天:jQuery 插件使用方法
在追求页面互动效果的时代,大家都想把页面效果做的美轮美奂,这一切都离不开前端技术脚本Javascript,而最近常被人用到的Javascript库文件则是jQuery. jQuery的使用具体步骤如 ...
- JavaScript常用方法(工具类的封装)
日期格式化 function formatDateTime(timeStamp) { var date = new Date(); date.setTime(timeStamp); var y = d ...
- 动态Lambda表达式打印HelloWorld
最近在用C#与数据库打交道.开发过程中采用了ORM模型(以前是纯sql玩法,复杂的逻辑用存储过程做). 为了能通过配置文件动态地查询字段,也就是说需要能这样写: db.AsQuery<T> ...
- 转--- 秒杀多线程第六篇 经典线程同步 事件Event
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇 一个经典的多线程同步问题> <秒杀多线程第五篇 经典线程同步关键段CS> 上一篇中使用关键段来解决经典的多线程同步互斥问题 ...
- [SCOI2016]幸运数字 线性基
题面 题面 题解 题面意思非常明确:求树上一条链的最大异或和. 我们用倍增的思想. 将这条链分成2部分:x ---> lca , lca ---> y 分别求出这2个部分的线性基,然后合并 ...
- CF765F Souvenirs 解题报告
CF765F Souvenirs 题意翻译 给出\(n(2 \le n \le 10^5 )\) ,一个长为\(n\)的序列\(a(0 \le a_i \le 10^9 )\). 给出\(m(1\le ...