最新SpringCloudGateway网关使用说明手册

一、Gataway的基本概念

Spring Cloud Gateway 作为SpringCloud生态系统中的网关,目标是替代Netflix Zuul。

最新SpringCloudGateway网关使用说明手册

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

表面上看起来,gateway貌似只是统一接收一个请求进而分发路由到不同的服务而已,实际上gateway的功能远不止于此。

gateway提供了一系列机制供我们实现众多功能,诸如:相关的鉴权,安全控制,日志统一处理,易于监控,限流。这些机制也是我们学习的重点,所以我们不要仅仅停留只会配置一个路由分发,比如集成一个权限控制到gateway中也是很不错的。

二、Gateway的工作原理

整个流程总结如下:

①、Gateway的客户端向Gateway服务发起请求,请求首先会被HttpWebHandlerAdapter进行提取组装成网关的上下文,然后网关的上下文会传递到DispatcherHandler

②、DispatcherHandler是所有请求的分发处理器,它主要负责分发请求对应的处理器。比如将请求分发到对应RoutedPredicatedHandlerMapping(路由断言处理器映射器)

③、路由断言处理器主要用于路由查找,以及找到路由后返回对应的FilteringWebHandler

④、FilteringWebHandler主要负责组装Filter链表并调用Filter执行一系列Filter处理,然后把请求转到后端对应的代理服务器,处理完毕后,将Response返回到Gateway客户端。

三、Gateway路由

Gateway路由配置分为基于配置的静态路由设置基于代码动态路由配置

假设现在有一个场景:

要实现这个需求,我们逐一演示两种配置方式

1、基于配置的静态路由设置:配置如下

spring: 
    cloud: 
        gateway:
          #路由配置
          routes:
            #唯一标识符
            - id: hailtaxi-driver
              uri: lb://hailtaxi-driver 表示负载均衡到名为hailtaxi-driver的服务
              #路由断言
              predicates:
                - Path=/driver/**
            #唯一标识符
            - id: hailtaxi-order
              uri: lb://hailtaxi-order
              #路由断言
              predicates:
                - Path=/order/**
            #唯一标识符
            - id: hailtaxi-pay
              uri: lb://hailtaxi-pay
              #路由断言
              predicates:
                - Path=/pay/**

2、基于代码的动态路由配置:配置如下

/***
 * 路由配置
 * @param builder
 * @return
 */
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {

    return builder.routes()
        .route("hailtaxi-driver", r -> r.path("/driver/**").uri("lb://hailtaxi-driver"))
        .route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order"))
        .route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay"))
        .build();
}

路由中的断言

断言用于确定请求是否满足特定条件的逻辑组件,比如上面的配置就用到了Path断言,判断是否满足某种请求路径规则,满足的才分发到对应的服务

常见断言配置:

这里大概总结一下有哪些内置实现的断言

断言源码分析

拿Cookie断言来说,首先看它的体系结构

可以发现,其实所有的断言都是继承了AbstractRoutePredicateFactory

import org.springframework.cloud.gateway.support.AbstractRoutePredicateFactory;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;

import javax.servlet.http.Cookie;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;


