Gateway
Gateway 介绍
看一个需求,引出网关服务
1、有一个前后端分离项目, 分析如图

2、使用网关服务, 重构项目架构

Gateway 网络拓扑图(重要)

Gateway 是什么

- Gateway 是在 Spring 生态系统之上构建的 API 网关服务,基于 Spring ,Spring Boot 和 Project Reactor 等技术。
- Gateway 旨在提供一种简单而有效的方式来对 API 进行路由,以及提供一些强大的过滤器功能, 例如∶熔断、限流、重试等
官网
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
Gateway 核心功能
鉴权
流量控制
熔断
日志监控
反向代理
Gateway VS Zuul
Gateway 和 Zuul 区别
- SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul
- SpringCloud Gateway 是基于 Spring WebFlux 框架实现的
- Spring WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty , 提升了网关性能
Gateway 特性
Spring Cloud Gateway 基于 Spring Framework(支持 Spring WebFlux),Project Reactor 和 Spring Boot 进行构建,具有如下特性:
- 动态路由
- 可以对路由指定 Predicate(断言) 和 Filter(过滤器)
- 集成Hystrix的断路器功能
- 集成 Spring Cloud 服务发现功能
- 请求限流功能
- 支持路径重写
Gateway 基本原理
Gateway 核心组件

