全网首发:Spring Cloud Gateway设置统一的请求前缀
前言最近由于项目中要引入websocket,而原来的zuul网关对websocket支持并不友好,所以将原先的zuul网关切换成了Gateway网关。踩了不少坑,其中的一个问题就是如何给Spring Cloud Gateway添加统一的请求前缀。在zuul网关中我们可以直接指定server.servlet.context-path属性,但是Gateway网关我们要如何配置呢?一、Spring Cl
前言
最近由于项目中要引入websocket,而原来的zuul网关对websocket支持并不友好,所以将原先的zuul网关切换成了Gateway网关。踩了不少坑,其中的一个问题就是如何给Spring Cloud Gateway添加统一的请求前缀。
在zuul网关中我们可以直接指定server.servlet.context-path属性,但是Gateway网关我们要如何配置呢?
一、Spring Cloud Gateway工作原理说明
这是官网的工作原理示意图:
Gateway的工作原理:
客户端请求经过HandlerMapping的处理,先进行路由匹配,如果匹配到路由(Router)就交给网关的web处理程序(Gateway Web Handler)来处理,经过一系列的调用过滤器链(肯定有责任链模式)后转发到被代理的服务执行真正的调用逻辑。
结合Gateway内部控制说明:
根据Predicate进行请求路径的匹配。
根据Filter对匹配的请求进行逻辑处理。
二、分析Gateway整合注册中心的原理
首先根据配置属性spring.cloud.gateway.discovery.locator.enabled定位到GatewayDiscoveryClientAutoConfiguration自动化配置类。
查看源码发现,核心是通过initPredicates和initFilters分别初始化定义了一个Predicate和Filter。
其中定义的PathRoutePredicate主要根据serviceId去进行请求的路径匹配,只有路径规则匹配成功后,才会进入Filter的逻辑处理。
initFilters方法中主要是定义了一个RewritePathGatewayFilter,主要是将发到具体服务中的请求的url路径进行重写,去除掉前缀
“/serviceId”
public static List<PredicateDefinition> initPredicates() {
ArrayList<PredicateDefinition> 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<FilterDefinition> initFilters() {
ArrayList<FilterDefinition> definitions = new ArrayList();
FilterDefinition filter = new FilterDefinition();
filter.setName(NameUtils.normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "'/' + serviceId + '/(?<remaining>.*)'";
String replacement = "'/${remaining}'";
filter.addArg("regexp", regex);
filter.addArg("replacement", replacement);
definitions.add(filter);
return definitions;
}
@Bean
public DiscoveryLocatorProperties discoveryLocatorProperties() {
DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
properties.setPredicates(initPredicates());
properties.setFilters(initFilters());
return properties;
}
三、方案确定
重写定义GatewayDiscoveryClientAutoConfiguration自动化配置类,改写原先的Predicate和Filter,添加统一的请求前缀“/api/v1”。
@Slf4j
@Configuration
public class GatewayDiscoveryClientConfig<main> extends GatewayDiscoveryClientAutoConfiguration {
@Value("${spring.cloud.gateway.api-prefix:/api/v1}")
private String prefix;
@Bean
@Override
public DiscoveryLocatorProperties discoveryLocatorProperties() {
DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
properties.setPredicates(myInitPredicates());
properties.setFilters(myInitFilters());
return properties;
}
public List<PredicateDefinition> myInitPredicates() {
ArrayList<PredicateDefinition> definitions = new ArrayList();
PredicateDefinition predicate = new PredicateDefinition();
//定义路由路径的匹配规则
predicate.setName(NameUtils.normalizeRoutePredicateName(PathRoutePredicateFactory.class));
String pattern ="'"+prefix+"/'+serviceId+'/**'";
predicate.addArg("pattern", pattern);
definitions.add(predicate);
return definitions;
}
public List<FilterDefinition> myInitFilters() {
ArrayList<FilterDefinition> definitions = new ArrayList();
FilterDefinition filter = new FilterDefinition();
//重新请求路径
filter.setName(NameUtils.normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "'"+prefix+"/' + serviceId + '/(?<remaining>.*)'";
String replacement = "'/${remaining}'";
filter.addArg("regexp", regex);
filter.addArg("replacement", replacement);
definitions.add(filter);
return definitions;
}
}
四、完整项目实战
1、maven依赖
spring-boot-starter-parent版本2.3.4.RELEASE
spring cloud版本Hoxton.SR8
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
2、属性配置
server.port=8020
spring.application.name=gateway
spring.cloud.gateway.api-prefix=/api/v1
#配置网关的默认路由
spring.cloud.gateway.enabled=true
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
#注册中心
eureka.client.service-url.defaultZone=http://register:8001/eureka/
eureka.instance.prefer-ip-address=true
eureka.instance.hostname=${spring.cloud.client.ip-address}
eureka.instance.instance-id=${eureka.instance.hostname}:${server.port}
3、代码
3.1 启动类代码
注意:
既然要自定义GatewayDiscovery配置类,就要在启动类中exclude对应的自动化配置类GatewayDiscoveryClientAutoConfiguration
@SpringBootApplication(exclude = {
GatewayDiscoveryClientAutoConfiguration.class
})
@EnableDiscoveryClient
@Slf4j
public class WxswjGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(WxswjGatewayApplication.class, args);
log.info("网关服务启动成功!");
}
}
3.2 自定义网关注册中心配置
/**
* @program: wxswj
* @description: 自定义网关注册中心配置
* @create: 2020-10-14 20:11
**/
@Slf4j
@Configuration
public class GatewayDiscoveryClientConfig<main> extends GatewayDiscoveryClientAutoConfiguration {
@Value("${spring.cloud.gateway.api-prefix:/api/v1}")
private String prefix;
@Bean
@Override
public DiscoveryLocatorProperties discoveryLocatorProperties() {
DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
properties.setPredicates(myInitPredicates());
properties.setFilters(myInitFilters());
return properties;
}
public List<PredicateDefinition> myInitPredicates() {
ArrayList<PredicateDefinition> definitions = new ArrayList();
PredicateDefinition predicate = new PredicateDefinition();
//定义路由路径的匹配规则
predicate.setName(NameUtils.normalizeRoutePredicateName(PathRoutePredicateFactory.class));
String pattern ="'"+prefix+"/'+serviceId+'/**'";
predicate.addArg("pattern", pattern);
definitions.add(predicate);
return definitions;
}
public List<FilterDefinition> myInitFilters() {
ArrayList<FilterDefinition> definitions = new ArrayList();
FilterDefinition filter = new FilterDefinition();
//重新请求路径
filter.setName(NameUtils.normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "'"+prefix+"/' + serviceId + '/(?<remaining>.*)'";
String replacement = "'/${remaining}'";
filter.addArg("regexp", regex);
filter.addArg("replacement", replacement);
definitions.add(filter);
return definitions;
}
}
总结
本文首先介绍了Spring Cloud Gateway的工作原理,然后通过跟踪源码解读了gateway和注册中心集成的实现原理,推断出Spring Cloud Gateway设置统一的请求前缀的实现方案。
最后给出了完整的实现代码。
希望这些问题解决的分析过程和实现思路能对大家有所帮助。
更多精彩,关注我吧。
更多推荐
所有评论(0)