public class CookieRoutePredicateFactory
        extends AbstractRoutePredicateFactory {

    /**
     * Cookie 名称的键。
     */
    public static final String NAME_KEY = "name";

    /**
     * 正则表达式的键。
     */
    public static final String REGEXP_KEY = "regexp";

    /**
     * 构造函数,调用父类构造函数,指定配置类。
     */
    public CookieRoutePredicateFactory() {
        super(Config.class);
    }
    
    /*
      通过shortcutFieldOrder方法设置Config配置类中的属性,需要根据具体的规则来设置
      通过shortcutType方法获取具体规则,具体参看:org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType
      规则包括以下几种:
        DEFAULT : 按照shortcutFieldOrder顺序依次赋值
    */
    @Override
    public List shortcutFieldOrder() {
        return Arrays.asList(NAME_KEY, REGEXP_KEY);
    }


    @Override
    public Predicate apply(Config config) {
        return new GatewayPredicate() {
            /**
             ServerWebExchange:用于表示 HTTP 请求和响应的交换,包含了请求的上下文信息以及响应的构建与发送功能
             */
            @Override
            public boolean test(ServerWebExchange exchange) {
                // 获取请求中的指定名称的 Cookie 列表
                List cookies = exchange.getRequest().getCookies()
                        .get(config.name);
                
                // 如果没有找到 Cookie,返回 false
                if (cookies == null) {
                    return false;
                }
                
                // 遍历所有 Cookie,检查其值是否匹配正则表达式
                for (HttpCookie cookie : cookies) {
                    if (cookie.getValue().matches(config.regexp)) {
                        return true; // 找到匹配的 Cookie,返回 true
                    }
                }
                return false; // 未找到匹配的 Cookie,返回 false
            }

            /**
             * 返回当前断言的字符串表示,便于调试。
             * 
             * @return 字符串表示。
             */
            @Override
            public String toString() {
                return String.format("Cookie: name=%s regexp=%s", config.name,
                        config.regexp);
            }
        };
    }
    
    /*
     内部配置类是用来接收在配置文件中配置的参数的
      routes:
        #唯一标识符
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由断言
          predicates:
            - Cookie=username,admin
    */
    @Validated
    public static class Config {
        
        @NotEmpty // 确保名称不能为空
        private String name;

        @NotEmpty // 确保正则表达式不能为空
        private String regexp;

        public String getName() {
            return name;
        }

        public Config setName(String name) {
            this.name = name;
            return this; // 返回当前对象,支持链式调用
        }

        public String getRegexp() {
            return regexp;
        }

        public Config setRegexp(String regexp) {
            this.regexp = regexp;
            return this; // 返回当前对象,支持链式调用
        }
    }
}

在 shortcutFieldOrder中去设置好config对应的属性值,这样配置中的username就赋给了config中的name,配置中的admin就赋给了config中的regexp。

自定义断言规则器

假设现在要完成这么一个需求:

转发带token的请求到hailtaxi-drvier服务中,这里定义请求带token是指包含某个请求头的请求,至于是什么请求头可以由配置指定

1、修改配置文件

    gateway:
      #路由配置
      routes:
        #唯一标识符
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由断言
          predicates:
            自定义一个Token断言,如果请求包含Authorization的token信息则通过
            - Token=Authorization

 2、创建 RoutePredicateFactory

断言工厂默认命名规则必须按照"名称"+RoutePredicateFactory,如上TokenRoutePredicateFactory的断言名称为Token

@Slf4j
@Component // 要交给spring容器管理
public class TokenRoutePredicateFactory extends AbstractRoutePredicateFactory {

    public TokenRoutePredicateFactory() {
        super(Config.class);
    }

    public Predicate apply(Config config) {
        return exchange -> {
            // 打印配置文件参数值
            String headerName = config.getHeaderName();
            HttpHeaders headers = exchange.getRequest().getHeaders();
            List header = headers.get(headerName);
            log.info("Token Predicate headers:{}", header);
            // 断言返回的是boolean值
            return header!=null && header.size()>0;
        };
    }

    @Override
    public List shortcutFieldOrder() {
        return Arrays.asList("headerName");//指定配置文件中加载到的配置信息应填充到Config的哪个属性上
    }

    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.DEFAULT;
    }

    @Data
    public static class Config { //static class
        private String headerName;//存储从配置文件中加载的配置
    }
}

四、Gateway过滤器

Spring Cloud Gateway根据作用范围划分为GatewayFilterGlobalFilter

GatewayFilter需要在配置中显式的配置才生效

GlobalFilter是全局过滤器,不需要在配置文件中配置,作用在所有的路由上。

这里列举两个常用的内置过滤器:

1)添加响应头

AddResponseHeaderGatewayFilterFactory  属于  GatewayFilter

对输出响应头设置属性,比如对输出的响应设置其头部属性名称为:X-Response-Default-MyName , 值为admin

修改配置文件,配置如下:

