Bootstrap

SpringCloud05_路由网关(Gateway)

zuul已经停止维护,主要学习Gateway。
zuul是一个基于servelt2.5使用阻塞架构,不支持长连接的设计模式,请求线程被阻塞到工作线程完成,性能较差;

1、Gateway

1.1 基本介绍

SpringCloud GateWay基于WebFlux框架实现。目标是提供统一的路由方式且基于Filter链的方式提供网关的基本功能,如:安全、监控/指标、限流等。
他使用非阻塞式API,支持WebScoket,并与Spring紧密集成。

特性:

  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定断言和过滤器;
  • 继承Hystrix断路器;
  • 请求限制功能;
  • 支持路径重写;

架构中的位置
在这里插入图片描述
基本概念:
(1)路由(Route):构建网关的基本模块,由ID,目标URI,一系列断言和过滤器组成,如果断言为true,则匹配该路由;
(2)断言(Predicate):开发人员可以匹配HTTP请求中所有的内容,如请求头或参数等,如果请求与断言相匹配则进行路由;
(3)过滤(Filter):使用过滤器,可以在请求被路由前或者后对请求进行修改;

1.2 环境搭建

新建一个Module,命名为gateway,需要按照cilent注册进注册中心;

  • pom.xml
    采坑11:要去掉与springboot启动有关的依赖,否则会发生冲突;
<?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>cloud2022</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>gateway</groupId>
    <artifactId>gateway</artifactId>

    <dependencies>
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--一般基础配置类-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
  • application.yml :
    采坑11:项目过多,可能导致配置文件yml下标变红,无法识别----->解决方法
server:
  port: 9527

eureka:
  client:
    register-with-eureka: true #是否要注册
    fetchRegistry: true #是否抓取注册信息
    service-url:
#      defaultZone: http://localhost:7001/eureka
      defaultZone: http://eureka7001:7001/eureka #,http://eureka7002:7002/eureka

spring:
  application:
    name : gateway9527
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          #uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
#          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由
            #- After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai]
            #- Cookie=username,zzyy
            #- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#  datasource:
#    url: jdbc:mysql://localhost:3306/springboot-mybatisplus?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
#    username: root
#    password: 123456
#    driver-class-name=com: mysql.cj.jdbc.Driver
##    type: com.alibaba.druid.pool.DruidDataSource


  • 启动类
package com.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication(scanBasePackages = "com.gateway")
@EnableEurekaClient
public class GateWayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GateWayApplication.class, args);
    }

}

  • 目录
    在这里插入图片描述
  • 访问:
    在这里插入图片描述
    ——访问:http://localhost:8001/payment/1
    在这里插入图片描述
    ——访问:http://localhost:9527/payment/1
    在这里插入图片描述

2、Route

2.1 GateWay网关路由两种配置方式**

(1)application.yml配置

spring:
  application:
    name : gateway9527
  cloud:
    gateway:
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          #uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
#          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由
         

(2)代码注入RouteLocator的Bean

2.2 动态路由(通过微服务实现)

之前的提供服务的路由地址是写死的,无法实现负载均衡和动态扩容;

  • 注册中心
    在这里插入图片描述
    第一步:开启动态创建路由功能:gateway. discovery. locator.enabled: = true
    第二步:更换名称:uri: lb://payment
  • application.yml
server:
  port: 9527

eureka:
  client:
    register-with-eureka: true #是否要注册
    fetchRegistry: true #是否抓取注册信息
    service-url:
#      defaultZone: http://localhost:7001/eureka
      defaultZone: http://eureka7001:7001/eureka #,http://eureka7002:7002/eureka

spring:
  application:
    name : gateway9527
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://payment #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://payment #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由
      
3、Predicate

在这里插入图片描述

上图为启动网关时后台日志,在之前的网关配置中,只使用到了path配置。多个路由配置类似于查询条件的and,多个and满足之后才进行路由转发;
Spring Cloud Gateway将路由作为Spring WebFlux HandlerMapping基础结构的一部分进行匹配。Spring Cloud Gateway包含许多内置的路由断言Factories。这些断言都匹配HTTP请求的不同属性。多个路由断言Factories可以通过 and 组合使用。

3.1 After 路由断言 Factory
After Route Predicate Factory采用一个参数——日期时间。在该日期时间之后发生的请求都将被匹配。

application.yml:

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: http://example.org
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]

3.2 Before 路由断言 Factory
Before Route Predicate Factory采用一个参数——日期时间。在该日期时间之前发生的请求都将被匹配。

predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]

3.3 Between 路由断言 Factory

Between 路由断言 Factory有两个参数,datetime1和datetime2。在datetime1和datetime2之间的请求将被匹配。datetime2参数的实际时间必须在datetime1之后。

predicates:
- Between=2020-03-20T17:42:47.789-07:00[America/Denver], 2020-03-31T17:42:47.789-07:00[America/Denver]

3.4 Cookie 路由断言 Factory

Cookie 路由断言 Factory有两个参数,cookie名称和正则表达式。请求包含次cookie名称且正则表达式为真的将会被匹配。 cookieName=value

我们先设置一个cookie响应回给浏览器:

Cookie cookie = new Cookie(“myCookie”,“zhangshaohan”);

predicates:
- Cookie=myCookie, zhangshaohan

3.5 Header 路由断言 Factory

Header 路由断言 Factory有两个参数,header名称和正则表达式。请求包含次header名称且正则表达式为真的将会被匹配。header=value

predicates:
- Header=X-Request-Id, \d+

3.6 Host 路由断言 Factory

Host 路由断言 Factory包括一个参数:host name列表。使用Ant路径匹配规则, .作为分隔符。

访问的主机匹配http或者https, baidu.com 默认80端口, 就可以通过路由。 多个,号隔开

predicates:
- Host=**.baid.com,**.localhost:8080

3.7 Method 路由断言 Factory

Method 路由断言 Factory只包含一个参数: 需要匹配的HTTP请求方式

predicates:
- Method=GET #所有GET请求都将被路由

3.8 Path 路由断言 Factory

Path 路由断言 Factory 有2个参数: 一个Spring PathMatcher表达式列表和可选matchOptionalTrailingSeparator标识 .

predicates:
- Path=/foo/{segment},/bar/{segment}

例如: /foo/1 or /foo/bar or /bar/baz的请求都将被匹配。

3.9 Query 路由断言 Factory

Query 路由断言 Factory 有2个参数: 必选项 param 和可选项 regexp.

predicates:
- Query=green #如果请求包含green查询参数,则路由匹配。
#-------------------------------------------------------
predicates:
- Query=red, gree. #如果请求参数里包含red参数,并且值匹配为gree. 表达式,则将会被路由

3.10 RemoteAddr 路由断言 Factory

RemoteAddr 路由断言 Factory的参数为 一个CIDR符号(IPv4或IPv6)字符串的列表,最小值为1,例如192.168.0.1/16(其中192.168.0.1是IP地址并且16是子网掩码)。

 predicates:
 - RemoteAddr=192.168.1.1/24 #如果请求的remote address 为 192.168.1.10则将被路由修改远程地址的解析方式

3.11 重量路由断言 Factory

该Weight路线谓词工厂有两个参数:group和weight(一个int)。权重是按组计算的。以下示例配置权重路由谓词:

gateway:
  routes:
  - id: weight_high
	uri: https://weighthigh.org
	predicates:
	- Weight=group1, 8
    - id: weight_low
    uri: https://weightlow.org
    predicates:
    - Weight=group1, 2

这条路线会将大约80%的流量转发到weighthigh.org,将大约20%的流量转发到weightlow.org。

4、Filter

使用过滤器,可以在请求被路由前或者后对请求进行修改。
4.1 生命周期Only Two:pre 、post

  • PRE: 这种过滤器在请求被路由之前调用,一般用于鉴权、限流等
  • POST:这种过滤器在路由到微服务以后执行,一般用于修改响应结果,比如增加header信息、打点结果日志。

4.2 种类Only Two:GatewayFilter(单一)、GlobalFilter(全局)

  • 局部过滤器GatewayFilter:应用在某个路由上,每个过滤器工厂都对应一个实现类,并且这些类的名称必须以 GatewayFilterFactory 结尾。
  • 全局过滤器:作用全部路由上。
  filters:
        - AddRequestHeader=X-Request-Foo, Bar

#这将为所有匹配请求的下游请求头部添加 X-Request-Foo:Bar。

4.3 自定义过滤器

自定义过滤器,实现implements GlobalFilter, Ordered接口,@Component
注入。

package com.gateway.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;

import java.util.Date;

@Component
public class GatewayLogFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("COME IN......"+new Date());
        String username = exchange.getRequest().getQueryParams().getFirst("username");
        if(username == null){
            System.out.println("用户名为空");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() { //加载顺序
        return 0;
    }
}

不带username访问:
在这里插入图片描述
在这里插入图片描述
带username访问:在这里插入图片描述

;