- web 请求,通过一些匹配条件,定位到真正的服务节点/微服务模块,在这个转发过程的前后,进行一些精细化控制。
- predicate: 就是匹配条件
- filter: 可以理解为是网关的过滤机制。有了 predicate 和 filter,再加上目标 URL.就可以实现一个具体的路由
Route(路由)
路由是构建网关的基本模块,它由 ID,目标 URI,一系列的断言和过滤器组成, 如果断言为 true 则匹配该路由。
Predicate(断言)
对 HTTP 请求中的所有内容(例如请求头或请求参数)进行匹配,如果请求与断言相匹配则进行路由。
简单举例:比如配置路径, - Path=/member/get/**
断言,路径相匹配的进行路由转发 , 如果 Http 请求的路径不匹配, 则不进行路由转发.
Filter(过滤)
使用过滤器,可以在请求被路由前或者之后对请求进行处理。
可以理解成在对 Http 请求断言匹配成功后, 可以通过网关的过滤机制, 对 Http 请求处理。
简单举例:
filters:
- AddRequestParameter=color, blue
#过滤器在匹配的请求头加上一对请求头,名称为 color 值为 blue, 比如原来的 http 请求是 http://localhost:10000/member/get/1 ==过滤器处理=> http://localhost:10000/member/get/1?color=blue
工作机制

- 客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
- Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
- 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前("pre")或之后 ("post")执行业务逻辑。
- Filter 在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等。
- 在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
一句话:路由转发+执行过滤器链。
搭建Gateway微服务
应用实例
需求分析/图解

- 通过网关暴露的接口,实现调用真正的服务
- 网关本身也是一个微服务模块
代码实现
参考 member-service-consumer-80 创建 e-commerce-gateway-20000(具体步骤参考以前)
修改 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>e-commerce-center</artifactId>
<groupId>com.lzw</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>e-commerce-gateway-20000</artifactId>
<!--引入相关的依赖-->
<dependencies>
<!--引入gateway-starter 网关场景启动器starter-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--引入eureka-client 场景启动器starter: 使用版本仲裁-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--特别说明:
1. 不要引入 spring-boot-starter-web,spring-boot-starter-actuator
否则会出现冲突
2. 因为gateway 是一个服务网关,不需要web
-->
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引入e_commerce_center-common-api-->
<!--version=1.0-SNAPSHOT-->
<!--groupId=com.lzw.springcloud-->
<!--artifactId=e_commerce_center-common-api-->
<dependency>
<groupId>com.lzw</groupId>
<artifactId>e_commerce_center-common-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
创建 application.yml(重点核心)
server:
port: 20000
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
# gateway 最终访问的url 是 url=uri+Path
#匹配后提供服务的路由地址: 也可以是外网 http://www.baidu.com
#比如: 客户端/浏览器请求 url http://localhost:20000/member/get/1
#如果根据Path匹配成功 最终访问的url/转发url 就是 url=http://localhost:10001/member/get/1
#如果匹配失败, 则有gateway返回404信息
#疑问: 这里配置的 uri 是固定的,在当前这种情况其实可以没有有Eureka Server,后面使用灵活方式
# 配置,就会使用到Eureka Server
uri: http://localhost:10001
predicates: #断言,可以有多种形式
- Path=/mebmer/get/**
#配置Eureak-Client
eureka:
instance:
hostname: e-commerce-service
client:
register-with-eureka: true #将自己注册到Eureka-Server
fetch-registry: true
service-url:
#表示将自己注册到哪个eureka-server
#为了方便,使用Eureka Server的单机环境测试
defaultZone: http://eureka9001.com:9001/eureka
创建主启动类
package com.lzw.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author LiAng
*/
@SpringBootApplication
@EnableEurekaClient
public class GateWayApplication20000 {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication20000.class, args);
}
}
启动测试
增加Gateway路由
修改 application.yml
server:
port: 20000
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
# gateway 最终访问的url 是 url=uri+Path
#匹配后提供服务的路由地址: 也可以是外网 http://www.baidu.com
#比如: 客户端/浏览器请求 url http://localhost:20000/member/get/1
#如果根据Path匹配成功 最终访问的url/转发url 就是 url=http://localhost:10001/member/get/1
#如果匹配失败, 则有gateway返回404信息
#疑问: 这里配置的 uri 是固定的,在当前这种情况其实可以没有有Eureka Server,后面使用灵活方式
# 配置,就会使用到Eureka Server
uri: http://localhost:10001
predicates: #断言,可以有多种形式
- Path=/memeber/**
- id: member_route02 # 路由id,程序员自己配置,要求唯一
uri: http://localhost:10001
predicates:
# 这是如果客户端/浏览器 访问gateway的url http://localhost:20000/member/save
#匹配Path成功 最终访问的url 就是 http://localhost:10001/member/save
- Path=/member/save
注意事项和细节
因为我们的 member 的 controller 的方法参数使用了@RequestBody
@PostMapping("/member/save")
public Result save(@RequestBody Member member) {
log.info("service-provider member={}",member);
int affected = memberService.save(member);
if (affected > 0) { //说明添加成功
return Result.success("添加会员成功", affected);
} else {
return Result.error("401", "添加会员失败");
}
}
所以,在使用 postman 时,需要使用 json 格式发送数据, 否则会报 400 错误。
@RequestBody 的作用是: 将前端发送的 json 数据封装成对象, 如果发送的不是 json 数据,会报错误
二说 Gateway 路由配置
方式 2: 编写配置类注入(了解)
先注释掉 application.yml 对网关路由部分
创建 GateWayRouteConfig.java
package com.lzw.springcloud.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author LiAng
* GateWayRouteConfig: 配置类-配置路由
*/
@Configuration
public class GateWayRouteConfig {
//配置注入路由
/**
* 在理解通过配置类注入/配置 路由,可以对照前面的application.yml来对比理解
* cloud:
* gateway:
* routes: #配置路由,可以配置多个 List<RouteDefinition> routes
* - id: member_route01 # 路由id,程序员自己配置,要求唯一
* uri: http://localhost:10001
* predicates: #断言,可以有多种形式
* - Path=/member/get/**
*/
@Bean
public RouteLocator myRouteLocator03(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//方法写完
//1. 下面的方法我们分别指定了id , uri 和path
//2. Function<PredicateSpec, Route.AsyncBuilder> fn
//(1) 是一个函数式接口
//(2) 接收的类型是 PredicateSpec ,返回的类型是 Route.AsyncBuilder
//(3) r -> r.path("/member/get/**")
// .uri("http://localhost:10001") 就是lambda表达式
return routes.route("member_route03", r -> r.path("/member/get/**")
.uri("http://localhost:10001/member")).
build();
}
}
测试 http://localhost:20000/member/get/1
测试完,恢复 application.yml
,注释掉 GateWayRouteConfig
动态路由
需求分析/图解

