一、概述

SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

二、Gateway核心概念

一.核心概念

1、Route(路由)

Route 是网关的基础元素,由 ID、目标 URI、断言、过滤器组成。当请求到达网关时,由 Gateway Handler Mapping 通过断言进行路由匹配(Mapping),当断言为真时,匹配到路由。

2、Filter(过滤器)

Filter 是 Gateway 中的过滤器,可以在请求发出前后进行一些业务上的处理。

3、Predicate(断言)

开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

二.工作流程

spring cloud 接口绕过网关的方式 springcloud中网关_HTTP

三、整合案例(Spring Cloud Gateway整合Eureka动态路由转发)

一.引入依赖(添加web依赖会报错,网关不需要web,因为gateway用的是webflux)

<!-- SpringCloud 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>

二.配置文件

Spring Gateway支持两种方式提供路由服务,其一是配置文件启用,其二则是通过代码达到目的,建议使用第一种。

1、eureka配置

eureka:
  instance:
    hostname: cloud-gateway
    instance-id: cloud-gateway9527
    prefer-ip-address: true  #访问路径可以显示IP地址
      #    Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
      lease-renewal-interval-in-seconds: 5
      #    Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
      lease-expiration-duration-in-seconds: 20
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
        defaultZone: http://eureka8001:8001/eureka/,http://eureka8002:8002/eureka/,http://eureka8003:8003/eureka/   #集群版

2、gateway配置(配置文件实现,看起来比较清晰)

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由转发。
      #路由是多个
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://CLOUD-PAYMENT-SERVICE #匹配后提供服务的路由地址,忽略大小写,lb的意思是负载均衡
          predicates:#断言
            - Path=/payment/get/**         # 路径相匹配的进行路由,意思就是问服务端有没有这个路径,有的话进行转发。

3、测试访问

测试访问该地址,可以转发到CLOUD-PAYMENT-SERVICE对应的服务方法。

http://localhost:9527/payment/get/2

四、断言的使用(YML配置案例)

一.Path

最常用断言,可以在一个router里配置多个path

routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://CLOUD-PAYMENT-SERVICE #匹配后提供服务的路由地址,忽略大小写,lb的意思是负载均衡
          predicates:#断言
            - Path=/payment/get1/**,/payment/get2/**

二.After

设定一个UTC时间,此时间之后的请求会成功,此时间之前的请求会404

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

三.Before

设定一个UTC时间,此时间之前的请求会成功,此时间之后的请求会404

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

四.Between

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

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

五.Cookie

Cookie 路由断言 Factory有两个参数,cookie名称和正则表达式。请求包含次cookie名称且正则表达式为真的将会被匹配。
cookie为键值对,后面的可以为具体的值,也可以为正则表达式的匹配。

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: http://example.org
        predicates:
        - Cookie=username, mobanbai#此处为键值对

测试

curl http://localhost:9527/payment/lb --cookie "username=mobanbai"

六.Header

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

spring:
 cloud:
   gateway:
     routes:
     - id: header_route
       uri: http://example.org
       predicates:
       - Header=X-Request-Id, \d+

测试

curl http://localhost:9527/payment/lb --H "X-Request-Id=12"

七.Method

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

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: http://example.org
        predicates:
        # 只有POST请求才会被转发
        - Method=POST

八.Query

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: http://example.org
        predicates:
        - Query=baz

九.RemoteAddr

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: http://example.org
        predicates:
        - RemoteAddr=192.168.1.1/24

五、过滤器

一.过滤器的生命周期

1、pre

前置过滤器

2、post

后置过滤

二.过滤器的种类

1、单一过滤器

2、全局过滤器

三.自定义过滤器

1、实现接口

全局过滤器要实现两个接口GlobalFilter, Ordered

@Component
public class AuthFilter implements GlobalFilter, Ordered
{
    // 排除过滤的 uri 地址
    // swagger排除自行添加
    private static final String[]           whiteList = {"/auth/login", "/user/register", "/system/v2/api-docs",
            "/auth/captcha/check", "/auth/captcha/get","/auth/login/slide"};

    @Resource(name = "stringRedisTemplate")
    private ValueOperations<String, String> ops;

  /**
   * 过滤器执行方法
   * 处理 Web 请求并(可选)委托给下一个
   * {@code WebFilter} 通过给定的 {@link GatewayFilterChain}。
   * @param exchange 当前服务器交换
   * @param 链提供了一种委托给下一个过滤器的方法
   * @return {@code Mono<Void>} 指示请求处理何时完成
   **/
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    {
        String url = exchange.getRequest().getURI().getPath();
        log.info("url:{}", url);
        // 跳过不需要验证的路径
        if (Arrays.asList(whiteList).contains(url))
        {
            return chain.filter(exchange);
        }
        String token = exchange.getRequest().getHeaders().getFirst(Constants.TOKEN);
        // token为空
        if (StringUtils.isBlank(token))
        {
            return setUnauthorizedResponse(exchange, "token can't null or empty string");
        }
        String userStr = ops.get(Constants.ACCESS_TOKEN + token);
        if (StringUtils.isBlank(userStr))
        {
            return setUnauthorizedResponse(exchange, "token verify error");
        }
        JSONObject jo = JSONObject.parseObject(userStr);
        String userId = jo.getString("userId");
        // 查询token信息
        if (StringUtils.isBlank(userId))
        {
            return setUnauthorizedResponse(exchange, "token verify error");
        }
        // 设置userId到request里,后续根据userId,获取用户信息
        ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(Constants.CURRENT_ID, userId)
                .header(Constants.CURRENT_USERNAME, jo.getString("loginName")).build();
        ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
        return chain.filter(mutableExchange);
    }

    private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange, String msg)
    {
        ServerHttpResponse originalResponse = exchange.getResponse();
        originalResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
        originalResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] response = null;
        try
        {
            response = JSON.toJSONString(R.error(401, msg)).getBytes(Constants.UTF8);
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }
        DataBuffer buffer = originalResponse.bufferFactory().wrap(response);
        return originalResponse.writeWith(Flux.just(buffer));
    }

    @Override
    public int getOrder()
    {
        return -200;
    }
}

