Spring Cloud Gateway入门

Spring Cloud Gateway入门

Spring Cloud Gateway是Spring官方基于Spring5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效且统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅仅提供统一的路由方式,并且还基于Filter链的方式提供了网关基本功能,例如:安全、监控/埋点、限流等。

Spring Cloud Gateway中有3个比较重要的概念:

  • 路由:路由是网关最基础的部分,路由信息由一个ID、一个目的URL、一组断言工程和一组Filter组成。如果路由断言为真,则说明请求的url和配置的路由匹配
  • 断言:Java 8 中的断言函数。Spring Cloud Gateway中的断言函数输入类型是Spring 5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于HTTP Request中的任何信息,比如请求头和参数等。
  • 过滤器:一个标准的Spring webFilter。Spring Cloud Gateway中的Filter分为两种类型的Filter, 分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理。

Spring Cloud Gateway的核心处理流程如下图所示,Gateway的客户端会向Spring Cloud Gateway发起请求,请求首先被HttpWebHandlerAdapter进行提取组装成网关的上下文,然后网关的上下文会传递到DispatcherHandler。DispatcherHandler是所有请求的分发处理器,DispatcherHandler主要负责分发请求对应的处理器,比如将请求分发到对应RoutePredicateHandlerMapping(路由断言处理映射器)。路由断言处理映射器主要用于路由的查找,以及找到路由后返回对应的FilteringWebHandler。FilteringWebHandler主要负责组装Filter链表并调用Filter执行一系列的Filter处理,然后把请求转到后端对应的代理服务器处理,处理完毕之后,将Response返回到Gateway客户端。

简单的Gateway例子

新建一个maven项目first-gateway,其pom依赖为:

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
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>

然后新建一个启动类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route(r -> r.path("/jd").uri("http://jd.com:80/").id("jd_route")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}

}

上图自定义了一个路由转发规则,将访问/jd的请求,定向到http://jd.com:80上

路由断言

Spring Cloud Gateway的路由匹配功能时以Spring WebFlux中的Handler Mapping为基础实现的。Spring Cloud Gateway也是由许多的路由断言工厂组成的。当Http Request请求进入Spring Cloud Gateway的时候,网关中的路由断言工厂会根据配置的路由规则,对Http Request请求进行断言匹配。匹配成功则进行下一步处理,否则断言失败直接返回错误信息。

After路由断言工厂

After Route Predicate Factory中会取一个UTC时间格式的参数,当请求进来的当前时间再配置的UTC时间之后,则会成功匹配,否则不能成功匹配。

将上个例子中的启动类改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
ZonedDateTime plusTime = LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault());
ZonedDateTime minusTime = LocalDateTime.now().minusHours(1).atZone((ZoneId.systemDefault()));
return builder.routes().route("after_route", r -> r.after(minusTime).uri("http://baidu.com")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}

}

先在第8行的after中传入minusTIme,表示,在minusTime之后进来的所有请求都可以通过。而minusTime就是当前时间往前移动一个小时。

在浏览器中访问http://localhost:8080,就可以看到请求被定向到baidu.com

而如果把minusTime改为plusTime,意为在当前时间一个小时候进来的请求才能允许通过。在浏览器中再次访问http://localhost:8080,就可以看到请求被拒绝了。

Before路由断言工厂

