Spring Cloud Gateway的动态路由实现
前言
网关中有两个重要的概念,那就是路由配置和路由规则,路由配置是指配置某请求路径路由到指定的目的地址。而路由规则是指匹配到路由配置之后,再根据路由规则进行转发处理。
Spring Cloud Gateway作为所有请求流量的入口,在实际生产环境中为了保证高可靠和高可用,尽量避免重启,需要实现Spring Cloud Gateway动态路由配置。Spring Cloud Gateway提供的两种方法去配置路由规则,但都是在Spring Cloud Gateway启动时,就将路由配置和规则加载到内存里,无法做到不重启网关就可以动态的对应路由的配置和规则进行增加,修改和删除。
Spring Cloud Gateway简单的动态路由实现
Spring Cloud Gateway的官方文档并没有讲如何动态配置,查看 Spring Cloud Gateway的源码,发现在org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint
类中提供了动态配置的Rest接口,但是需要开启Gateway的端点,而且提供的功能不是很强大。通过参考和GatewayControllerEndpoint相关的代码,可以自己编码实际动态路由配置。
简单动态路由的实现
Maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
根据Spring Cloud Gateway的路由模型定义数据传输模型
分别创建GatewayRouteDefinition.java
, GatewayPredicateDefinition.java
, GatewayFilterDefinition.java
这三个类。
(1) 创建路由定义模型
/**
* Gateway的路由定义模型
* @author yezhou
*/
@Data
public class GatewayRouteDefinition {
/**
* 路由的Id
*/
private String id;
/**
* 路由断言集合配置
*/
private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
/**
* 路由过滤器集合配置
*/
private List<GatewayFilterDefinition> filters = new ArrayList<>();
/**
* 路由规则转发的目标uri
*/
private String uri;
/**
* 路由执行的顺序
*/
private int order = 0;
}
(2) 创建过滤器定义模型
/**
* 过滤器定义模型
* @author yezhou
*/
@Data
public class GatewayFilterDefinition {
/**
* Filter Name
*/
private String name;
/**
* 对应的路由规则
*/
private Map<String, String> args = new LinkedHashMap<>();
}
(3) 路由断言定义模型
/**
* 路由断言定义模型
* @author yezhou
*/
@Data
public class GatewayPredicateDefinition {
/**
* 断言对应的Name
*/
private String name;
/**
* 配置的断言规则
*/
private Map<String, String> args = new LinkedHashMap<>();
}
编写动态路由实现类
编写DynamicRouteServiceImpl并实现ApplicationEventPublisherAware接口
import com.alibaba.fastjson.JSON;
import me.yezhou.spring.cloud.gateway.model.GatewayPredicateDefinition;
import me.yezhou.spring.cloud.gateway.model.GatewayRouteDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
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 org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @author yezhou
*/
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
/**
* 增加路由
*
* @param definition
* @return
*/
public String add(RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
/**
* 更新路由
*
* @param definition
* @return
*/
public String update(RouteDefinition definition) {
try {
this.routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe();
} catch (Exception e) {
return "update fail,not find route routeId: " + definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception e) {
return "update route fail";
}
}
/**
* 删除路由
*
* @param id
* @return
*/
public String delete(String id) {
System.out.println(id);
try {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
return "delete success";
} catch (Exception e) {
e.printStackTrace();
return "delete fail";
}
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
/**
* spring:
* cloud:
* gateway:
* routes: #当访问http://localhost:8080/baidu,直接转发到https://www.baidu.com/
* - id: baidu_route
* uri: http://baidu.com:80/
* predicates:
* - Path=/baidu
*
* @param args
*/
public static void main(String[] args) {
GatewayRouteDefinition definition = new GatewayRouteDefinition();
GatewayPredicateDefinition predicate = new GatewayPredicateDefinition();
Map<String, String> predicateParams = new HashMap<>(8);
definition.setId("jd_route");
predicate.setName("Path");
predicateParams.put("pattern", "/jd");
predicate.setArgs(predicateParams);
definition.setPredicates(Arrays.asList(predicate));
String uri = "http://www.jd.com";
//URI uri = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com").build().toUri();
definition.setUri(uri);
System.out.println("definition:" + JSON.toJSONString(definition));
RouteDefinition definition1 = new RouteDefinition();
PredicateDefinition predicate1 = new PredicateDefinition();
Map<String, String> predicateParams1 = new HashMap<>(8);
definition1.setId("baidu_route");
predicate1.setName("Path");
predicateParams1.put("pattern", "/baidu");
predicate1.setArgs(predicateParams1);
definition1.setPredicates(Arrays.asList(predicate1));
URI uri1 = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com").build().toUri();
definition1.setUri(uri1);
System.out.println("definition1:" + JSON.toJSONString(definition1));
}
}
编写Rest接口
编写RouteController类的提供Rest接口,用于动态路由配置。
import me.yezhou.spring.cloud.gateway.model.GatewayFilterDefinition;
import me.yezhou.spring.cloud.gateway.model.GatewayPredicateDefinition;
import me.yezhou.spring.cloud.gateway.model.GatewayRouteDefinition;
import me.yezhou.spring.cloud.gateway.service.DynamicRouteServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
/**
* @author yezhou
*/
@RestController
@RequestMapping("/route")
public class RouteController {
@Autowired
private DynamicRouteServiceImpl dynamicRouteService;
/**
* 增加路由
* @param gwdefinition
* @return
*/
@PostMapping("/add")
public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
try {
RouteDefinition definition = assembleRouteDefinition(gwdefinition);
return this.dynamicRouteService.add(definition);
} catch (Exception e) {
e.printStackTrace();
}
return "succss";
}
@GetMapping("/delete/{id}")
public String delete(@PathVariable String id) {
return this.dynamicRouteService.delete(id);
}
@PostMapping("/update")
public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = assembleRouteDefinition(gwdefinition);
return this.dynamicRouteService.update(definition);
}
private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = new RouteDefinition();
// ID
definition.setId(gwdefinition.getId());
// Predicates
List<PredicateDefinition> pdList = new ArrayList<>();
for (GatewayPredicateDefinition gpDefinition: gwdefinition.getPredicates()) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setArgs(gpDefinition.getArgs());
predicate.setName(gpDefinition.getName());
pdList.add(predicate);
}
definition.setPredicates(pdList);
// Filters
List<FilterDefinition> fdList = new ArrayList<>();
for (GatewayFilterDefinition gfDefinition: gwdefinition.getFilters()) {
FilterDefinition filter = new FilterDefinition();
filter.setArgs(gfDefinition.getArgs());
filter.setName(gfDefinition.getName());
fdList.add(filter);
}
definition.setFilters(fdList);
// URI
URI uri = UriComponentsBuilder.fromUriString(gwdefinition.getUri()).build().toUri();
definition.setUri(uri);
return definition;
}
}
配置application.yml文件
在application.yml文件配置应用的配置信息,并开启Spring Cloud Gateway对外提供的端点Rest接口。
server:
port: 8080
spring:
application:
name: ok-cloud-gateway
# 配置输出日志
logging:
level:
org.springframework.cloud.gateway: TRACE
org.springframework.http.server.reactive: DEBUG
org.springframework.web.reactive: DEBUG
reactor.ipc.netty: DEBUG
#开启端点
management:
endpoints:
web:
exposure:
include: '*'
security:
enabled: false
启动应用测试
(1) 启动应用之后,由于开启了端点,首先打开浏览器访问端点http://localhost:8080/actuator/gateway/routes
,查看路由信息返回为空
(2) 打开PostMan,访问http://localhost:8080/route/add
,发起Post请求,返回success说明向Gateway增加路由配置成功
{
"filter": [],
"id": "jd_route",
"order": 0,
"predicates": [{
"args": {
"pattern": "/jd"
},
"name": "Path"
}],
"uri": "http://www.jd.com"
}
然后再打开PostMan访问端点http://localhost:8080/actuator/gateway/routes
,查看路由信息返回,可以看到已经添加的路由配置
(3) 打开浏览器访问http://localhost:8080/jd
,可以正常转发https://www.jd.com/
对应的京东商城首页
(4) 通过访问http://localhost:8080/route/update
,对id为jd_route的路由更新配置:
{
"filter": [],
"id": "jd_route",
"order": 0,
"predicates": [{
"args": {
"pattern": "/taobao"
},
"name": "Path"
}],
"uri": "http://www.taobao.com"
}
然后再访问路由端点URL,发现路由配置已经被更新,然后通过浏览器访问http://localhost:8080/taobao
,可以成功转发到淘宝网
(5) 通过访问http://localhost:8080/route/delete/jd_route
,其中的id为路由对应的id,删除路由
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/04/01/dynamic-routing-implementation-of-spring-cloud-gateway/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论