SpringCloud 详解(十一)

发布于 2022-04-21  120 次阅读


GateWay

基于 WebFlux 框架实现,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty(非阻塞异步)。提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能。

SpringCloud 详解(十一)插图

三大核心

  • Route(路由):路由是构建网关的基本模块,它由 ID、目标 URI、一系列的断言和过滤器组成。
  • Predicate(断言):开发人员可以匹配 HTTP 请求中的所有内容,如果请求与断言相匹配则进行路由。
  • Filter(过滤):可以在请求被路由之前或者之后对请求进行修改。

工作流程

SpringCloud 详解(十一)插图1

客户端向 SpringCloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 GatewayWeb Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前或之后执行业务逻辑。过滤之前可以做参数校验、权限校验、流量监控、日志输出和协议转换等,过滤之后可以做响应内容、响应头的修改,日志的输出和流量监控等。过滤器有着非常重要的作用。

核心逻辑:路由转发 + 执行过滤器链

搭建

准备工作

创建一个 Module,一个空的 Maven 项目。并导入下面的依赖。

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>ml.guest997</groupId>
        <artifactId>Commons</artifactId>
        <version>${project.version}</version>
    </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>

创建主启动类。

package ml.guest997;

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

@SpringBootApplication
@EnableEurekaClient
public class Gateway {
    public static void main(String[] args) {
        SpringApplication.run(Gateway.class, args);
    }
}

编写配置文件

server:
  port: 9527

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

eureka:
  instance:
    hostname: gateway
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

测试

分别启动 Eureka-Server、Payment 和 Gateway 模块后,浏览器访问:127.0.0.1:8001/payment/get/1,能够正常调用。再访问:127.0.0.1:9527/payment/get/1,发现也是能够正常调用的。(/payment/lb 路径同理)

Config 路由

package ml.guest997.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;

@Configuration
public class GatewayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("path_route_blog", r -> r.path("/articles").uri("https://guest997.ml/articles")).build();
        return routes.build();
    }
}

测试

分别启动 Eureka-Server 和 Gateway 模块后,浏览器访问:127.0.0.1:9527/articles,会发现请求被转发到了我的博客地址。

动态路由

spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true     #利用微服务名进行路由。
      routes:
        - id: payment_routh     #路由的 ID,没有固定规则但要求唯一,建议配合服务名。
#          uri: http://localhost:8001     #提供服务的路由地址
          uri: lb://payment
          predicates:           #断言,相匹配的才进行路由
            - Path=/payment/get/**
        - id: payment_routh2
#          uri: http://localhost:8001
          uri: lb://payment
          predicates:
            - Path=/payment/lb

测试

分别启动 Eureka-Server、Payment、Payment2 和 Gateway 模块后,浏览器多次访问:127.0.0.1:9527/payment/lb,会发现端口是互相切换的。

常用断言

  • The After Route Predicate Factory
  • The Before Route Predicate Factory
  • The Between Route Predicate Factory
  • The Cookie Route Predicate Factory
  • The Header Route Predicate Factory
  • The Host Route Predicate Factory
  • The Method Route Predicate Factory
  • The Path Route Predicate Factory
  • The Query Route Predicate Factory
predicates:            #能够使用正则表达式
#表示在什么时间之前和之后和之间,发送请求才有效。
  - After=2022-03-24T14:56:33.082+08:00[Asia/Shanghai]        #ZonedDateTime 类的地区时间显示
  - Before=2022-03-23T14:56:33.082+08:00[Asia/Shanghai]
  - Between=2022-03-23T14:56:33.082+08:00[Asia/Shanghai], 2022-03-24T14:56:33.082+08:00[Asia/Shanghai]
#表示 Cookie 中需要携带的参数和值
  - Cookie=username, guest997
#表示匹配的 Header 的类型和值
  - Header=X-Request-Id, \d+
#表示匹配的 Host 主机列表
  - Host=**.guest997.ml, **.guest997.cf
#表示匹配的请求类型
  - Method=GET
#表示匹配的路径
  - Path=/articles/**
#表示请求需要携带的参数和值
  - Query=password, guest.

过滤

其本身具有几十种过滤器,有兴趣的可以自己去网上找一下,下面讲的是自定义的全局过滤器。

package ml.guest997.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;

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("进入过滤器的时间:" + new Date());
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname == null) {
            log.info("用户名不存在");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    //表示当前过滤器的优先级
    @Override
    public int getOrder() {
        return 0;
    }
}

测试

分别启动 Eureka-Server、Payment、Payment2 和 Gateway 模块后,浏览器访问:127.0.0.1:9527/payment/lb,会发现访问失败,而访问:127.0.0.1:9527/payment/lb?uname=Guest997,就能正常访问。