spring:
  cloud:
    gateway:
     配置全局默认过滤器 作用在所有路由上,也可单独为某个路由配置
      default-filters:
      往响应过滤器中加入信息
        - AddResponseHeader=X-Response-Default-MyName,itheima

2)前缀处理

在项目中做开发对接接口的时候,我们很多时候需要统一API路径,比如统一以/api开始的请求调用hailtaxi-driver服务,但真实服务接口地址又没有/api路径,我们可以使用Gateway的过滤器处理请求路径。

在gateway中可以通过配置路由的过滤器StripPrefix实现映射路径中的前缀处理,我们来使用一下该过滤器,再进一步做说明。

    gateway:
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/api/driver/**
        filters:
          - StripPrefix=1

此处- StripPrefix=1表示真实请求地址是当前用户请求以/api开始的uri中去除第1个路径/api.

自定义GatewayFilter

1、实现GatewayFilter接口

GatewayFilter 一般作用在某一个路由上,需要实例化创建才能使用,局部过滤器需要实现接口GatewayFilter、Ordered。

创建com.demo.filter.PayFilter代码如下:

public class PayFilter implements GatewayFilter,Ordered {

    /***
     * 过滤器执行拦截
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("GatewayFilter拦截器执行---pre-----PayFilter");
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            System.out.println("GatewayFilter拦截器执行---post-----PayFilter");
        }));
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

 2、继承GatewayFilterFactory

如果定义局部过滤器,想在配置文件中进行配置来使用,可以继承AbstractGatewayFilterFactory抽象类或者AbstractNameValueGatewayFilterFactory

过滤器工厂默认命名规则必须按照"名称"+GatewayFilterFactory`,如上StripPrefixGatewayFilterFactory的过滤器名称为StripPrefix

2.1、继承AbstractGatewayFilterFactory

需求:

在网关中统一支付方式,编写一个过滤器:PayMethodGatewayFilterFactory

1、编写过滤器

@Slf4j
@Component //一定要将其交给spring容器管理
public class PayMethodGatewayFilterFactory extends AbstractGatewayFilterFactory {

    public PayMethodGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String paymethod = config.getPayMethod();
            String msg = config.getMsg();
            log.info("PayMethodGatewayFilterFactory 加载到的配置信息为:{}---{}",paymethod,msg);
            //将paymethod添加到请求头中
            exchange.getRequest().mutate().header("paymethod",paymethod);
            return chain.filter(exchange);
        };
    }

    @Override
    public List shortcutFieldOrder() {
        return  Arrays.asList("payMethod","msg");//指定从yml中提前出来的配置信息填充到配置类中哪个属性,按规则配置
    }

    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.DEFAULT;//默认规则
    }

    /**
     * 加载从yml中提取出来的配置信息
     */
    @Data
    public static class Config {
        private String payMethod;
        private String msg;
    }
}

 2、配置文件中使用如下:

    gateway:
      #路由配置
      routes:
        #唯一标识符
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由断言
          predicates:
            - Path=/driver/**
            - Cookie=username,admin
            - Header=token,^(?!\d+$)[\da-zA-Z]+$
            - Method=GET,POST
            - Token=Authorization
          filters:
            - PayMethod=alipay,业务整合

其实在filter中就可以实现任何你想做的操作了,包括日志记录,认证与授权等等

五、跨域配置

出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。

在Spring Cloud Gateway中配置跨域是非常简单的,如下面application.yml所示:

    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - PUT

另外一种写法就需要创建CorsWebFilter过滤器,代码如下:

/**
 * 配置跨域
 * @return
 */
@Bean
public CorsWebFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    // cookie跨域
    config.setAllowCredentials(Boolean.TRUE);
    config.addAllowedMethod("*");
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    // 配置前端js允许访问的自定义响应头
    config.addExposedHeader("Authorization");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
    source.registerCorsConfiguration("/**", config);
    return new CorsWebFilter(source);
}

版权声明:如无特殊标注,文章均来自网络,本站编辑整理,转载时请以链接形式注明文章出处,请自行分辨。

本文链接:https://www.shbk5.com/dnsj/72818.html