代码实现
修改 e-commerce-gateway-20000 的 application.yml
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
#疑问: 这里配置的 uri 是固定的,在当前这种情况其实可以没有有Eureka Server,后面使用灵活方式
# 配置,就会使用到Eureka Server
# uri: http://localhost:10001
# 1. lb: 协议名 , member-service-provider 注册到eureka server 服务名(小写)
# 2. 默认情况下,负载均衡算法是轮询
uri: lb://member-service-provider
predicates: #断言,可以有多种形式
- Path=/member/get/**
- id: member_route02 # 路由id,程序员自己配置,要求唯一
# uri: http://localhost:10001
uri: lb://member-service-provider
predicates: #断言,可以有多种形式
# 这是如果客户端/浏览器 访问gateway的url http://localhost:20000/member/save
#匹配Path成功 最终访问的url 就是 http://localhost:10001/member/save
- Path=/member/save
测试
启动 e-commerce-eureka-server-9001
启动 member-service-provider-10001
启动 member-service-provider-10002
启动 e-commerce-gateway-20000
注意事项和细节
配置好动态路由后 Gateway 会根据注册中心上微服务名,为请求创建动态路由,实现动态路由功能
使用的 lb 协议支持负载均衡-轮询算法
配置自己的负载均衡算法, 测试完毕恢复成原来的轮询算法
package com.lzw.springcloud.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author LiAng
* RibbonRule:配置类,配置自己的负载均衡算法
*/
@Configuration
public class RibbonRule {
//配置注入自己的负载均衡算法
@Bean
public IRule myRibbonRule(){
return new RandomRule();
}
}
Predicate/断言
基本介绍
Predicate 就是一组匹配规则,当请求匹配成功,就执行对应的 Route, 匹配失败,放弃处理/转发。
Route Predicate Factories
文档地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories
Spring Cloud Gateway包括许多内置的Route Predicate工厂, 所有这些Predicate都与 HTTP请求的不同属性匹配, 可以组合使用
Spring Cloud Gateway 创建 Route 对象时,使用RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给Route
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合
After Route Predicate
需求
只有 2022-10-11 21:43:00 之后的请求才进行匹配/转发, 不满足该条件的,不处理
修改 e-commerce-gateway-20000 的 application.yml
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
uri: lb://member-service-provider
predicates: #断言,可以有多种形式
- Path=/member/get/**
- After=2022-10-11T21:43:00.000+08:00[Asia/Shanghai]
如何获取时间格式, 创建一个测试类,来获取当前时间,再根据需要修改
package com.lzw.springcloud;
import java.time.ZonedDateTime;
/**
* @author LiAng
*/
public class T2 {
public static void main(String[] args) {
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
}
}
Before Route Predicate
需求
只有 2022-11-11 21:43:00 之前的请求才进行匹配/转发, 不满足该条件的,不处理
修改 e-commerce-gateway-20000 的 application.yml
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
uri: lb://member-service-provider
predicates: #断言,可以有多种形式
- Path=/member/get/**
- Before=2022-11-11T21:43:00.000+08:00[Asia/Shanghai]
Between Route Predicate
需求
只有 2020-11-18 12:35:50 和 2022-11-18 12:35:50 之间的请求才进行匹配/转发, 不满足该条件的,不处理
修改 e-commerce-gateway-20000 的 application.yml
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
uri: lb://member-service-provider
predicates: #断言,可以有多种形式
- Path=/member/get/**
- Between=2020-11-18T12:35:50.387+08:00[Asia/Shanghai],2022-11-18T12:35:50.387+08:00[Asia/Shanghai]
Cookie Route Predicate
需求
请求带有 cookie 键: user
;值: lzw
才匹配/断言成功

chocolate
是 cookie 名字;ch.p
是 cookie 的值,是按照正则表达式来匹配的
修改 e-commerce-gateway-20000 的 application.yml
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
uri: lb://member-service-provider
predicates: #断言,可以有多种形式
- Path=/member/get/**
- Cookie=user,lzw

Header Route Predicate
需求
请求头 Header 有 X-Request-Id
, 并且值为 hello 才匹配/断言成功

X-Request-Id
是 header 的名称, \d+
是一个正则表达式
修改 e-commerce-gateway-20000 的 application.yml
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
uri: lb://member-service-provider
predicates: #断言,可以有多种形式
- Path=/member/get/**
- Header=X-Request-Id, hello

Host Route Predicate
需求
请求 Host 是**.liang.**
才匹配/断言成功 , 比如 Host www.liang.com
修改 e-commerce-gateway-20000 的 application.yml
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
uri: lb://member-service-provider
predicates: #断言,可以有多种形式
- Path=/member/get/**
- Host=**.liang.**
Host 可以有多个, 使用逗号间隔

Method Route Predicate
需求
请求是 Get 方式才匹配/断言成功
修改 e-commerce-gateway-20000 的 application.yml
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
uri: lb://member-service-provider
predicates: #断言,可以有多种形式
- Path=/member/get/**
- Method=GET
请求方式可以有多个, 使用逗号间隔
Path Route Predicate
参考文档 https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories

Path 可以有多个, 使用逗号间隔
Query Route Predicate
需求
请求有参数 email,并且满足电子邮件的基本格式,才能匹配/断言成功

red 是参数名 gree. 是值, 支持正则表达式
修改 e-commerce-gateway-20000 的 application.yml
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
uri: lb://member-service-provider
predicates: #断言,可以有多种形式
- Path=/member/get/**
- Query=email, [\w-]+@([a-zA-Z]+\.)+[a-zA-Z]+

RemoteAddr Route Predicate
需求
请求的 IP 是 127.0.0.1, 才能匹配/断言成功
修改 e-commerce-gateway-20000 的 application.yml
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
uri: lb://member-service-provider
predicates: #断言,可以有多种形式
- Path=/member/get/**
- RemoteAddr=127.0.0.1
Weight Route Predicate
参考文档 https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-weight-route-predicates-factory
Filter/过滤器
基本介绍
文档地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories
- 路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应
- Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生
类型
GatewayFilter

GlobalFilter
文档地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#global-filters
GatewayFilter 使用
开发直接使用 GatewayFilter 较少,一般是自定义过滤器
修改 e-commerce-gateway-20000 的 application.yml
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 # 路由id,程序员自己配置,要求唯一
# gateway 最终访问的url 是 url=uri+Path
#匹配后提供服务的路由地址: 也可以是外网 http://www.baidu.com
#比如: 客户端/浏览器请求 url http://localhost:20000/member/get/1
#如果根据Path匹配成功 最终访问的url/转发url 就是 url=http://localhost:10001/member/get/1
#如果匹配失败, 则有gateway返回404信息
#疑问: 这里配置的 uri 是固定的,在当前这种情况其实可以没有有Eureka Server,后面使用灵活方式
# 配置,就会使用到Eureka Server
# uri: http://localhost:10001
# 1. lb: 协议名 , member-service-provider 注册到eureka server 服务名(小写)
# 2. 默认情况下,负载均衡算法是轮询
uri: lb://member-service-provider
filters:
- AddRequestParameter=color, blue #过滤器工厂会在匹配的请求头加上一对请求头, 名称为 color 值为 blue
- AddRequestParameter=age, 18 #过滤器工厂会在匹配的请求头加上一对请求头,名称为 age 值为 18
predicates: #断言,可以有多种形式
- Path=/member/get/**
修改 member-service-provider-10001\src\main\java\com\lzw\springcloud\controller\MemberController.java
@GetMapping("/member/get/{id}")
public Result getMemberById(@PathVariable("id") Long id, HttpServletRequest request) {
String color = request.getParameter("color");
Member member = memberService.queryMemberById(id);
//使用Result把查询到的结果返回
if (member != null) {
return Result.success("查询会员成功 member-service-provider-10001;" + color, member);
} else {
return Result.error("402","ID= " + id + "不存在");
}
}

自定义 GlobalFilter
需求分析
- 自定义全局 GlobalFilter 过滤器
- 如果请求参数
user=liang , pwd=123456
则放行, 否则不能通过验证
代码实现
在 e-commerce-gateway-20000 创建 CustomGateWayFilter.java
package com.lzw.springcloud.filter;
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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author LiAng
*/
@Component
public class CustomGateWayFilter implements GlobalFilter, Ordered {
//filter是核心的方法,将我们的过滤的业务,写在该方法中
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("------CustomGateWayFilter------");
//先获取到对应的参数值
//比如 http://localhost:20000/member/get/1?user=liang&pwd=123456
String user = exchange.getRequest().getQueryParams().getFirst("user");
String pwd = exchange.getRequest().getQueryParams().getFirst("pwd");
if(!("liang".equals(user) && "123456".equals(pwd))) {//如果不满足条件
System.out.println("-----非法用户-----");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//回应
return exchange.getResponse().setComplete();
}
//验证通过, 放行
return chain.filter(exchange);
}
//order 表示过滤器执行的顺序, 数字越小, 优先级越高
@Override
public int getOrder() {
return 0;
}
}

测试完毕,记得代码恢复到测试前