springcloud系列学习笔记目录参见博主专栏 spring boot 2.X/spring cloud Greenwich。
由于是一系列文章,所以后面的文章可能会使用到前面文章的项目。文章所有代码都已上传GitHub:https://github.com/liubenlong/springcloudGreenwichDemo
本系列环境:Java11;springboot 2.1.1.RELEASE;springcloud Greenwich.RELEASE;MySQL 8.0.5;
Spring Cloud Gateway
是Spring Cloud官方推出的第二代网关框架,取代Zuul网关。网关在微服务系统中有着非常作用,网关常见的功能有路由转发、权限校验、限流控制等作用。
spring cloud 早期版本中使用的是zuul 1.X 。后来zuul 1.X 升级到 zuul 2.x 的进度过于缓慢,springcloud官方图案就就自行研发了这个网关。
Spring Cloud Gateway
是基于Project Reactor
的,是响应式的架构。它依赖了reactor
以及webflux
。关于响应式编程请参考笔者系列文章:
本节看一下springcloud gateway如何使用。参考值官网教程https://spring.io/guides/gs/gateway/。
我们这里引入相关依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
application.yml中的配置:
server:
port: 8095
# 最佳实践:springcloud应用都要指定application.name
spring:
application:
name: springcloud-gateway-helloworld
编写main方法,我这里将bean的配置放到了main方法中:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p
.path("/get")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri("http://httpbin.org:80"))
.build();
}
}
在上面的myRoutes方法用于创建路由,该方法中可以添加各种predicates和filters。predicates断言的意思,顾名思义就是根据具体的请求的规则,由具体的route去处理,filters是各种过滤器,用来对请求做各种判断和修改。
上面简单路由的规则是将/get
的请求路由到http://httpbin.org:80/get
,并且在header中添加参数Hello=World
。
启动服务,可以从日志输出中看到,springcloud gateway加载了各种路由断言工厂:
访问http://127.0.0.1:8095/get
输出:
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"Cache-Control": "no-cache",
"Connection": "close",
"Forwarded": "proto=http;host=\"127.0.0.1:8095\";for=\"127.0.0.1:52306\"",
"Hello": "World",
"Host": "httpbin.org",
"Postman-Token": "fbcbbf84-b40b-d9b4-b62c-dea1105c3019",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"X-Forwarded-Host": "127.0.0.1:8095",
"X-Postman-Interceptor-Id": "8f8a8b77-9faa-af0f-da6a-203d2049af46"
},
"origin": "127.0.0.1, 103.126.92.86",
"url": "http://127.0.0.1:8095/get"
}
备注:httpbin这个网站能测试 HTTP 请求和响应的各种信息,比如 cookie、ip、headers 和登录验证等,且支持 GET、POST 等多种方法,对 web 开发和测试很有帮助。它用 Python + Flask 编写,是一个开源项目。其官网地址是https://httpbin.org/ 。可以httpbin的使用可以参考https://blog.phpgao.com/how-to-httpbin.html。
如果直接访问http://httpbin.org:80/get
.输出结果是
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"Cache-Control": "no-cache",
"Connection": "close",
"Cookie": "_gauges_unique_day=1; _gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1",
"Host": "httpbin.org",
"Postman-Token": "e77a3596-58f5-bdd8-a953-edbaaeb8e3ff",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
"X-Postman-Interceptor-Id": "090915cc-dc8e-3fd5-0d2c-d2a179a6b2fa"
},
"origin": "103.126.92.86",
"url": "http://httpbin.org/get"
}
读者可以比较一下两者的区别,主要是使用springcloud gateway会存在Forwarded
转发。
上面依赖中我们已经将Hystrix引入进来。这里修改启动类,添加一个新的路由器,并且添加一个fallback方法。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@SpringBootApplication
@RestController
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("path-route", p -> p
.path("/get") //根据PATH路由
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri("http://httpbin.org:80"))
// 使用hystrix
.route("hystrix-route", p -> p
.host("*.abc.com") //根据host路由
.filters(f -> f
.hystrix(config -> config
.setName("mycmd")
.setFallbackUri("forward:/fallback")))
.uri("http://httpbin.org:80"))
.build();
}
/**
* 该方法对应于上面的路由hystrix-route的fallback。
* Mono是webflux的类,响应式编程的写法
* @return
*/
@RequestMapping("/fallback")
public Mono<String> fallback() {
return Mono.just("fallback");
}
}
重启服务,在postman中访问http://127.0.0.1:8095/delay/3
,并且在header中指定其host:
由于经过了路由之后,实际的访问路径变成了http://httpbin.org:80/delay/3
,该接口访问失败,所以输出结果是fallback
。
我们将访问连接改为http://127.0.0.1:8095/headers
就会访问成功:
到此为止,应该基本了解springcloud gateway的运行机制了。
Spring Cloud Gateway内置了许多Predict,这些Predict的源码在org.springframework.cloud.gateway.handler.predicate
包中,如果读者有兴趣可以阅读一下。现在列举各种Predicate如下图:
接下来看一下每种predicate
的应用。
上面是通过编写java代码来实现路由功能,接下来我们使用配置文件方式来实现。
首先我们将java启动类中的com.example.Application#myRoutes
方法注释掉,避免与配置文件中的相互影响
配置中添加对应的path route:
server:
port: 8095
spring:
application:
name: springcloud-gateway-helloworld
profiles: # 这里指定应用哪个profile,对应下面的spring.profiles
active: path_route
--- # 三个减号的意思是在一个文件中编写多个配置,通过spring.profiles.active指定应用哪个配置
spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://httpbin.org:80/
predicates:
- Path=/get
profiles: path_route
请求http://127.0.0.1:8095/get
结果:
访问http://127.0.0.1:8095/ip
返回404。
编写after_route
spring:
application:
name: springcloud-gateway-helloworld
profiles:
active: after_route
---
spring:
cloud:
gateway:
routes:
- id: after_route
uri: http://httpbin.org:80/get # 这里后面的get没有生效,生效的只是前面的host: httpbin.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
profiles: after_route
这里指定了访问时间在2017-01-20 17:42:47.789。访问http://127.0.0.1:8095/get
方返回结果与上面一致,可以正常访问,说明路由生效了。
访问http://127.0.0.1:8095/ip
也可以正常返回结果。说明这里spring.cloud.gateway.routes[0].uri=http://httpbin.org:80/get
中的get
路径没有生效,只是host生效了,通过host拼接上真实的访问URL的path,这里就是http://httpbin.org:80/
拼接上ip
即路由后真实访问的路径是http://httpbin.org:80/ip
。
spring:
cloud:
gateway:
routes:
- id: before_route
uri: http://example.org
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
在指定直接之前的 任何请求相匹配。
Between需要两个参数,用逗号分隔,与指定的两个时间内的任何请求相匹配。
spring:
cloud:
gateway:
routes:
- id: between_route
uri: http://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
---
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: http://httpbin.org:80/get
predicates:
- Cookie=name, lisi
profiles: cookie_route
上面指定了满足cookie中的name=lisi
才进行路由。别忘了将spring.profiles.active
改为cookie_route
启动这个cookie_route。
在发起请求时我们这里需要指定Cookie的值。
读者可以将配置文件中的name的期望值改一下,再次测试。如果期望的Cookie在URL请求头中不存在,则会404.
这里模拟header中设置请求数据。同理别忘了将spring.profiles.active
改为header_route
启动这个header_route。
Header有两个参数,第一个是key,第二个是value
---
spring:
cloud:
gateway:
routes:
- id: header_route
uri: http://httpbin.org:80/
predicates:
- Header=X-Request-Id, \d+
profiles: header_route
上面路由的含义是header中要存在X-Request-Id=[数字]
的信息。重启服务.
读者可以尝试将123
改为任意一个非数字字符串,测试一下,返回404.
---
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://httpbin.org:80/
predicates:
- Host=*.abc.com
profiles: host_route
满足Host=*.abc.com的请求才会被路由。如果与多个host匹配则用逗号分隔即可。
---
spring:
cloud:
gateway:
routes:
- id: method_route
uri: http://httpbin.org:80/
predicates:
- Method=GET
profiles: method_route
所有的GET请求都会被转发。
---
spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://httpbin.org:80/
predicates:
- Query=foo, abc
profiles: query_route
Query接收两个参数,一个是参数名[必填],一个是参数值[选填]。
如果参数值不填,表示请求参数中保护指定key的参数即可,如果填了参数值,则不仅要包含key,其值还要与指定的value相同。
如上图所示(来自官网),客户端向Spring Cloud Gateway发出请求。 Gateway Handler Mapping根据predicate
确定走哪个路由,则将其发送到Gateway web handler处理。 Gateway web handler处理请求时会经过一系列的过滤器链。 先执行所有pre
过滤器逻辑,然后进行代理请求。 在发出代理请求之后,收到代理服务的响应之后执行post
过滤器逻辑。在执行所有“pre”过滤器逻辑时,往往进行了鉴权、限流、日志输出等功能,以及请求头的更改、协议的转换;转发之后收到响应之后,会执行所有“post”过滤器的逻辑,在这里可以响应数据进行了修改,比如响应头、协议的转换等。
在Spring Cloud Gateway中,filter从作用范围可分为另外两种,一种是针对于单个路由的gateway filter,它在配置文件中的写法同predict类似;另外一种是针对于所有路由的global gateway filer。
Spring Cloud Gateway 内置了很多过滤器工厂,都继承了AbstractGatewayFilterFactory
抽象类,以AddRequestHeaderGatewayFilterFactory
为例:
官方提供了很多filter工厂,这里我们只找几个常用的FilterFactory举例。
这里我们以上面的query_route
为例进行改造,在要求请求参数中有foo=abc
之外,添加过滤器,在header中添加X-Request-Foo=Bar
。注意这里filters下写的是FilterFactory的前缀,而不是完整的方法名,springboot的约定大于配置规则
生效。
--- # Query--请求参数
spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://httpbin.org:80/
predicates:
- Query=foo, abc
filters:
- AddRequestHeader=X-Request-Foo, Bar
profiles: query_route
AddRequestParameter
含义是追加请求参数。在上述实例基础上添加AddRequestParameter:
--- # Query--请求参数
spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://httpbin.org:80/
predicates:
- Query=foo, abc
filters:
- AddRequestHeader=X-Request-Foo, Bar
- AddRequestParameter=age, 10
profiles: query_route
Hystrix的配置还是比较复杂的,这里仅仅列出如何集成hystrix,详细的hystrix文档请参考笔者另一篇文章分布式熔断、限流与服务保护:深入 Hystrix 原理及使用。
我们还是在之前query_route基础上改造。首先指定使用HystrixGatewayFilterFactory过滤器工厂,并且设置了两个参数name和fallbackUri,这里fallbackUri必须以forward
开头,表明是转发。然后我们这里设置了fallbackcommand的超时时间用于测试。读者可以尝试修改这个timeoutInMilliseconds的值,来观察http://127.0.0.1:8095/get?foo=abc
的输出结果。
配置如下:
# 指定fallbackcommand这个command的超时时间。用于下面的hystrix测试
hystrix:
command:
fallbackcommand:
execution:
isolation:
thread:
timeoutInMilliseconds: 100
--- # Query--请求参数
spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://httpbin.org:80/
predicates:
- Query=foo, abc
filters:
- AddRequestHeader=X-Request-Foo, Bar
- AddRequestParameter=age, 10
- name: Hystrix # 这里就是指定了 HystrixGatewayFilterFactory 这个过滤器工厂。下面是该工厂的参数
args:
name: fallbackcommand
fallbackUri: forward:/fallback
profiles: query_route
给请求添加前缀。该功能可以用于对外隐藏真实路径。
为了验证该功能,我们编写一个controller,方便起见,这里我们没有新起项目,还是使用的现有8095端口的项目:
@RestController
@RequestMapping("hello")
public class HelloController {
@RequestMapping("a")
public Mono<String> a() {
System.out.println("aaaaaaaa");
return Mono.just("aa");
}
@RequestMapping("b")
public Mono<String> b() {
System.out.println("bbbbb");
return Mono.just("bb");
}
}
我们通过添加配置如下:
--- # prefixPath_route
spring:
cloud:
gateway:
routes:
- id: prefixPath_route
uri: http://127.0.0.1:8095/
predicates:
- Query=foo, abc
filters:
- PrefixPath=/hello
profiles: prefixPath_route
通过上面的filter,springcloud gateway会将http://127.0.0.1:8095/a?foo=abc
请求路由到http://127.0.0.1:8095/hello/a?foo=abc
,添加前缀/hello
。返回结果为aa
:
这个功能属于中要功能。
限流算法常用的有两种:漏桶算法、令牌桶算法。由于本节内容稍多,故单独写一篇文章。请读者移步至 阅读。
重定向。有两个参数,第一个是相应的status,第二个是重定向的URL地址。
下面我们请求接口a,之后重定向到b(借用上面的 prefixPath_route):
--- # prefixPath_route
spring:
cloud:
gateway:
routes:
- id: prefixPath_route
uri: http://127.0.0.1:8095/
predicates:
- Query=foo, abc
filters:
- PrefixPath=/hello
- RedirectTo=302, http://127.0.0.1:8095/hello/b # 执行完请求后重定向到b
profiles: prefixPath_route
执行请求http://127.0.0.1:8095/a?foo=abc
输出结果是bb
。来看一下控制台输出:
aaaaaaaa
bbbbb
可见是先执行完a方法,然后重定向到b执行。
spring:
cloud:
gateway:
routes:
- id: removerequestheader_route
uri: http://example.org
filters:
- RemoveRequestHeader=X-Request-Foo
RemoveRequestHeader会在请求执行前将制定的header参数移除。
spring:
cloud:
gateway:
routes:
- id: removeresponseheader_route
uri: http://example.org
filters:
- RemoveResponseHeader=X-Response-Foo
RemoveResponseHeader会在将response返回给用户前将其header中的X-Response-Foo移除。
RewritePath URL重写(允许使用正则表达式)。
我们在header_route添加上RewritePath=/foo/(?
,含义是假如有/foo/get
的请求,在发送给下游之前,会将其重写为/get
。
--- # Header--请求头
spring:
cloud:
gateway:
routes:
- id: header_route
uri: http://httpbin.org:80/
predicates:
- Header=X-Request-Id, \d+
filters:
- RemoveRequestHeader=X-Request-Foo # 将header中的参数移除
- RewritePath=/foo/(?.*), /$\{segment} # URL重写(允许使用正则表达式)
profiles: header_route
spring:
cloud:
gateway:
routes:
- id: nameRoot
uri: http://nameservice
predicates:
- Path=/name/**
filters:
- StripPrefix=2
去除前缀。
例如发送请求/name/bar/foo
会被转换为http://nameservice/foo
还有很多过滤器工厂,这里就不一一介绍了,使用到的同学请参考 Spring Cloud Gateway官方文档 。
springcloud系列学习笔记目录参见博主专栏 spring boot 2.X/spring cloud Greenwich。
由于是一系列文章,所以后面的文章可能会使用到前面文章的项目。文章所有代码都已上传GitHub:https://github.com/liubenlong/springcloudGreenwichDemo
本系列环境:Java11;springboot 2.1.1.RELEASE;springcloud Greenwich.RELEASE;MySQL 8.0.5;