2、ServerWebExchange(服务网络交换器)

ServerWebExchange的注释: ServerWebExchange是一个HTTP请求-响应交互的契约。提供对HTTP请求和响应的访问,并公开额外的服务器端处理相关属性和特性,如请求属性。

其实,ServerWebExchange命名为服务网络交换器,存放着重要的请求-响应属性、请求实例和响应实例等等,有点像Context的角色。

public interface HttpMessage {
    
    // 获取请求头,目前的实现中返回的是ReadOnlyHttpHeaders实例,只读
    HttpHeaders getHeaders();
}    

public interface ReactiveHttpInputMessage extends HttpMessage {
    
    // 返回请求体的Flux封装
    Flux<DataBuffer> getBody();
}

public interface HttpRequest extends HttpMessage {

    // 返回HTTP请求方法,解析为HttpMethod实例
    @Nullable
    default HttpMethod getMethod() {
        return HttpMethod.resolve(getMethodValue());
    }
    
    // 返回HTTP请求方法,字符串
    String getMethodValue();    
    
    // 请求的URI
    URI getURI();
}    

public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage {
    
    // 连接的唯一标识或者用于日志处理标识
    String getId();   
    
    // 获取请求路径,封装为RequestPath对象
    RequestPath getPath();
    
    // 返回查询参数,是只读的MultiValueMap实例
    MultiValueMap<String, String> getQueryParams();

    // 返回Cookie集合,是只读的MultiValueMap实例
    MultiValueMap<String, HttpCookie> getCookies();  
    
    // 远程服务器地址信息
    @Nullable
    default InetSocketAddress getRemoteAddress() {
       return null;
    }

    // SSL会话实现的相关信息
    @Nullable
    default SslInfo getSslInfo() {
       return null;
    }  
    
    // 修改请求的方法,返回一个建造器实例Builder,Builder是内部类
    default ServerHttpRequest.Builder mutate() {
        return new DefaultServerHttpRequestBuilder(this);
    } 

    interface Builder {

        // 覆盖请求方法
        Builder method(HttpMethod httpMethod);
         
        // 覆盖请求的URI、请求路径或者上下文,这三者相互有制约关系,具体可以参考API注释
        Builder uri(URI uri);
        Builder path(String path);
        Builder contextPath(String contextPath);

        // 覆盖请求头
        Builder header(String key, String value);
        Builder headers(Consumer<HttpHeaders> headersConsumer);
        
        // 覆盖SslInfo
        Builder sslInfo(SslInfo sslInfo);
        
        // 构建一个新的ServerHttpRequest实例
        ServerHttpRequest build();
    }         
}

3、GatewayFilterChain(网关过滤链表)

/**
 * 网关过滤链表接口
 * 用于过滤器的链式调用
 */
public interface GatewayFilterChain {

    /**
     *  链表启动调用入口方法*/
    Mono<Void> filter(ServerWebExchange exchange);

}