一、网关(Gateway)
1.1 什么是网关(Gateway)?
API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服 务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控(黑白名单)、路由转发等等.
1.2 为什么需要网关?
微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端(pc androud ios 平板)要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。总的来说,网关具有以下功能:
- 协议转换,路由转发
- 流量聚合,对流量进行监控,日志输出
- 作为整个系统的钱罐,对流量进行控制,有限流的作用
- 作为系统的前端界面,外部流量只能通过网关才能访问系统
- 可以在网关层做权限判断
- 可以在网关层做缓存
1.3 Gateway快速入门
增强版
要求功能:客户端访问api网关,网关将请求转到商品微服务
步骤一:创建一个api-gateway工程(微服务),并在pom文件中,引入gateway依赖
步骤二:创建网关的主启动类
步骤三:修改配置文件
#普通版
server:
port: 8000
spring:
cloud:
gateway:
routes:
- id: shop-product #路由的唯一标识,只要不重复都可以,如果不写默认的是通过UUID产生的,一般写成被路由服务的名称
uri: http://locahhost/8081 #被路由的地址
order: 0 #优先级,数字越小,优先级越高
predicates: # 断言,执行路由的判断条件,只有断言都为真,才会执行路由
- Path=/product/**
- id: shop-order
uri: http://locahhost/8091
order: 0
predicates:
- Path=/order/**
# 过滤器,可以再请求前或者请求后做一些动作
# filters:
# -StripPrefix=1
#将网关添加到注册中心
nacos:
server-addr: localhost:8848
application:
name: my-gateway
#增强版
server:
port: 8000
spring:
cloud:
gateway:
routes:
- id: shop-product #路由的唯一标识,只要不重复都可以,如果不写默认的是通过UUID产生的,一般写成被路由服务的名称
uri: lb://shop-product #被路由的地址, lb代表lb协议
order: 0 #优先级,数字越小,优先级越高
predicates: # 断言,执行路由的判断条件,只有断言都为真,才会执行路由
- Path=/product/**
# - Age=18,60
- id: shop-order
uri: lb://shop-order
order: 0
predicates:
- Path=/order/**
# 过滤器,可以再请求前或者请求后做一些动作
# filters:
# -StripPrefix=1
#将网关添加到注册中心
nacos:
server-addr: localhost:8848
application:
name: my-gateway
增强版与普通版的区别:普通版路由路径是写死的,当微服务的端口修改时,还需要修改网关的配置文件,比较麻烦,增强版将网关作为一个微服务,注册到注册中心,然后从注册中心中下载其他微服务的服务名
步骤四:通过网关访问微服务
还有一个简写版实用性不大,就不再赘述。
1.4 Gateway的实现原理
1.4.1 路由
路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:
- id,路由标识符,区别于其他 Route。
- uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
- order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。
- predicate,断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。
- filter,过滤器用于修改请求和响应信息。
1.4.2 执行流程
执行流程大体如下:
1. Gateway Client(网关客户端)向Gateway Server发送请求
2. 请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文
3. 然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给 RoutePredicateHandlerMapping
4. RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用
5. 如果过断言成功,由FilteringWebHandler创建过滤器链并调用
6. 请求会一次经过PreFilter--微服务--PostFilter的方法,最终返回响应
1.4.3 断言
Predicate(断言, 谓词) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。
断言就是说: 在 什么条件下 才能进行路由转发。
内置路由断言工厂
实操几个断言:
server:
port: 8000
spring:
cloud:
gateway:
routes:
- id: shop-product #路由的唯一标识,只要不重复都可以,如果不写默认的是通过UUID产生的,一般写成被路由服务的名称
uri: lb://shop-product #被路由的地址, lb代表lb协议
order: 0 #优先级,数字越小,优先级越高
predicates: # 断言,执行路由的判断条件,只有断言都为真,才会执行路由
- Path=/product/** #访问路径
- Before=2020-11-28T00:00:00.000+08:00 # 表示在2020前访问
- Method=POST # 请求方式必须为POST
nacos:
server-addr: localhost:8848
application:
name: my-gateway
1.4.3.2 自定义断言
假设我们的应用仅仅让age在(min,max)之间的人来访问。
步骤一:在配置文件中,配置一个Age的断言
server:
port: 8000
spring:
cloud:
gateway:
routes:
- id: shop-product #路由的唯一标识,只要不重复都可以,如果不写默认的是通过UUID产生的,一般写成被路由服务的名称
uri: lb://shop-product #被路由的地址, lb代表lb协议
order: 0 #优先级,数字越小,优先级越高
predicates: # 断言,执行路由的判断条件,只有断言都为真,才会执行路由
- Path=/product/**
- Age=18,60
步骤二:自定义一个工厂, 实现断言方法
package com.dhy.gateway.config;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* @program: springcloud-father
* @description: zidingyiduanyan
* @author: 杜航宇
* @create: 2021-07-08 11:21
**/
@Component
// 泛型为自定义的配置类,用来存储配置中真的值
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
@Data
@NoArgsConstructor
//这里的Static不要漏,需要静态方法
public static class Config {
private Integer minAge;
private Integer maxAge;
}
//读取配置文件中的值,并配置为配置类中的属性
public AgeRoutePredicateFactory(){
super(AgeRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder(){
return Arrays.asList("minAge","maxAge");
}
public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config){
return (exchange) -> {
String age = exchange.getRequest().getQueryParams().getFirst("age");
if (StringUtils.isNotEmpty(age)) {
int a = Integer.parseInt(age);
return a >= config.minAge && a <= config.maxAge;
}
return true;
};
}
}
步骤三:启动测试
测试发现,当age在(18,60)可以访问,其它范围不能访问
二、过滤器
上边一期学习了服务网关的断言,断言决定了请求由哪一个路由处理。在路由处理之前,需要经过“pre”类型的过滤器处理,处理返回响应后,可以由“post”类型的过滤器处理。
2.1 过滤器的作用
由过滤器工作流程点可以知道过滤器有着非常重要的作用,在“pre”类型的过滤器可以实现参数校验、权限校验、流量监控、日志输出、协议转换等功能,在“post”类型的过滤器中可以做响应内容、响应头的修改、日志输出、流量监控等功能。要弄清楚为什么需要网关这一层,就不得不提到过滤器的作用了。
当我们有很多个服务时,以图10-3中的user-service、goods-service、sales-service等服务为例,客户端在请求各个服务的API时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等。
2.2 过滤器的分类
2.2.1 局部过滤器
局部过滤器是针对单个路由的过滤器。
2.2.1.1 内置局部过滤器
在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。
server:
port: 8000
spring:
cloud:
gateway:
routes:
- id: shop-product #路由的唯一标识,只要不重复都可以,如果不写默认的是通过UUID产生的,一般写成被路由服务的名称
uri: lb://shop-product #被路由的地址, lb代表lb协议
order: 0 #优先级,数字越小,优先级越高
predicates: # 断言,执行路由的判断条件,只有断言都为真,才会执行路由
- Path=/product/**
- Age=18,60
- id: shop-order
uri: lb://shop-order
order: 0
predicates:
- Path=/order/**
# 过滤器,可以再请求前或者请求后做一些动作
filters:
- setStatus=2000 #将返回的状态值修改为2000
- StripPrefix=1 #StripPrefix参数表示在将请求发送到下游之前从请求中剥离的路径个数。
#将网关添加到注册中心
nacos:
server-addr: localhost:8848
application:
name: my-gateway
2.2.2 全局过滤器
全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。
2.2.2.1 内置全局过滤器
2.2.2.2 自定义全局过滤器(权限统一校验)
在下面的案例中,将讲述如何编写自己的GlobalFilter,该GlobalFilter会校验请求中是否包含请求参数“token”,如果不包含请求参数“token”,则不转发路由;否则,执行正常的逻辑。代码如
package com.dhy.gateway.filter;
import lombok.val;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @program: springcloud-father
* @description: dengluquanxian
* @author: XXX
* @create: 2021-07-08 17:12
**/
@Component
public class LoginFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 获取token头
String token = request.getHeaders().getFirst("token");//获取token
if (!StringUtils.isEmpty(token)){
if("admin".equals(token)){
return chain.filter(exchange); //如果token存在,且输入的登录名与tokenz中的一直,放行
}
}
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//权限不足,需要登录
return null;
}
@Override
public int getOrder() {
return 0;
}
}