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依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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) 创建路由定义模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 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) 创建过滤器定义模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 过滤器定义模型
* @author yezhou
*/
@Data
public class GatewayFilterDefinition {

/**
* Filter Name
*/
private String name;

/**
* 对应的路由规则
*/
private Map<String, String> args = new LinkedHashMap<>();

}

(3) 路由断言定义模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 路由断言定义模型
* @author yezhou
*/
@Data
public class GatewayPredicateDefinition {

/**
* 断言对应的Name
*/
private String name;

/**
* 配置的断言规则
*/
private Map<String, String> args = new LinkedHashMap<>();

}

编写动态路由实现类

编写DynamicRouteServiceImpl并实现ApplicationEventPublisherAware接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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接口,用于动态路由配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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增加路由配置成功

1
2
3
4
5
6
7
8
9
10
11
12
{
"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的路由更新配置:

1
2
3
4
5
6
7
8
9
10
11
12
{
"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,删除路由

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2021 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :