目录
1 简介
- 不管是来自客户端的请求,还是服务内部调用,一切对服务的请求都可经过网关。
- 网关实现鉴权、动态路由等等操作。
- Gateway是我们服务的统一入口。
Spring Cloud Gateway本身也是一个微服务,需要注册到Eureka,其功能特性如下:
- 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
- 动态路由
- Predicates 和 Filters 作用于特定路由
- 集成 Hystrix 断路器
- 集成 Spring Cloud DiscoveryClient
- 简单好用的 Predicates 和 Filters
- 限流
- 路径重写
相关术语
- Route(路由):这是网关的基本模块。它由一个 ID,一个目标 URI,一组断言和一组过滤器(可不写)定义。如果断言为真,则路由匹配。
- Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
- Filter(过滤器):这是org.springframework.cloud.gateway.filter.GatewayFilter的实例,我们可以使用它修改请求和响应。
2 搭建网关
搭建网关微服务,实现服务路由分发:
实现效果:当访问 http://访问网关的服务/user/findById?id=1时,网关路由至 http://127.0.0.1:9091/user/findById?id=1
步骤
- 创建SpringBoot工程gateway_server
- 勾选starter:网关、Eureka客户端
- 编写基础配置:端口,应用名称,注册中心地址
- 编写路由规则:唯一标识id,路由url地址,路由限定规则
- 启动网关服务进行测试
# 端口
server.port: 10010
# 应用名
spring.application.name: api-gateway
# 注册中心地址
eureka.client.service-url.defaultZone: http://127.0.0.1:10086/eureka
#配置路由转发规则
spring:
cloud:
gateway:
#配置多个路由
routes:
#当前路由的唯一标识
- id: user-service-route
#转发到服务的地址
uri: http://127.0.0.1:8080
#断言:当前路由的转发规则(拦截规则)所有请求地址都会先经过路由,经过路由后判断请求地址是否符合断言的规则
#把请求中上下文为带:http://127.0.0.1:10010/feign_consumer/** 的所有请求都拦截下来
#并路由到:http://127.0.0.1:8080/feign_consumer/**
predicates:
- Path=/feign_consumer/**
测试:输入 http://localhost:10010/feign_consumer/2,用的是网关的port,但是实际访问的是服务的消费者 http://localhost:8080/feign_consumer/2(服务消费者再去调用生产者,当然也可以直接访问服务生产者)。此外注意:这里测试访问的是服务消费者,接下来涉及到动态路由,所以直接访问服务提供者。配置文件里面的断言也会修改,所以下里面的内容请注意!
3 动态路由
在刚才路由规则中,我们把路径对应服务地址写死了!如果服务提供者集群的话,这样做不合理。
正确的做法是:根据服务名称,从Eureka注册中心查找服务对应的所有实例列表,然后进行动态路由(利用Ribbon进行负载均衡访问 ),我们只需要写一些配置即可完成上述操作。如下:
# 端口
server.port: 10010
# 应用名
spring.application.name: api-gateway
# 注册中心地址
eureka.client.service-url.defaultZone: http://127.0.0.1:10086/eureka
#配置路由转发规则
spring:
cloud:
gateway:
#全局过滤器
default-filters:
- AddResponseHeader=happy-a,happy
#配置多个路由
routes:
#第1个路由:通过网关访问服务消费者,负载均衡是服务消费者客户端提供的功能
- id: user-service-route
#转发到服务的地址(地址被写死,没法集群访问)
uri: http://127.0.0.1:8080
#断言:当前路由的转发规则(拦截规则)所有请求地址都会先经过路由,经过路由后判断请求地址是否符合断言的规则
#把请求中上下文为带:http://127.0.0.1:10010/feign_consumer/** 的所有请求都拦截下来
#并路由到:http://127.0.0.1:8080/feign_consumer/**
predicates:
- Path=/feign_consumer/**
#第2个路由:通过网关直接访问服务生产者集群(其实就是负载均衡)
- id: user-service-route2
#动态路由解决访问集群的问题:lb://服务名称
uri: lb://user-service
#所有请求都拦截,从而方便增加路由前缀
predicates:
- Path=/**
#局部过滤器
filters:
#添加前缀
#原始请求: http://127.0.0.1:10010/findById?id=1
#实际访问: http://127.0.0.1:10010/user/findById?id=1
#- PrefixPath=/user
#去除前缀:数字是几就去除几个前缀,前缀写什么都无所谓
#原始请求: http://127.0.0.1:10010/a/user/findById?id=1
#实际访问: http://127.0.0.1:10010/user/findById?id=1
- StripPrefix=1
4 路由前缀
在gateway中可以通过配置路由的过滤器PrefixPath 实现映射路径中的前缀添加,起到隐藏接口地址的作用,避免接口地址暴露。
注意:添加前缀和去除前缀只能二选一!
- 添加前缀
原始请求: http://127.0.0.1:10010/findById?id=1
实际访问: http://127.0.0.1:10010/user/findById?id=1
- 去除前缀
StripPrefix后面数字是几就去除几个前缀,前缀写什么都无所谓
原始请求: http://127.0.0.1:10010/a/user/findById?id=1
实际访问: http://127.0.0.1:10010/user/findById?id=1 (去除了1个前缀a)
5 过滤器
5.1 Gateway 自带的过滤器
过滤器是网关的重要功能,主要用于实现请求的鉴权,“路由前缀”功能也是使用过滤器实现的。过滤器的使用场景如下:
- 请求鉴权:如果没有访问权限,直接进行拦截(主要场景)
- 异常处理:记录异常日志
- 服务调用时长统计
Gateway自带过滤器有几十个,常见的过滤器有:
参考链接:过滤器官网说明
过滤器名称 | 说明 |
---|---|
AddRequestHeader | 对匹配上的请求加上Header |
AddRequestParameters | 对匹配上的请求增加请求参数 |
AddResponseHeader | 对从网关返回的响应添加Header |
StripPrefix | 对匹配上的请求路径去除前缀 |
PrefixPath | 对匹配上的请求路径添加前缀 |
过滤器类型:
- 局部过滤器:只作用在当前配置的路由上。
- 全局过滤器:作用在所有路由上。
全局过滤器配置
以 AddRequestParameters 过滤器为例,在请求响应头添加一个键值对:
局部过滤器
上面的例子中设置了路由前缀和后缀,用的就是局部过滤器
5.2 自定义全局过滤器
- 在前面的学习中,使用的是自带的过滤器功能,如果涉及到和业务逻辑相关的开发,则需要自定义过滤器
- 接下来,针对全局过滤器写一个简单的例子:如果请求参数中有token则允许访问,否则不允许访问,鉴权的过程在这里暂时忽略,只是去模拟过滤器的拦截效果。
自定义过滤器案例实现步骤:
- 新建filter包,实现GlobalFilter(过滤器功能)和 Ordered(指定过滤器功能的执行顺序)接口,创建全局过滤器类MyGlobalFilter
- 编写业务逻辑:重写filter方法,如果请求中有token参数,则认为请求有效,放行,否则不放行,注意:过滤器必须注入SpringIOC容器中
- 测试:http://localhost:10010/feign_consumer/2 访问不到,http://localhost:10010/feign_consumer/2?token=123 可以访
/**
* 全局鉴权过滤器,必须注入到spring容器中才能生效
*/
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
//所有请求地址都会经过当前的过滤方法
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//拿到请求和响应对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//从请求参数中获取token:由于请求参数中可能存在两个名字一样的参数,故只取参数中第一个叫“token”的
String token = request.getQueryParams().getFirst("token");
//如果存在token则放行,如果不存在则拦截,并提示用户未被授权(不判断token本身是否符合要求)
if (null == token) {
//拦截,提示用户未授权,返回错误状态码401(未授权),404(资源未找到),500(服务器错误)
//实际情况直接返回到登陆页面
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//请求放行
return chain.filter(exchange);
}
//返回值:当前自定义过滤器的执行顺序,返回值越小,越靠前执行
@Override
public int getOrder() {
return 0;
}
}