为什么使用gateway
1.网关作为请求的入口,可以提供限流,权限校验等一系列功能
2.提供路由分发功能
2. 工作原理图
3. 简单使用
3.1 引入jar包
org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
org.springframework.boot
spring-boot-starter-actuator
3.2 配置文件
spring.application.name=gateway
spring.cloud.nacos.config.namespace=dev
spring.cloud.nacos.config.group=GATEWAY_GROUP
spring.cloud.nacos.config.file-extension=yaml
spring.cloud.nacos.config.server-addr=zy.nacos.com:8848
# gateway默认从注册中心给所有的服务建立一个默认的路由 默认false
spring.cloud.gateway.discovery.locator.enabled=true
3.2.3 动态路由
3.2.1 增加配置bean 用来监听nacos的配置文件修改
package com.zy.more.config;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import reactor.core.publisher.Mono;
/**
* 动态路由,可以通过获取Bean才做该类,提供增删改查已经发布功能
* @author wunaozai
* @Date 2020-03-16
*/
@Service
public class DynamicRouteConfig implements ApplicationEventPublisherAware {
private static final Logger log = LoggerFactory.getLogger(DynamicRouteConfig.class);
@Autowired
private RouteDefinitionWriter routedefinitionWriter;
private ApplicationEventPublisher publisher;
private String dataId = "gateway-router.properties";
private String group = "DEFAULT_GROUP";
@Value("${spring.cloud.nacos.config.server-addr}")
private String serverAddr;
@Value("${spring.cloud.nacos.config.namespace}")
private String namespace;
private static final List ROUTE_LIST = new ArrayList<>();
@PostConstruct
public void dynamicRouteByNacosListener() {
try {
Properties prop = new Properties();
prop.put("serverAddr", serverAddr);
prop.put("namespace", namespace);
ConfigService config = NacosFactory.createConfigService(prop);
String content = config.getConfig(dataId, group, 5000);
publisher(content);
config.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String config) {
publisher(config);
}
@Override
public Executor getExecutor() {
return null;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 增加路由
* @param def
* @return
*/
public Boolean addRoute(RouteDefinition def) {
try {
routedefinitionWriter.save(Mono.just(def)).subscribe();
ROUTE_LIST.add(def.getId());
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**
* 删除路由
* @return
*/
public Boolean clearRoute() {
for(String id: ROUTE_LIST) {
routedefinitionWriter.delete(Mono.just(id)).subscribe();
}
ROUTE_LIST.clear();
return false;
}
/**
* 发布路由
*/
private void publisher(String config) {
clearRoute();
try {
log.info("重新更新动态路由");
List gateway = JSONObject.parseArray(config, RouteDefinition.class);
for(RouteDefinition route: gateway) {
addRoute(route);
}
publisher.publishEvent(new RefreshRoutesEvent(this.routedefinitionWriter));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher app) {
publisher = app;
}
}
3.2.2 nacos增加json配置文件 gateway-router.properties
[{
"id": "order",
"order": 1,
"predicates": [{
"args": {
"pattern": "/order/**"
},
"name": "Path"
}],
"uri": "lb://order"
},{
"id": "product",
"order": 2,
"predicates": [{
"args": {
"pattern": "/product/**"
},
"name": "Path"
}],
"uri": "lb://product"
},{
"id": "baidu",
"order": 3,
"predicates": [{
"args": {
"pattern": "/baidu/**"
},
"name": "Path"
}],
"uri": "http://www.baidu.com"
}]
这样就能在在不停止gateway服务的情况下,动态的修改gateway路由信息
4. 配置说明
gateway分为三大部分
route: 路由
predicate: 断言
filter: 过滤器
4.1 简介
spring:
cloud:
gateway:
routes: #路由属性
- id: after_route #id 唯一标识
uri: https://example.org #当配置到此路由后跳转路径
predicates: #断言 也就是判断条件 如果符合此条件,就跳转此路径
- Cookie=mycookie,mycookievalue #条件 predicates默认有11种
filters: #过滤器
- AddRequestHeader=X-Request-red, blue #过滤器也有31种 这个过滤器作用是经过的请求都会加上一个请求头
4.2 十一种predicates Factory
4.2.1 After
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
表示在2017-01-20T17:42:47.789-07:00[America/Denver]这个时间之后的所有请求都会被匹配到这个路由
4.2.2 Before
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
表示在2017-01-20T17:42:47.789-07:00[America/Denver]这个时间之前的所有请求都会被匹配到这个路由
4.2.3 Between
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-20T17:42:47.789-07:00[America/Denver]
表示在两个时间之间的所有请求都会被匹配到这个路由,第二个时间必须大于第一个时间
上面这三个predicate factory 中的时间都必须是dateTime(java8中的ZoneDateTime)
4.2.4 Cookie
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
表示请求中有一个cookie name是chocolate,值可以匹配到正则表达式[ch.p] 才会匹配到这个路由
4.2.5 Header
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
表示请求中有一个Headername是X-Request-Id,值可以匹配到正则表达式[\d+] 才会匹配到这个路由
4.2.6 Host
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
表示请求的host主机名如果是.somehost.org, **.anotherhost.org,则匹配此路由
4.2.7 Method
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST
请求如果是HTTP Get/Post请求则匹配此路由
4.2.8 Path
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
请求路径中能匹配到/red/{segment},/blue/{segment} 则匹配此路由
4.2.9 Query
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=green
请求中包含参数名为green,则会匹配此路由
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=red, gree.
同理,不光可以配置名字,如果两个参数,则第一个参数为参数名,第二个参数为参数值的正则表达式
4.2.10 RemoteAddr
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
如果请求中的remoteAddr匹配192.168.1.1/24,则匹配此路由
4.2.11 Weight
spring:
cloud:
gateway:
routes:
- id: weight_high
predicates:
- Weight=group1, 8
- id: weight_low
predicates:
- Weight=group1, 2
权重配置,20%的请求会被发送至weight_low,80%的请求会被发送至weight_heigh
4.2.12 总结
- 当一个路由中存在多个predicate时,所有的条件都必须满足才能匹配此路由
- 当一个请求满足多个路由的predicate的条件时,只有第一个满足条件的路由会生效
4.3 过滤器
4.3.1 AddRequestHeader
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue
顾名思义,把所有匹配到此路由的请求的请求头都添加一个Header X-Request-red:blue
4.3.2 AddRequestParameter
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
filters:
- AddRequestParameter=red, blue
顾名思义,把所有匹配到此路由的请求中都添加一个参数param red:blue
4.3.3 AddResponseHeader
spring:
cloud:
gateway:
routes:
- id: add_response_header_route
uri: https://example.org
filters:
- AddResponseHeader=X-Response-Red, Blue
把所有到此路由的请求的Response中增加响应头 X-Response-Red:Blue
4.3.4 DedupeResponseHeader
spring:
cloud:
gateway:
routes:
- id: dedupe_response_header_route
uri: https://example.org
filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
去重,去除经过此路由的请求中的响应头的Access-Control-Allow-Credentials 和 Access-Control-Allow-Origin的重复值
..................................官网有三十多种,官网地址..........................................................
4.4 自定义过滤器
4.4.1 自定义全局过滤器
全局过滤器不需要在配置文件中配置,因为所有的请求都要经过全局过滤器
一个简单的全局过滤器
package com.zy.more.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author: zhangyao
* @create:2020-07-16 14:38
**/
@Component
@Slf4j
public class AuthorGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("我的自定义全局过滤器启动");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
4.4.2 自定义过滤器
定义一个过滤器,并在配置文件中指定某个路由使用
- 定义一个实现了GatewayFilter接口的类
- 把这个类注入进GatewayFIlterFactory配置中
package com.zy.more.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author: zhangyao
* @create:2020-07-16 15:12
**/
@Component
@Slf4j
public class MySingleGatewayFilterFactory extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Object config) {
return new MyTestGatewayFilter();
}
public class MyTestGatewayFilter implements GatewayFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("111111111111111111");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
}
5.源码分析
在分析源码之前,猜测一下gateway的运行原理
- 请求先进入 Gateway Handler mappeing 中解析出对应的路由 Route 和 Predicates
- 再进入 Gateway Web Handler 中转发到不同的服务
- 在发送到不同的服务之前和之后都有对应的过滤器来对请求进行处理
以上都是猜测,进行源码分析
5.1 入口(初始化自动配置类)
查看gateway core包的Spring.factories文件
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
#用来验证项目中是否存在DispatcherServlet(SpringMvc使用,webFlux中不能存在) 是否不存在DispatcherHandler(WebFlux使用)
org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\
#核心配置类 配置gateway Route Filter 等相关bean
org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\
#负载均衡配置
org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\
#缓存相关配置
org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\
#服务发现相关配置 gateway支持从注册中心查询服务并生成对应路由
org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.gateway.config.GatewayEnvironmentPostProcessor
5.1.1 GatewayClassPathWarningAutoConfiguration
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.cloud.gateway.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Configuration;
@Configuration
@AutoConfigureBefore({GatewayAutoConfiguration.class})
public class GatewayClassPathWarningAutoConfiguration {
private static final Log log = LogFactory.getLog(GatewayClassPathWarningAutoConfiguration.class);
private static final String BORDER = "\n\n**********************************************************\n\n";
public GatewayClassPathWarningAutoConfiguration() {
}
@Configuration
@ConditionalOnMissingClass({"org.springframework.web.reactive.DispatcherHandler"})
protected static class WebfluxMissingFromClasspathConfiguration {
public WebfluxMissingFromClasspathConfiguration() {
GatewayClassPathWarningAutoConfiguration.log.warn("\n\n**********************************************************\n\nSpring Webflux is missing from the classpath, which is required for Spring Cloud Gateway at this time. Please add spring-boot-starter-webflux dependency.\n\n**********************************************************\n\n");
}
}
@Configuration
@ConditionalOnClass(
name = {"org.springframework.web.servlet.DispatcherServlet"}
)
protected static class SpringMvcFoundOnClasspathConfiguration {
public SpringMvcFoundOnClasspathConfiguration() {
GatewayClassPathWarningAutoConfiguration.log.warn("\n\n**********************************************************\n\nSpring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.\n\n**********************************************************\n\n");
}
}
}
在项目启动时监测是否有 org.springframework.web.servlet.DispatcherServlet 这个类是SpringMvc使用,不能在Gateway中使用,因为Gateway是基于WebFlux的,同理,项目中必须有 org.springframework.web.reactive.DispatcherHandler
5.1.2 GatewayAutoConfiguration
gateway核心配置类,在此配置类中注入了Gateway相关的路由,过滤器相关bean
5.1.2.1 Route的加载过程
5.1.2.1.1 PropertiesRouteDefinitionLocator
从配置文件中获取路由信息
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.cloud.gateway.config;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import reactor.core.publisher.Flux;
public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {
private final GatewayProperties properties;
public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
this.properties = properties;
}
public Flux getRouteDefinitions() {
//这里调用的GatewayProperties中的getRoutes方法,也就是读取了配置文件中的路由信息
return Flux.fromIterable(this.properties.getRoutes());
}
}
5.1.2.1.2 RouteDefinitionRouteLocator
路由定义类(RouteDefinition) 转换为路由类(Route)
private Route convertToRoute(RouteDefinition routeDefinition) {
AsyncPredicate predicate = this.combinePredicates(routeDefinition);
List gatewayFilters = this.getFilters(routeDefinition);
return ((AsyncBuilder)Route.async(routeDefinition).asyncPredicate(predicate).replaceFilters(gatewayFilters)).build();
}
5.1.2.1.3 CompositeRouteDefinitionLocator
这个类是对RouteDefinitionLocator的包装,就是从 PropertiesRouteDefinitionLocator(配置文件中读取) InMemoryRouteDefinitionRepository(内存中读取) CachingRouteDefinitionLocator(缓存中读取)
DiscoveryClientRouteDefinitionLocator(注册中心服务发现中获取路由)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.cloud.gateway.route;
import reactor.core.publisher.Flux;
public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator {
private final Flux delegates;
public CompositeRouteDefinitionLocator(Flux delegates) {
this.delegates = delegates;
}
public Flux getRouteDefinitions() {
//调用RouteDefinitionLocator的各个实现类的getRouteDefinitions获取路由定义
return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);
}
}
5.1.2.1.4 CachingRouteLocator
是对RouteLocator的封装,直接从内存中读取路由信息
public CachingRouteLocator(RouteLocator delegate) {
this.delegate = delegate;
this.routes = CacheFlux.lookup(this.cache, "routes", Route.class).onCacheMissResume(() -> {
return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);
});
}
public Flux getRoutes() {
return this.routes;
}
5.1.2.1.5 InMemoryRouteDefinitionRepository
默认可以从内存中获取路由,或者可以自己实现 RouteDefinitionRepository,从数据库等媒介中读取路由信息
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.cloud.gateway.route;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.cloud.gateway.support.NotFoundException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
private final Map routes = Collections.synchronizedMap(new LinkedHashMap());
public InMemoryRouteDefinitionRepository() {
}
public Mono save(Mono route) {
return route.flatMap((r) -> {
this.routes.put(r.getId(), r);
return Mono.empty();
});
}
public Mono delete(Mono routeId) {
return routeId.flatMap((id) -> {
if (this.routes.containsKey(id)) {
this.routes.remove(id);
return Mono.empty();
} else {
return Mono.defer(() -> {
return Mono.error(new NotFoundException("RouteDefinition not found: " + routeId));
});
}
});
}
public Flux getRouteDefinitions() {
return Flux.fromIterable(this.routes.values());
}
}
5.1.3 GatewayLoadBalancerClientAutoConfiguration
负载均衡过滤器配置
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.cloud.gateway.config;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;
@Configuration
@ConditionalOnClass({LoadBalancerClient.class, RibbonAutoConfiguration.class, DispatcherHandler.class})
@AutoConfigureAfter({RibbonAutoConfiguration.class})
@EnableConfigurationProperties({LoadBalancerProperties.class})
public class GatewayLoadBalancerClientAutoConfiguration {
public GatewayLoadBalancerClientAutoConfiguration() {
}
@Bean
@ConditionalOnBean({LoadBalancerClient.class})
@ConditionalOnMissingBean({LoadBalancerClientFilter.class})
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
return new LoadBalancerClientFilter(client, properties);
}
}
注入LoadBalancerClientFilter 过滤器,引入RibbonLoadBalanceClient (gateway也默认引入了Ribbon)
点进去看下 LoadBalancerClientFilter 的作用
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
log.trace("LoadBalancerClientFilter url before: " + url);
ServiceInstance instance = this.choose(exchange);
if (instance == null) {
String msg = "Unable to find instance for " + url.getHost();
if (this.properties.isUse404()) {
throw new LoadBalancerClientFilter.FourOFourNotFoundException(msg);
} else {
throw new NotFoundException(msg);
}
} else {
URI uri = exchange.getRequest().getURI();
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
URI requestUrl = this.loadBalancer.reconstructURI(new LoadBalancerClientFilter.DelegatingServiceInstance(instance, overrideScheme), uri);
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
} else {
return chain.filter(exchange);
}
}
这个过滤器的作用就是判断路由的uri是不是包含 lb:开头
如果是,就使用ribbon负载均衡去转发请求
5.1.4 GatewayMetricsAutoConfiguration
注入监控相关filter
5.1.5 GatewayRedisAutoConfiguration
缓存注入
5.1.6 GatewayDiscoveryClientAutoConfiguration
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.cloud.gateway.discovery;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;
@Configuration
@ConditionalOnProperty(
name = {"spring.cloud.gateway.enabled"},
matchIfMissing = true
)
@AutoConfigureBefore({GatewayAutoConfiguration.class})
@AutoConfigureAfter({CompositeDiscoveryClientAutoConfiguration.class})
@ConditionalOnClass({DispatcherHandler.class, DiscoveryClient.class})
@EnableConfigurationProperties
public class GatewayDiscoveryClientAutoConfiguration {
public GatewayDiscoveryClientAutoConfiguration() {
}
@Bean
@ConditionalOnBean({DiscoveryClient.class})
@ConditionalOnProperty(
name = {"spring.cloud.gateway.discovery.locator.enabled"}
)
public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
}
@Bean
public DiscoveryLocatorProperties discoveryLocatorProperties() {
DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
properties.setPredicates(initPredicates());
properties.setFilters(initFilters());
return properties;
}
public static List initPredicates() {
ArrayList definitions = new ArrayList();
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(NameUtils.normalizeRoutePredicateName(PathRoutePredicateFactory.class));
predicate.addArg("pattern", "'/'+serviceId+'/**'");
definitions.add(predicate);
return definitions;
}
public static List initFilters() {
ArrayList definitions = new ArrayList();
FilterDefinition filter = new FilterDefinition();
filter.setName(NameUtils.normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "'/' + serviceId + '/(?.*)'";
String replacement = "'/${remaining}'";
filter.addArg("regexp", regex);
filter.addArg("replacement", replacement);
definitions.add(filter);
return definitions;
}
}
当接入有注册中心时,并在配置文件中开启 spring.cloud.gateway.enabled 后,gateway会从注册中心中查询所有服务,并创建对应的路由,默认断言是 PathRoutePredicateFactory "pattern", "'/'+serviceId+'/**'"
5.2 请求处理
5.2.1 DispatcherHandler 请求分发
服务启动,首先进入 DispatcherHandler (类似springmvc的DispatcherServlet) 的 initStrategies 方法,初始化所有的HandlerMapping放入上下文
当有请求进入网关时,同样也是进入DispatcherHandler,DispatcherHandler执行handle方法
protected void initStrategies(ApplicationContext context) {
//获取所有HandlerMapping接口的实现类
Map mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
//转换成List
ArrayList mappings = new ArrayList(mappingBeans.values());
//排序(Order接口)
AnnotationAwareOrderComparator.sort(mappings);
//将转换后的Mappings转换为一个不能修改,只读的List
this.handlerMappings = Collections.unmodifiableList(mappings);
//获取所有HandlerAdapter的实现类
Map adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
//转换为list
this.handlerAdapters = new ArrayList(adapterBeans.values());
//排序
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
//获取所有HandlerResultHandler的实现类
Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerResultHandler.class, true, false);
//转为list
this.resultHandlers = new ArrayList(beans.values());
//排序
AnnotationAwareOrderComparator.sort(this.resultHandlers);
}
//请求的处理方法
public Mono handle(ServerWebExchange exchange) {
//如果初始化的方法里handlerMappings不为空,就执行handlerMapping的getHandler方法
return this.handlerMappings == null ? this.createNotFoundError() : Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {
return mapping.getHandler(exchange);
}).next().switchIfEmpty(this.createNotFoundError()).flatMap((handler) -> {
return this.invokeHandler(exchange, handler);
}).flatMap((result) -> {
return this.handleResult(exchange, result);
});
}
进入handlerMapping实现类AbstractHandlerMapping的getHandler方法
这个方法是用来获取所有的handler的
public Mono getHandler(ServerWebExchange exchange) {
//函数式编程获取getHandlerInternal
return this.getHandlerInternal(exchange).map((handler) -> {
if (this.logger.isDebugEnabled()) {
this.logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
}
if (CorsUtils.isCorsRequest(exchange.getRequest())) {
CorsConfiguration configA = this.corsConfigurationSource.getCorsConfiguration(exchange);
CorsConfiguration configB = this.getCorsConfiguration(handler, exchange);
CorsConfiguration config = configA != null ? configA.combine(configB) : configB;
if (!this.getCorsProcessor().process(config, exchange) || CorsUtils.isPreFlightRequest(exchange.getRequest())) {
return REQUEST_HANDLED_HANDLER;
}
}
return handler;
});
}
//抽象方法 由每个继承类定义具体的实现
protected abstract Mono getHandlerInternal(ServerWebExchange var1);
5.2.2 RoutePredicateHandlerMapping 获取路由
再看 getHandlerInternal 方法
进入AbstractHandlerMapping的实现类 RoutePredicateHandlerMapping
这个类是通过断言来过滤符合条件的路由,并转发到处理类
protected Mono getHandlerInternal(ServerWebExchange exchange) {
//判断请求的端口是否是健康检查端口
if (this.managmentPort != null && exchange.getRequest().getURI().getPort() == this.managmentPort) {
return Mono.empty();
} else {
//把RoutePredicateHandlerMapping这个handlerMaping放入exchange上下文中
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR, this.getSimpleName());
//lookupRoute过滤出符合处理请求的路由
return this.lookupRoute(exchange).flatMap((r) -> {
exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Mapping [" + this.getExchangeDesc(exchange) + "] to " + r);
}
//把符合条件的路由放入exchange上下文中
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, r);
//交给webHandler处理
return Mono.just(this.webHandler);
}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
if (this.logger.isTraceEnabled()) {
this.logger.trace("No RouteDefinition found for [" + this.getExchangeDesc(exchange) + "]");
}
})));
}
}
5.2.3 FilteringWebHandler 过滤
上文中获取到路由之后,就需要对请求进行过滤处理
除了全局过滤器外,还要获取路由中定义的网关过滤器
//构造函数中对全局过滤器进行处理
public FilteringWebHandler(List globalFilters) {
this.globalFilters = loadFilters(globalFilters);
}
//处理全局过滤器
private static List loadFilters(List filters) {
//对GlobalFilter进行包装 如果实现了Ordered接口(有排序),就包装成OrderedGatewayFilter 如果没有实现排序接口,就用gatewayFilter包装
return (List)filters.stream().map((filter) -> {
FilteringWebHandler.GatewayFilterAdapter gatewayFilter = new FilteringWebHandler.GatewayFilterAdapter(filter);
if (filter instanceof Ordered) {
int order = ((Ordered)filter).getOrder();
return new OrderedGatewayFilter(gatewayFilter, order);
} else {
return gatewayFilter;
}
}).collect(Collectors.toList());
}
//过滤链处理
public Mono handle(ServerWebExchange exchange) {
//获取exchange上下文中的route路由信息
Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
//获取路由中的过滤器
List gatewayFilters = route.getFilters();
//全局过滤器
List combined = new ArrayList(this.globalFilters);
//全局过滤器加上路由中的过滤器
combined.addAll(gatewayFilters);
//排序
AnnotationAwareOrderComparator.sort(combined);
if (logger.isDebugEnabled()) {
logger.debug("Sorted gatewayFilterFactories: " + combined);
}
//递归调用此类中的内部类DefaultGatewayFilterChain过滤器链进行过滤处理
return (new FilteringWebHandler.DefaultGatewayFilterChain(combined)).filter(exchange);
}
递归调用
private static class DefaultGatewayFilterChain implements GatewayFilterChain {
private final int index;
private final List filters;
public DefaultGatewayFilterChain(List filters) {
this.filters = filters;
this.index = 0;
}
private DefaultGatewayFilterChain(FilteringWebHandler.DefaultGatewayFilterChain parent, int index) {
this.filters = parent.getFilters();
this.index = index;
}
public List getFilters() {
return this.filters;
}
public Mono filter(ServerWebExchange exchange) {
return Mono.defer(() -> {
if (this.index < this.filters.size()) {
GatewayFilter filter = (GatewayFilter)this.filters.get(this.index);
FilteringWebHandler.DefaultGatewayFilterChain chain = new FilteringWebHandler.DefaultGatewayFilterChain(this, this.index + 1);
return filter.filter(exchange, chain);
} else {
return Mono.empty();
}
});
}
}
5.2.4 转发请求
转发请求由两个全局过滤器实现
NettyRoutingFilter 和 LoadBalancerClientFilter
5.2.4.1 LoadBalancerClientFilter
如果路由的uri是lb开头,就会进入负载均衡从注册中心获取地址
否则进入下一个过滤器
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
log.trace("LoadBalancerClientFilter url before: " + url);
ServiceInstance instance = this.choose(exchange);
if (instance == null) {
String msg = "Unable to find instance for " + url.getHost();
if (this.properties.isUse404()) {
throw new LoadBalancerClientFilter.FourOFourNotFoundException(msg);
} else {
throw new NotFoundException(msg);
}
} else {
URI uri = exchange.getRequest().getURI();
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
URI requestUrl = this.loadBalancer.reconstructURI(new LoadBalancerClientFilter.DelegatingServiceInstance(instance, overrideScheme), uri);
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
} else {
return chain.filter(exchange);
}
}
5.2.4.2 NettyRoutingFilter
如果是http开头或者https开头就通过这个过滤器来转发请求
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI requestUrl = (URI)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
if (!ServerWebExchangeUtils.isAlreadyRouted(exchange) && ("http".equals(scheme) || "https".equals(scheme))) {
ServerWebExchangeUtils.setAlreadyRouted(exchange);
ServerHttpRequest request = exchange.getRequest();
HttpMethod method = HttpMethod.valueOf(request.getMethodValue());
String url = requestUrl.toString();
HttpHeaders filtered = HttpHeadersFilter.filterRequest((List)this.headersFilters.getIfAvailable(), exchange);
DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
filtered.forEach(httpHeaders::set);
String transferEncoding = request.getHeaders().getFirst("Transfer-Encoding");
boolean chunkedTransfer = "chunked".equalsIgnoreCase(transferEncoding);
boolean preserveHost = (Boolean)exchange.getAttributeOrDefault(ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE, false);
Flux responseFlux = ((RequestSender)this.httpClient.chunkedTransfer(chunkedTransfer).request(method).uri(url)).send((req, nettyOutbound) -> {
req.headers(httpHeaders);
if (preserveHost) {
String host = request.getHeaders().getFirst("Host");
req.header("Host", host);
}
return nettyOutbound.options(SendOptions::flushOnEach).send(request.getBody().map((dataBuffer) -> {
return ((NettyDataBuffer)dataBuffer).getNativeBuffer();
}));
}).responseConnection((res, connection) -> {
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = new HttpHeaders();
res.responseHeaders().forEach((entry) -> {
headers.add((String)entry.getKey(), (String)entry.getValue());
});
String contentTypeValue = headers.getFirst("Content-Type");
if (StringUtils.hasLength(contentTypeValue)) {
exchange.getAttributes().put("original_response_content_type", contentTypeValue);
}
HttpStatus status = HttpStatus.resolve(res.status().code());
if (status != null) {
response.setStatusCode(status);
} else {
if (!(response instanceof AbstractServerHttpResponse)) {
throw new IllegalStateException("Unable to set status code on response: " + res.status().code() + ", " + response.getClass());
}
((AbstractServerHttpResponse)response).setStatusCodeValue(res.status().code());
}
HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter((List)this.headersFilters.getIfAvailable(), headers, exchange, Type.RESPONSE);
if (!filteredResponseHeaders.containsKey("Transfer-Encoding") && filteredResponseHeaders.containsKey("Content-Length")) {
response.getHeaders().remove("Transfer-Encoding");
}
exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet());
response.getHeaders().putAll(filteredResponseHeaders);
exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR, res);
exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR, connection);
return Mono.just(res);
});
if (this.properties.getResponseTimeout() != null) {
responseFlux = responseFlux.timeout(this.properties.getResponseTimeout(), Mono.error(new TimeoutException("Response took longer than timeout: " + this.properties.getResponseTimeout()))).onErrorMap(TimeoutException.class, (th) -> {
return new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, (String)null, th);
});
}
return responseFlux.then(chain.filter(exchange));
} else {
return chain.filter(exchange);
}
}