Spring Cloud Gateway是Spring提供的API网关层,可以通过它代理请求,然后转发到真正的目标地址上。使用Spring Cloud Gateway需要加上spring-cloud-starter-gateway
和spring-boot-starter-webflux
依赖。
<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>
如果应用中加了
spring-cloud-starter-gateway
依赖,但是又不希望启用Gateway时可以指定spring.cloud.gateway.enabled=false
。
Spring Cloud Gateway的自动配置由org.springframework.cloud.gateway.config.GatewayAutoConfiguration
定义。现假设有一个服务对应的地址是http://localhost:8900
,我们的网关希望请求路径是/hello/**
时就路由到http://localhost:8900
,那我们就可以进行如下这样的配置。假设我们的网关部署的端口是9103,当访问http://localhost:9103/hello/abc
时就会被路由到http://localhost:8900/hello/abc
。
spring:
cloud:
gateway:
routes:
- id: route_id
uri: http://localhost:8900
predicates:
- Path=/hello/**
定义网关的路由信息时通常需要定义三个信息,id、uri和predicates。id表示这一条路由规则的标识,可以随便定义;uri表示当这一路由定义匹配到了后请求需要转发到的目标uri;predicates用来定义路由匹配的条件,可以有多个。上面的配置定义的路由匹配的条件是请求路径满足/hello/**
格式。匹配路由的条件(或者说是规则),除了通过请求路径进行匹配的Path外,还有很多,简单列举几个如下。
- Before:当前时间在指定的时间前匹配,时间格式是ISO格式。
Before=2019-01-20T17:42:47.789-07:00[America/Denver]
表示按照美国Denver时间在2019年1月20日17:42以前匹配。 - After:表示时间在指定的时间之后匹配。
- Between:表示时间在指定的时间之间时匹配。如
Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
。 - Header:通过头信息匹配。
Header=X-Request-Id, \d+
表示头信息中包含名为X-Request-Id
的头,且其值全部是数字组成(第二个参数表示值,且是正则表达式)。 - Host:通过host匹配。
Host=**.somehost.org
表示当请求的头信息中拥有一个名为Host的头信息,且其值为foo.somehost.org
或bar.somehost.org
等时匹配。 - Method:通过请求方法匹配。
Method=GET
表示当请求方法为GET请求时匹配。 - Cookie:通过Cookie进行匹配。
Cookie=chocolate, ch.p
表示请求中含有一个名为chocolate值为匹配ch.p
正则表达式的Cookie时匹配,所以这里值可以为chap
、chbp
等。 - Query:通过URL查询参数匹配。它接收两个参数,参数名和参数值,其中参数值是可选的,且是正则表达式。
Query=foo, ba.
表示请求中包含名为foo的查询参数且值为baa、bab等时匹配。 - RemoteAddr:通过客户端IP地址来匹配。它可以同时匹配多个IP地址,多个IP时用逗号分隔,也可以按子网匹配。
RemoteAddr=10.10.10.1/24,10.10.11.1
表示客户端IP是10.10.10.*
或10.10.11.1
时匹配。
可以在org.springframework.cloud.gateway.handler.predicate
包下查看各个Predicate的定义,每个Predicate定义的类名称都是Predicate的名称加RoutePredicateFactory后缀,如Before Predicate的类名称是BeforeRoutePredicateFactory。
关于路由匹配条件的更多信息请参考 https://cloud.spring.io/spring-cloud-static/Finchley.SR2/multi/multi_gateway-request-predicates-factories.html。
自定义路由匹配条件
自定义路由匹配条件可以新建一个名称以RoutePredicateFactory结尾的Class,该Class需要继承AbstractRoutePredicateFactory抽象类,并定义为bean。下面的代码中就定义了一个名为My的RoutePredicateFactory,其对应的配置类是其内部定义的Config,接收一个name参数,通过该参数可以指定Request请求中需要包含的参数名,当请求中包含该参数名且为GET请求时即表示满足某个路由的匹配条件。
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
private static final String NAME_KEY = "name";
public MyRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
ServerHttpRequest request = exchange.getRequest();
if (HttpMethod.GET.equals(request.getMethod())
&& request.getQueryParams().containsKey(config.getName())) {
return true;
}
return false;
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(NAME_KEY);
}
@Data
public static class Config {
private String name;
}
}
假设现在有一个路由定义需要请求中包含参数abc且为GET请求即表示满足路由转发条件,则我们可以使用上面新定义的MyRoutePredicateFactory进行如下配置。
spring:
cloud:
gateway:
routes:
- id: route_id
uri: http://localhost:8900
predicates:
- My=abc
GatewayFilter
Spring Cloud Gateway的路由定义除了可以定义路由的匹配条件外,还可以定义路由需要被处理的GatewayFilter。
GatewayFilter可以对路由的请求或响应内容进行更改。下面的代码就给这个路由定义了三个GatewayFilter,AddRequestHeader这个Filter给Header中加了名为ABC,值为DEF的Header,第二次又加了名为ABC2,值为DEF2的Header;AddRequestParameter这个Filter加了名为foo,值为bar的查询参数。
spring:
cloud:
gateway:
routes:
- id: route_id
uri: http://localhost:8900
predicates:
- Path=/hello/**
filters:
- AddRequestHeader=ABC, DEF
- AddRequestHeader=ABC2, DEF2
- AddRequestParameter=foo, bar
如你所见,AddRequestHeader这类GatewayFilter一次只能添加一个Header,如果需要添加多个Header,则定义多个AddRequestHeader。
除了上面两个GatewayFilter外,Spring Cloud Gateway还内置了很多GatewayFilter。它们都定义在org.springframework.cloud.gateway.filter.factory
包中,类名称都是XXXGatewayFilterFactory,那么对应的GatewayFilter的名称就是XXX,比如AddRequestHeaderGatewayFilterFactory就对应AddRequestHeader这个Filter,以下是其中部分的简要介绍。
- AddResponseHeader:添加响应的Header。
AddResponseHeader=X-Response-Foo, Bar
表示添加名为X-Response-Foo,值为Bar的响应Header。 - SetResponseHeader:设置响应的Header,它与AddResponseHeader的区别是它可能进行覆盖,而AddResponseHeader是完全追加的。
- Hystrix:表示可以使用Hystrix进行封装,然后使用其断路器机制,使用该GatewayFilter时需要添加
spring-cloud-starter-netflix-hystrix
依赖。该Filter只是用来定义路由对应的HystrixCommand的名称,比如Hystrix=myCommandName
表示当前路由对应的HystrixCommand的名称是myCommandName,然后就可以通过myCommandName来定义Hystrix的相关配置了,Hystrix的相关配置是定义在application.properties或application.yml中的,如hystrix.command.myCommandName.execution.isolation.thread.timeoutInMilliseconds=5000
。 - PrefixPath:添加路径前缀。
PrefixPath=/mypath
表示路径自动添加前缀/mypath
,所以当客户端访问的是/hello
时转发给底层服务的路径是/mypath/hello
。 - RemoveRequestHeader:从请求Header中移除某个Header。
RemoveRequestHeader=X-Request-Foo
表示将从请求Header中移除名为X-Request-Foo的Header,然后再转发到底层服务。 - RemoveResponseHeader:从响应Header中移除某个Header。
- RewritePath:重写路径采用的是正则表达式替换。如
RewritePath=/foo/(?<segment>.*), /$\{segment}
时,当访问的是/foo/abc
转发到底层服务的是/abc
。 - SetPath:它可以直接指定转发的Path,但通常会接收一个路径参数。比如下面这样,segment就是一个路径变量,当访问的是
/foo/abc
时将转发到/abc
。Path中同时定义多个路径变量也是可以的。
spring:
cloud:
gateway:
routes:
- id: setpath_route
uri: http://example.org
predicates:
- Path=/foo/{segment}
filters:
- SetPath=/{segment}
- StripPrefix:定义转发前需要去除的前缀级数。
StripPrefix=2
表示需要去除两级,当访问/a/b/c/d
时将转发到/c/d
。
关于GatewayFilter的更多介绍和配置信息请参考https://cloud.spring.io/spring-cloud-static/Finchley.SR2/multi/multi__gatewayfilter_factories.html。接下来介绍几个配置复杂一点的GatewayFilter。
关于Gateway可配置的更多信息请参考
org.springframework.cloud.gateway.config.GatewayProperties
。
Hystrix GatewayFilter
使用Hystrix GatewayFilter的路由会被HystrixCommand封装起来。HystrixCommand配置的核心是commandName,Hystrix GatewayFilter就是用来指定其对应的commandName的,比如下面这样。
spring:
cloud:
gateway:
routes:
- id: hystrix_route
uri: http://example.org
filters:
- Hystrix=myCommandName
然后我们就可以在application.properties或application.yml中定义Hystrix的相关配置了,比如hystrix.command.myCommandName.execution.isolation.thread.timeoutInMilliseconds=5000
。使用Hystrix GatewayFilter时可以配置一个fallbackUri,其会在断路器打开时转为访问fallbackUri。fallbackUri必须以forward:
开头,其对应的路径是Gateway应用的路径。比如下面的配置,当我们通过Hystrix设置了访问该路由的超时时间是5秒钟,超过5秒钟还没有返回时就将转为访问本Gateway应用的/hystrix/fallback
。
spring:
cloud:
gateway:
routes:
- id: hystrix_test
uri: http://localhost:8900
predicates:
- Path=/hello/gateway/hystrix/{num}
filters:
- name: Hystrix
args:
name: myCommandName
fallbackUri: forward:/hystrix/fallback
上面的配置,指定name和args属性是标准的
org.springframework.cloud.gateway.filter.FilterDefinition
的配置,直接写Hystrix=myCommandName
是把它作为一个整体作为FilterDefinition的构造参数,其内部会再拆分为name和args。
要访问到上面的fallbackUri指定的forward:/hystrix/fallback
我们可以在Gateway应用中定义如下Controller。
@RestController
@RequestMapping("hystrix/fallback")
public class HystrixFallbackController {
@GetMapping
public String fallback() {
return "gateway fallback result";
}
}
RequestRateLimiter GatewayFilter
RequestRateLimiter GatewayFilter使用org.springframework.cloud.gateway.filter.ratelimit.RateLimiter
来限制一个用户的访问速度。Spring Cloud Gateway已经提供了一个基于Redis的实现,org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter
。使用它需要添加spring-boot-starter-data-redis-reactive
依赖。然后可以通过参数redis-rate-limiter.replenishRate
来指定一个用户在每秒允许发起的最大请求数,多余的请求不会被丢弃;通过参数redis-rate-limiter.burstCapacity
来设置一个用户每秒能发起的最大请求数,多余的请求会被丢弃。它俩的区别是前者相当于每秒能处理的请求数,多余的会留到下一秒处理,而后者是能够接受的最大的请求数,多余的会被丢弃。比如下面的配置设置了前者为10,后者为20,表示每秒能处理一个用户的10个请求,如果第一秒来了20个请求,那么剩余的10个将放到第二秒处理。
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: http://example.org
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
如果把两者都设置为10,则第一秒来了20个请求时,将只能处理10个,另外10个将被丢弃。
限制的访问速度是根据org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
解析出来的Key来限制的,默认是基于Principal的实现,如果有需要可以实现自己的KeyResolver,然后把它定义为一个bean,通过SPEL表达式#{@myKeyResolver}
来引用对应的bean。如果有需要也可以实现自己的RateLimiter,然后把它定义为一个bean,然后进行下面这样的配置。
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: http://example.org
filters:
- name: RequestRateLimiter
args:
rate-limiter: "#{@myRateLimiter}"
key-resolver: "#{@userKeyResolver}"
Retry GatewayFilter
Retry GatewayFilter可以对转发请求进行重试。可以通过retries、series、statuses、methods四个参数来控制重试机制。
- retries用来指定最大的重试次数。
- series用来指定需要重试的错误码系列,在没有指定状态码时使用。它的可选值可以参考
org.springframework.http.HttpStatus.Series
。 - statues用来指定需要重试的错误码。
- methods用来指定允许重试的方法
它们的默认配置如下面代码所示。
private int retries = 3;
private List<Series> series = toList(Series.SERVER_ERROR);
private List<HttpStatus> statuses = new ArrayList<>();
private List<HttpMethod> methods = toList(HttpMethod.GET);
下面的配置就配置了该路由在遇到状态码为BAD_GATEWAY或INTERNAL_SERVER_ERROR时进行重试,最大重试次数为3,其它参数也可以进行类似的配置。
spring:
cloud:
gateway:
routes:
- id: retry_test
uri: http://localhost:8900
predicates:
- Path=/hello/**
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,INTERNAL_SERVER_ERROR
自定义GatewayFilter
自定义GatewayFilter可以选择实现GatewayFilterFactory,也可以是继承抽象类AbstractGatewayFilterFactory或AbstractNameValueGatewayFilterFactory等,它们都需要指定一个配置类,当然了,如果不需要指定额外的配置信息也可以不指定配置类,因为抽象类已经有默认的配置类了,AbstractNameValueGatewayFilterFactory默认的配置类是NameValueConfig。GatewayFilterFactory类的命名需要以GatewayFilterFactory结尾,对应的GatewayFilterFactory的名称就是去除GatewayFilterFactory之后的部分。比如下面我们定义的MyGatewayFilterFactory的名称就是My。然后需要把它定义为一个bean。下面的代码中我们继承了AbstractGatewayFilterFactory,然后通过构造方法指定了配置类为其内部的Config类,Config类定义了一个name属性。我们的处理逻辑就是在请求转发之前打印一句话,请求转发后打印一句话。
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
private static final String NAME_KEY = "name";
public MyGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
URI uri = exchange.getRequest().getURI();
System.out.println("请求" + uri + "以前的处理,config配置的name为:" + config.getName());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
System.out.println("请求" + uri + "以后的处理,config配置的name为:" + config.getName());
}));
};
}
/**
* 在配置该Filter时,采用简写形式(即FilterName=参数)时后面的参数对应Config类中参数的顺序。
* @return
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(NAME_KEY);
}
@Data
public static class Config {
private String name;
}
}
接着我们就可以在需要使用MyGatewayFilterFactory的路由定义中进行配置了,比如下面的配置就使用了MyGatewayFilterFactory,并指定上面Config配置类的name属性值为ABCDE
。以此种方式指定参数需要重写上面的shortcutFieldOrder()
方法。
spring:
cloud:
gateway:
routes:
- id: route_id
uri: http://localhost:8900
predicates:
- Path=/hello/**
filters:
- My=ABCDE
以下是等价的全配置,其通过name属性指定GatewayFilterFactory的名称,args指定对应配置类里面可定义的参数。
spring:
cloud:
gateway:
routes:
- id: route_id
uri: http://localhost:8900
predicates:
- Path=/hello/**
filters:
- AddRequestHeader=ABC, DEF
- AddRequestHeader=ABC1, DEF1
- AddRequestParameter=foo, bar
- AddRequestParameter=foo1, bar1
- name: My
args:
name: ABCDE
GlobalFilter
GatewayFilter是针对于特定的路由作用的,而GlobalFilter是作用于所有的路由的。Spring Cloud Gateway已经内置了一些GlobalFilter,列举几个如下:
- ForwardRoutingFilter 其会判断需要路由到的URI是否是forward开头的,如果是将把请求交由DispatcherHandler处理,即由当前Gateway应用自己处理。
- LoadBalancerClientFilter 其会判断路由到的URI是否以lb开头的(如
lb://servicename
),如果是将使用LoadBalanceClient获取服务(前面的servicename)对应的一个真实的地址。 - NettyRoutingFilter 当路由到的URI是以http或https开头的,会采用Netty的HttpClient处理路由地址。
- GatewayMetricsFilter 负责收集某些指标信息。
关于内置的GlobalFilter的更多信息请参考https://cloud.spring.io/spring-cloud-static/Finchley.SR2/multi/multi__global_filters.html。
LoadBalancerClientFilter
LoadBalancerClientFilter 其会判断路由到的URI是否以lb开头的(如lb://servicename
),如果是将使用LoadBalanceClient获取服务(前面的servicename)对应的一个真实的地址。使用负载均衡需要Classpath下存在DiscoveryClient相关的实现,比如前面介绍的Eureka、Consul等。下面的代码中我们就定义了一个路由到的目标地址是依赖于LoadBalanceClient的,当我们访问/lb/a/b/c
的时候,就将映射到lb_route,然后会从DiscoveryClient获取服务test的一个地址,假设获取到的地址是http://localhost:8080
,那么请求会被转发到http://localhost:8080/a/b/c
(StripPrefixGatewayFilter会把前缀/lb
去掉)。
spring:
cloud:
gateway:
routes:
- id: lb_route
uri:lb://test
predicates:
- Path=/lb/**
filters:
- StripPrefix=1
自定义GlobalFilter
有需要可以实现自己的GlobalFilter。自定义的GlobalFilter需要定义为bean,多个Filter之间可以通过@Order
或Ordered接口指定顺序,数值越小的排在越前。
@Component
@Order(1)
public class MyGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI uri = exchange.getRequest().getURI();
System.out.println("接收到请求:" + uri);
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
System.out.println("请求处理结束:" + uri);
}));
}
}
当接收到某个请求时GatewayFilter和GlobalFilter可能是相互穿插的,它们会被放到同一个Filter链里面,对应的顺序将按照Spring的Order规范来排序。
通过Java定义路由
除了在配置文件中配置路由定义信息之外,也可以通过Java程序来配置路由信息。配置方式如下,在@Configuration
类中定义一个返回值为RouteLocator的方法,方法可以接收一个RouteLocatorBuilder类型的参数,方法上需要加上@Bean
。然后在方法体中通过RouteLocatorBuilder来定义路由信息。下面的代码就定义了一个接收请求/api/**
,然后路由到http://localhost:8900
的路由。route()方法可以调用多次,每次都将定义一个新的路由定义。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.path("/api/**")
.filters(f ->
f.addResponseHeader("X-TestHeader", "foobar"))
.uri("http://localhost:8900")
).build();
}
}
https支持
Gateway应用也可以启用https支持,它的启用跟普通的Spring Boot应用的配置是一样的。比如下面代码中就定义了https对应的keyStore的路径为Classpath下的keystore.p12,keyStore的密码为Spring,key为Spring,keyStore的类型为PKCS12。
server:
ssl:
enabled: true
key-alias: spring
key-store-password: spring
key-store: classpath:keystore.p12
key-store-type: PKCS12
虽然Gateway应用启用了https协议,但是底层的服务还是可以使用http协议访问的,它们是相互独立的。如果我们需要路由到的底层的服务是需要通过https访问的,则可以配置spring.cloud.gateway.httpclient.ssl.useInsecureTrustManager=true
来信任所有的证书,比如下面这样。
spring:
cloud:
gateway:
httpclient:
ssl:
useInsecureTrustManager: true
routes:
- id: route_id
uri: https://localhost:8900
predicates:
- Path=/hello/**
filters:
- AddRequestHeader=ABC, DEF
- AddRequestParameter=foo, bar
Spring Cloud Gateway底层进行路由转发会使用Netty的
reactor.ipc.netty.http.client.HttpClient
,基于该HttpClient可配置的相关信息可参考org.springframework.cloud.gateway.config.HttpClientProperties
。
对于生产而言,信任所有的证书不是一个好的选择,它可能不安全,可通过如下方式配置你信任的公钥证书。
spring:
cloud:
gateway:
httpclient:
ssl:
trustedX509Certificates:
- cert1.pem
- cert2.pem
跨域配置
Spring Cloud Gateway也支持跨域配置,相关可配置的信息可以参考org.springframework.cloud.gateway.config.GlobalCorsProperties
。比如下面的配置就定义了所有Origin为test.elim.com
的GET请求都允许访问当前的Gateway。
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "test.elim.com"
allowedMethods:
- GET
Actuator Endpoint支持
Spring Cloud Gateway也提供了基于Spring Boot Actuator的支持,提供了一个名为gateway的Endpoint。在项目中添加了spring-boot-starter-actuator
依赖,并对外发布了gateway这个Endpoint后可以通过/actuator/gateway/routes
查看所有的路由定义;可通过/actuator/gateway/routes/route_id
查看id为route_id
的路由定义;/actuator/gateway/routefilters
可以查看可用的GatewayFilter定义;/actuator/gateway/globalfilters
可查看所有的GlobalFilter定义;更多关于gateway Endpoint的操作可查看对应的源码org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint
。