网关的主要作用
- 协议转换,路由转发
- 流量聚合,对流量进行监控,日志输出
- 作为整个系统的前端工程,对流量进行控制,有限流的作用
- 作为系统的前端边界,外部流量只能通过网关才能访问系统
- 可以在网关层做权限的判断
- 可以在网关层做缓存
gateway的请求过程
客户端向Spring Cloud Gateway发出请求。 如果Gateway Handler Mapping确定请求与路由匹配(这个时候就用到predicate),则将其发送到Gateway web handler处理。 Gateway web handler处理请求时会经过一系列的过滤器链。 过滤器链被虚线划分的原因是过滤器链可以在发送代理请求之前或之后执行过滤逻辑。 先执行所有“pre”过滤器逻辑,然后进行代理请求。 在发出代理请求之后,收到代理服务的响应之后执行“post”过滤器逻辑。这跟zuul的处理过程很类似。在执行所有“pre”过滤器逻辑时,往往进行了鉴权、限流、日志输出等功能,以及请求头的更改、协议的转换;转发之后收到响应之后,会执行所有“post”过滤器的逻辑,在这里可以响应数据进行了修改,比如响应头、协议的转换等。
三个关键字
- Route: 请求到网关之后转发到那个服务。使用ID标识,路由到哪里的Url地址。还有断言和过滤器的集合。
- Predicate(断言): 是java8的断言函数,由开发人员去匹配当此请求的任何信息。根据请求信息路由到那个服务。
- Fiilter(过滤器): 请求和响应都可以在过滤器中都可以修改。
整合注册中心
- maven
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- application.yml
server:
port: 1105
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:1109
gateway:
discovery:
locator:
# true 表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。
#如果使用自定义的路由规则需要将这里设置为false 不然会有两个路由
enabled: true
# 是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了
lowerCaseServiceId: true
- 启动配置
@SpringBootApplication
@EnableDiscoveryClient
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
}
- 启动报错
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method modifyRequestBodyGatewayFilterFactory in org.springframework.cloud.gateway.config.GatewayAutoConfiguration required a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' in your configuration.
原因:
依赖冲突,spring-cloud-starter-gateway与spring-boot-starter-web和spring-boot-starter-webflux依赖冲突
解决:
排除 spring-boot-starter-web和spring-boot-starter-webflux依赖
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
- 增加一个web-demo工程注册到nacos就可以了
predicate(断言)简介
在上面的处理过程中,有一个重要的点就是讲请求和路由进行匹配,这时候就需要用到predicate,它是决定了一个请求走哪一个路由。
在上图中,有很多类型的Predicate,比如说时间类型的Predicated,当只有满足特定时间要求的请求会进入到此predicate中,并交由router处理;cookie类型的,指定的cookie满足正则匹配,才会进入此router;以及host、method、path、querparam、remoteaddr类型的predicate,每一种predicate都会对当前的客户端请求进行判断,是否满足当前的要求,如果满足则交给当前请求处理。如果有很多个Predicate,并且一个请求满足多个Predicate,则按照配置的顺序第一个生效。
//在web-demo写一个请求
@GetMapping(value = "/a")
public String a() {
return "demo a";
}
//gateway网关配置
routes:
#id标签配置的是router的id,每个router都需要一个唯一的id,
- id: api-a
uri: lb://web-demo
#当是get请求的时候跳转到web-demo服务,会将url后面的请求路径携带过去
predicates:
- Method=GET
//url访问 http://localhost:1105/a
Fiilter
当我们有很多个服务时,客户端请求各个服务的Api时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等。对于这种重复操作的工作,在微服务的上一层加一个全局的权限控制、限流、日志输出的Api Gatewat服务,然后再将请求转发到具体的业务服务层。这个Api Gateway服务就是起到一个服务边界的作用,外接的请求访问系统,必须先通过网关层。
Spring Cloud Gateway同zuul类似,有“pre”和“post”两种方式的filter。客户端的请求先经过“pre”类型的filter,然后将请求转发到具体的业务服务,收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端。
与zuul不同的是,filter除了分为“pre”和“post”两种方式的filter外,在Spring Cloud Gateway中,filter从作用范围可分为另外两种,一种是针对于单个路由的gateway filter,它在配置文件中的写法同predict类似;另外一种是针对于所有路由的global gateway filer。
- gateway提供的过滤器
//请求头增加参数
//web-demo服务增加请求
@GetMapping(value = "/demo")
public String addRequestHeader(@RequestHeader(value = "X-test", required = false) String username) {
System.out.println("X-test:" + username);
return "demo SUCCESS";
}
//网关配置
routes:
#id标签配置的是router的id,每个router都需要一个唯一的id,
- id: api-a
#lb就是负载均衡从注册中心获取WEB-DEMO
uri: lb://web-demo
predicates:
- Method=GET
filters:
#经过次过滤器的时候会在请求头上增加X-test值为test
- AddRequestHeader=X-test, test
//url访问 http://localhost:1105/demo
//访问路径重写
//web-demo服务增加请求
@GetMapping(value = "/demo/path")
public String demoPath() {
System.out.println("demoPath SUCCESS");
return "demoPath SUCCESS";
}
//网关配置
routes:
#id标签配置的是router的id,每个router都需要一个唯一的id,
- id: api-a
uri: lb://web-demo
predicates:
#根据路由断言
- Path=/a/**
filters:
#路径重写 网关访问的是/a/** 现在改写为/demo/**调用WEB-DEMO服务
- RewritePath=/a/(?<segment>.*),/demoPath/${segment}
//url访问 http://localhost:1105/a/path
自定义Fiilter
- 局部过滤器
使用GatewayFilter,将应用注册到单个路由或一个分组的路由上
//过滤器配置
@Component
@Slf4j
public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {
private static final String KEY = "flag";
/**
* 封装yml传进来的参数
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(KEY);
}
public RequestTimeGatewayFilterFactory() {
super(Config.class);
}
/**
* 实现拦截逻辑
*/
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
//pre过滤器
exchange.getAttributes().put("requestTimeGateway", System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
//post过滤器
Long startTime = exchange.getAttribute("requestTimeGateway");
if (startTime != null) {
log.debug("请求路径:{},耗时:{}ms",exchange.getRequest().getURI().getRawPath(),System.currentTimeMillis() - startTime);
if (config.isFlag()) {
log.debug("参数:{}",exchange.getRequest().getQueryParams());
}
}
})
);
};
}
/**
* 参数
*/
public static class Config {
private boolean flag;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
}
routes:
#id标签配置的是router的id,每个router都需要一个唯一的id,
- id: api-a
uri: lb://web-demo
predicates:
#根据路由断言
- Path=/a/**
filters:
#路径重写 网关访问的是/a/** 现在改写为/demo/**调用web-demo服务
- RewritePath=/a/(?<segment>.*),/demo/${segment}
#是否开启请求参数的输出
- RequestTime=false
- 全局过滤器
可用在鉴权,日志拦截等。无需在配置文件中配置,作用在所有路由上。最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
@Component
@Slf4j
public class TokenFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.debug("pre:token ");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.debug("post类型的过滤器");
}));
}
@Override
public int getOrder() {
return 0;
}
}