SpringCloud11-GateWay网关
SpringCloud系列文档地址: SpringCloud 进阶之路
zuul 1.x:https://github.com/Netflix/zuul/wiki
Spring Cloud GateWay官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 等技术开发的网关,Spring Cloud Gateway 旨在为微服务架构提供一种简单有效的、统一的 API 路由管理方式。
Spring Cloud Gateway 作为 Spring Cloud 生态系中的网关,其目标是替代 Netflix Zuul,它不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全、监控/埋点和限流等。
Spring Cloud Gateway 依赖 Spring Boot 和 Spring WebFlux,基于 Netty 运行。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。
一句话总结:Gateway使用的是Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。
1.3 网关GateWay在微服务中的位置
Route 是最核心的路由元素,它定义了ID,目标URI ,predicates的集合与filter的集合,如果Predicate聚合返回真,则匹配该路由
Predicate(断言)基于java8的函数接口Predicate,其输入参数类型ServerWebExchange,其作用就是允许开发人员根据当前的http请求进行规则的匹配,比如说http请求头,请求时间等,匹配的结果将决定执行哪种路由
Filter(过滤)指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改;
web请求通过一系列的匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精细化的控制,predicate就是我们的匹配条件,而filter则是一个无所不能的拦截器,有了这两个之后,再加上目标uri,就可以实现一个具体的路由了。
官网的工作流程图:
客户端向springcloudgateway发出请求。 如果在Gateway Handler Mapping(网关处理程序映射)中找到与请求相匹配的路由,则将其发送到Gateway Web Handler(网关Web处理程序)。 Handler再通过指定的过滤器链来将请求发送到我们实际的服务中执行业务逻辑,然后返回。过滤器被虚线分开的原因是过滤器可以在发送代理请求之前(pre)和之后(post)执行业务逻辑。 Filter在“Pre”类型的过滤器中可以做参数校验,权限校验,流量监控,日志输出和协议转换等操作;在“Post”类型的过滤器中可以做响应内容,响应头的修改,日志的输出,流量监控等操作。Filer的功能强大。
**
** 2.1 创建模块cloud-gateway-gateway9527
2.1.1 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>springcloud2020</artifactId> <groupId>com.zdw.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-gateway-gateway9527</artifactId> <dependencies> <!--gateway网关--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--gateway无需web和actuator,如果有的话启动会报错--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--热部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--引入自定义的公共包,回用到里面的实体--> <dependency> <groupId>com.zdw.springcloud</groupId> <artifactId>cloud-api-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>2.1.2 application.yml配置
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名称进行路由 routes: - id: payment_route # 路由的id,没有规定规则但要求唯一,建议配合服务名 #匹配后提供服务的路由地址 uri: http://localhost:8001 predicates: - Path=/payment/get/** # 断言,路径相匹配的进行路由 - id: payment_route2 uri: http://localhost:8001 predicates: - Path=/payment/lb/** #断言,路径相匹配的进行路由 eureka: instance: hostname: cloud-gateway-service client: fetch-registry: true register-with-eureka: true service-url: # 单击版 defaultZone: http://localhost:7001/eureka # 集群版 # defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka我们现在是要在服务 cloud-provider-payment8001 外面包上一层9527的网关,所以我们配置的路由是针对cloud-provider-payment8001服务的,等会测试的时候,需要把cloud-provider-payment8001启动。
2.1.3 主启动类
package com.zdw.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; /** * @author ZDW * @create 2020-08-19 16:00 */ @SpringBootApplication //作为eureka的客户端注册进eureka中 @EnableEurekaClient public class GatewayMain9527 { public static void main(String[] args) { SpringApplication.run(GatewayMain9527.class,args); } }2.1.4 测试 1、启动cloud-eureka-server7001服务 2、启动cloud-provider-payment8001服务 3、启动cloud-gateway-gateway9527服务
浏览器访问: 添加网关前,直接访问8001:http://localhost:8001/payment/get/1 是可以访问到数据的 添加网关后,直接访问9527:http://localhost:9527/payment/get/1 也是可以访问成功的 通过配置路由和断言,在8001的外面包了一层9527的网关,对外暴露的是9527的地址。
2.2 通过Java代码配置路由 在上面的application.yml文件中,我们是配置了路由的,不过除了这种方式外,还支持通过Java配置类的方式来进行路由配置:
业务需求:通过9527网关访问到外网的百度新闻网址
2.2.1 配置类:GatewayConfig
package com.zdw.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 ZDW * @create 2020-08-19 16:18 * 通过Java配置类来进行路由的配置 */ @Configuration public class GatewayConfig { /** * 配置了一个id为route-name的路由规则 * 当访问地址 http://localhost:9527/guonei时会自动转发到地址: http://news.baidu.com/guonei * @param builder * @return */ @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { RouteLocatorBuilder.Builder routes = builder.routes(); routes.route("path_route_zdw1", r -> r.path("/guonei") .uri("http://news.baidu.com/guonei")).build(); return routes.build(); } @Bean public RouteLocator customRouteLocator2(RouteLocatorBuilder builder) { RouteLocatorBuilder.Builder routes = builder.routes(); routes.route("path_route_zdw1", r -> r.path("/guoji") .uri("http://news.baidu.com/guoji")).build(); return routes.build(); } }2.2.2 测试 重启服务:cloud-gateway-gateway9527,然后浏览器访问:http://localhost:9527/guonei 会被路由到百度:http://news.baidu.com/guonei
3、通过服务名实现动态路由 在上面的application.yml中,我们配置的路由的uri是:uri: http://localhost:8001,如果服务是多个,比如有8001和8002,此时我们这里写的8001,那么就只会把请求转发到8001,这样明显不太好,无法实现负载均衡。所以接下来,要通过配置,动态的实现路由,可以把请求转发到8001和8002两个服务。
默认情况下Gatway会根据注册中心注册的服务列表, 以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能;
3.1 修改application.yml
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名称进行路由 routes: - id: payment_route # 路由的id,没有规定规则但要求唯一,建议配合服务名 #匹配后提供服务的路由地址 #uri: http://localhost:8001 # lb是固定的,表示的是协议lb,表示启用Gateway的负载均衡功能, # cloud-payment-service是cloud-provider-payment8001和cloud-provider-payment8002注册到eureka注册中的服务名称 uri: lb://cloud-payment-service predicates: - Path=/payment/get/** # 断言,路径相匹配的进行路由 - id: payment_route2 #uri: http://localhost:8001 uri: lb://cloud-payment-service predicates: - Path=/payment/lb/** #断言,路径相匹配的进行路由 eureka: instance: hostname: cloud-gateway-service client: fetch-registry: true register-with-eureka: true service-url: # 单击版 defaultZone: http://localhost:7001/eureka # 集群版 # defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka3.2 测试 1、启动cloud-eureka-server7001服务 2、启动cloud-provider-payment8001服务和cloud-provider-payment8002服务(这两个服务配置的是eureka的单机版) 3、启动cloud-gateway-gateway9527服务
浏览器访问:http://localhost:9527/payment/lb 可以看到8001和8002交替进行
Predicate 是基于java8的函数接口Predicate,其输入参数类型ServerWebExchange,其作用就是允许开发人员根据当前的http请求进行规则的匹配,比如说http请求头,请求时间等,匹配的结果将决定执行哪种路由;
在启动9527服务的日志中,可以看到:
可以看到这些都是路由谓词工厂 (Route Predicate Factories)的相关谓词。
4.1 Route Predicate Factories是什么 Spring Cloud Gateway的路由谓词工厂 (Route Predicate Factories),路由谓词工厂的作用是:符合Predicate的条件,就使用该路由的配置,否则就不管。
Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。 Spring Cloud Gateway包括许多内置的Route PredicateI。所有这些Predicate都与HTTP请求的不同属性匹配。多个RoutePredicate.工厂可以进行组合。 Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象, Predicate对象可以赋值给Route。 Spring Cloud Gateway包含许多内置的Route Predicate Factories。 所有这些谓词都匹配HTTP请求的不同属性,多种谓词工厂可以互相组合,并通过逻辑and。
4.2 常用的Route Predicate Factories 参考:https://www.cnblogs.com/bjlhx/p/9785926.html
4.2.1 After Route Predicate
spring: cloud: gateway: routes: - id: after_route uri: https://example.org predicates: # 这个是美国时区,表示要在这个时间只后,上面的路由才会生效 - After=2017-01-20T17:42:47.789-07:00[America/Denver]上面- After配置的时间是美国时间,可以使用下面的代码得到当前时区的时间:
System.out.println(ZonedDateTime.now()); //2020-08-19T17:01:44.755+08:00[Asia/Shanghai]其他的省略。。。。
可以参考:https://blog.csdn.net/forezp/article/details/85057268
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂 类来产生。
5.1 常用的Filter 5.1.1 单个路由的gateway filter 它在配置文件中的写法同predict类似;
过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。过滤器可以限定作用在某些特定请求路径上。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂。
GatewayFilter工厂同上一篇介绍的Predicate工厂类似,都是在配置文件application.yml中配置,遵循了约定大于配置的思想,只需要在配置文件配置GatewayFilter Factory的名称,而不需要写全部的类名,比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader,而不是全部类名。在配置文件中配置的GatewayFilter Factory最终都会相应的过滤器工厂类处理。
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories
5.1.2 针对于所有路由的global gateway filer GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#global-filters
**
** 自定义的Filter需要实现GlobalFilter和Ordered接口;
作用:全局日志记录和统一网关鉴权等一系列操作;
5.2.1 需求 判断请求中是否有参数username,如果没有,就提示非法用户
5.2.2 自定义MyLogGatewayFilter
package com.zdw.springcloud.filter; import lombok.extern.slf4j.Slf4j; 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; import java.util.Date; /** * @author ZDW * @create 2020-08-19 17:38 */ @Component @Slf4j public class MyLogGatewayFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("****** come in MyLogGateWayFilter: " + new Date()); String uname = exchange.getRequest().getQueryParams().getFirst("uname"); if(uname == null) { log.info("*****用户名为null,非法用户,o(╥﹏╥)o"); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } /** * 加载过滤器的顺序,数字越小,优先级越高 */ @Override public int getOrder() { return 0; } }5.2.3 测试 1、启动cloud-eureka-server7001服务 2、启动cloud-provider-payment8001服务和cloud-provider-payment8002服务(这两个服务配置的是eureka的单机版) 3、启动cloud-gateway-gateway9527服务
浏览器访问:http://localhost:9527/payment/lb ,没有任何响应,可以看到后台打印了:*****用户名为null,非法用户,o(╥﹏╥)o ,这说明已经被我们的过滤器把请求拦截了
浏览器访问:http://localhost:9527/payment/lb?uname=z3 可以得到正常的响应,端口8001和8002交替显示 }
/** * 加载过滤器的顺序,数字越小,优先级越高 */ @Override public int getOrder() { return 0; }1、启动cloud-eureka-server7001服务 2、启动cloud-provider-payment8001服务和cloud-provider-payment8002服务(这两个服务配置的是eureka的单机版) 3、启动cloud-gateway-gateway9527服务
浏览器访问:http://localhost:9527/payment/lb ,没有任何响应,可以看到后台打印了:*****用户名为null,非法用户,o(╥﹏╥)o ,这说明已经被我们的过滤器把请求拦截了
浏览器访问:http://localhost:9527/payment/lb?uname=z3 可以得到正常的响应,端口8001和8002交替显示
参考网址: https://blog.csdn.net/zengdongwen/article/details/108120495 本次课程的讲解是尚硅谷的周阳老师,如有需要请购买正版书籍或网上到尚硅谷官网进行学习。