Before路由断言工厂会取一个UTC时间格式的时间参数,当请求进来的当前时间再路由断言工厂之前会成功匹配,否则不能成功匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
ZonedDateTime plusTime = LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault());
ZonedDateTime minusTime = LocalDateTime.now().minusHours(1).atZone((ZoneId.systemDefault()));
return builder.routes().route("before_route", r -> r.before(minusTime).uri("https://baidu.com")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

首先在befor方法中传入minusTime,表示在一小时之前的请求,才能通过,接着传入plusTime,表示只要在当前时间后一个小时前的所有请求都可以通过。

Between路由断言工厂

Between路由断言工厂会取两个UTC时间格式的时间参数,当请求进来的当前时间在配置的UTC时间工厂之间会成功匹配,否则不能成功匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
ZonedDateTime plusTime = LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault());
ZonedDateTime minusTime = LocalDateTime.now().minusHours(1).atZone((ZoneId.systemDefault()));
return builder.routes().route("between_route", r -> r.between(minusTime, plusTime).uri("https://baidu.com")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

Cookie路由断言工厂

Cookie路由断言工厂会取两个参数-cookie名称对应的key和value。当请求中携带的cooke和Cookied断言工厂配置的cookie一致,则路由匹配成功,否则匹配不成功。

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("cookie_route", r -> r.cookie("user_name", "test").uri("https://baidu.com")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

Header路由断言工厂

Header路由断言工厂用于根据配置的路由header信息进行断言匹配路由,匹配成功进行转发,否则不进行转发。

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("cookie_route", r -> r.header("X-Auth-Toke", "test").uri("https://baidu.com")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

Host路由断言工厂

Host路由断言工厂根据配置的Host,对请求中的Host进行断言处理,断言成功则进行路由转发,否则不转发。

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("cookie_route", r -> r.host("*.localhost").uri("https://baidu.com")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

Method路由断言工厂

Method路由断言工厂会根据路由信息配置的method对请求方法是Get或者Post等进行断言匹配,匹配成功则进行转发,否则处理失败。

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("cookie_route", r -> r.method("GET").uri("https://baidu.com")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

Query路由断言工厂

Query路由断言工厂会从请求中获取两个参数,将请求中参数和Query断言路由中的配置进行匹配。

比如 http://localhost:8080?foo=bar中的foo=bar和r.query(“foo”, “bar”)配置一致则转发成功,否则转发失败。

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("cookie_route", r -> r.query("user_name", "test").uri("https://baidu.com")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

RemoteAddr路由断言工厂

RemoteAddr路由断言工厂配置一个IPv4或IPv6网段的字符串或者IP。当请求IP地址在网段之内或者和配置的IP相同,则表示成功匹配,成功转发,否则不能转发.

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("cookie_route", r -> r.remoteAddr("127.0.0.1").uri("https://baidu.com")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

内置Filter

Spring Cloud Gateway中内置很多的路由过滤工厂,也可以根据实际应用场景定制自己的路由过滤器工厂。路由过滤器允许以某种方式修改请求进来的http请求或返回http响应。路由过滤器主要作用域需要处理的特定路由。Spring Cloud Gateway提供了很多种类的过滤器工厂,过滤器的实现类将近二十多个。总的来说,可以分为七类:Header、Parameter、Path、Status、Redirect跳转。Hystrix熔断和RateLimiter。

AddRequestHeader过滤器工厂

AddRequestHeader过滤器工厂用于对匹配上的请求加上header。

1
2
3
4
5
6
7
8
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("add_request_head_route", r -> r.path("/test").filters(f -> f.addRequestHeader("X-Auth-Token", "Token")).uri("https://baidu.com")).build();
}
}

AddRequestParameter过滤器

AddRequestParameter过滤器作用是对匹配上的请求路由添加请求参数。

1
2
3
4
5
6
7
8
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("add_request_parameter_route", r -> r.path("/test").filters(f -> f.addRequestParameter("foo", "bar")).uri("https://baidu.com")).build();
}
}

RewritePath过滤器

Spring Cloud Gateway可以使用RewritePath替换Zuul的StripPrefix功能,而且功能更 强大。在Zuul中使用如下配置:

1
2
3
4
5
6
7
zuul:
routes:
demo:
sensitiveHeaders: Access-Control-Allow-Origin, Access-Contol-Allow-Methods
path: /demo/**
stripPrefix: true
url: http://demo.com

说明:这里的stripPrefix默认为true,也就是所有的/demo/xxx的请求转发给http://demo.com/xxxx,去除demo前缀。

Spring Cloud Gateway实现类似的功能,使用的是RewritePath过滤器工厂。

1
2
3
4
5
6
7
8
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("add_request_head_route", r -> r.path("/test/**").filters(f -> f.rewirtePath("/foo/(?<segment>.*)", "/$\\{segment}")).uri("https://baidu.com")).build();
}
}

AddResponseHeader过滤器

AddResponseHeader过滤器工厂的作用是对从网关返回的响应添加Header。

1
2
3
4
5
6
7
8
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("add_request_head_route", r -> r.path("/test").filters(f -> f.addResponseHeader("X-Auth-Token", "Token")).uri("https://baidu.com")).build();
}
}

StripPrefix过滤器

StripPrefix过滤器工厂是一个对针对请求url前缀进行处理的filter工厂,用于去除前缀。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("between_route", r -> r.path("/1/2/test/*").filters(f -> f.stripPrefix(2)).uri("https://baidu.com")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}

}

stripPrefix(int)的入参是一个整型,表示要剥离的前缀层数。比如,/1/2/test会被剥离成/test

Retry过滤器

网关作为所有请求流量的入口,网关对路由进行协议适配和协议转发处理的过程中,如果出现异常或网络抖动,为了保证后端服务请求的高可用,一般处理方式会对网络请求进行重试。

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
public class GatewayApplication {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("retry_route", r -> r.path("/1/2/test/*").filters(f -> f.retry(2).setStatus(HttpStatus.INTERNAL_SERVER_ERROR)).uri("https://baidu.com")).build();
}

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}

}

上面代码表示设置重试次数为两次,当代理服务调用失败时设置返回的状态码为500.

参考资料

疯狂Spring Cloud微服务架构实战

0%