SpringCloud集成Gateway新一代服务网关

Gateway简介

什么是API网关?

API网关是作为一个 API 架构,用来保护、增强和控制对于 API 服务的访问。API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。

API网关都有哪些职能?

API网关的分类与功能

Gateway是什么

Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。

为什么用Gateway

Spring Cloud Gateway 可以看做是一个 Zuul 1.x 的升级版和代替品,比 Zuul 2 更早的使用 Netty 实现异步 IO,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。

Spring Cloud Gateway 里明确的区分了 Router 和 Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。

比如内置了 10 种 Router,使得我们可以直接配置一下就可以随心所欲的根据 Header、或者 Path、或者 Host、或者 Query 来做路由。

比如区分了一般的 Filter 和全局 Filter,内置了 20 种 Filter 和 9 种全局 Filter,也都可以直接用。当然自定义 Filter 也非常方便。

Gateway核心概念

Route(路由):这是网关的基本构建块,它由一个ID,一个URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。

Predicate(断言):输入类是一个ServerWebExchange。我们可以使用它来匹配来自HTTP请求的任何内容,例如headers。如果请求与断言相匹配则进行路由。

Filter(过滤器):Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者后对请求进行修改。

Gateway工作流程

客户端向Spring Cloud Gateway发出请求,然后在Gateway Hander Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。

Handler通过指定的过滤器链来将请求发送到我们实际的服务业务逻辑,然后返回。过滤器之间用虚线分是因为过滤器可能会在发送代理请求之间("pre")或之后("post")执行业务逻辑。

Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、协议转换等。在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

主要核心就是路由转发+执行过滤器链。

SpringCloud集成Gateway

入门配置

1、新建module,服务名称为cloud-gateway-service

1)修改pom.xml文件,主要还是新增了gateway的依赖。踩坑提示,不要引入web依赖,不然会报错,项目都起不来。如下图:

<!-- 添加eureka客户端的依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!--gateway-->
<!--注意:不要引入web依赖,不然会报错,项目都起不来-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

2)application.yml配置

server:
  port: 8877

spring:
  application:
    name: cloud-gateway-service # 集群的服务名都是这个, 只有一个

#eureka配置
eureka:
  client:
    #表示是否将自己注册进eureka  默认为true
    register-with-eureka: true
    #是否从EurekaServer中抓取已有的注册信息,默认为true,单点无所谓,集群必须设置true才能和ribbon使用负载均衡
    fetch-registry: true
    service-url:
      #集群配置
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

3)启动类

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

4)启动项目,报错(spring-boot为2.3.3.RELEASE,SpringCloud为Hoxton.SR12):

Description:

An attempt was made to call a method that does not exist. The attempt was made from the following location:

    org.springframework.cloud.gateway.config.GatewayAutoConfiguration$NettyConfiguration.buildConnectionProvider(GatewayAutoConfiguration.java:798)

The following method did not exist:

    reactor.netty.resources.ConnectionProvider$Builder.evictInBackground(Ljava/time/Duration;)Lreactor/netty/resources/ConnectionProvider$ConnectionPoolSpec;

The method's class, reactor.netty.resources.ConnectionProvider$Builder, is available from the following locations:

    jar:file:/E:/englishPath/selfrespority/io/projectreactor/netty/reactor-netty/0.9.11.RELEASE/reactor-netty-0.9.11.RELEASE.jar!/reactor/netty/resources/ConnectionProvider$Builder.class

The class hierarchy was loaded from the following locations:

    reactor.netty.resources.ConnectionProvider.Builder: file:/E:/englishPath/selfrespority/io/projectreactor/netty/reactor-netty/0.9.11.RELEASE/reactor-netty-0.9.11.RELEASE.jar
    reactor.netty.resources.ConnectionProvider.ConnectionPoolSpec: file:/E:/englishPath/selfrespority/io/projectreactor/netty/reactor-netty/0.9.11.RELEASE/reactor-netty-0.9.11.RELEASE.jar


Action:

Correct the classpath of your application so that it contains a single, compatible version of reactor.netty.resources.ConnectionProvider$Builder

Disconnected from the target VM, address: '127.0.0.1:13683', transport: 'socket'

Process finished with exit code 1

查看网上资料,是版本兼容问题(引入的 reactor-netty 版本是0.9.11.RELEASE),所以我们在父工程pom.xml的dependencyManagement标签对reactor-netty进行升级:

<!--版本兼容问题,根据自己springboot的版本进行适配,网上有的说是降低版本,有的是升高版本-->
<dependency>
    <groupId>io.projectreactor.netty</groupId>
    <artifactId>reactor-netty</artifactId>
    <version>0.9.14.RELEASE</version>
</dependency>

重新启动项目,控制台没有报错,查看eureka注册中心,也成功注册上去了

2、网关如何映射呢?

1)首先,我们创建一个新的模块cloud-gateway-logistics-service,以它为例来映射一下。这个模块对的创建过程我们不再详细列出,其实就几步:引入依赖、配置application.yml、新建启动类。我们在该模块下新建一个LogisticsController类,有这么几个方法:

@RestController
@RequestMapping("/logistics")
public class LogisticsController {

    @RequestMapping("/get")
    public String get(String name) {
        return "调用成功:" + name + ", 流水号:" + UUID.randomUUID().toString().replaceAll("-", "");
    }

    @RequestMapping("/lb")
    public String lb() {
        return "success response string:" + UUID.randomUUID().toString().replaceAll("-", "");
    }
}

分别直接访问:

http://localhost:6677/logistics/get?name=linhongwei

http://localhost:6677/logistics/lb

2)上面的端口6677是cloud-gateway-logistics-service的应用端口,直接暴露在了外边,不安全,如果我们不想暴露真的6677端口号,希望在外面6677外边包上一层我们的Gateway的端口8877,这就需要在网关应用(cloud-gateway-service)的yml配置上新增路由配置。如下图:

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

重启网关服务后,然后我们访问 http://localhost:8877/logistics/get?name=linhongwei,可以成功访问,说明我们的网关的路由和断言就配置成功啦!如下图:

我们现在配置的是YML进行配置的,还有一种配置方案就是通过硬编码的方式。就是代码中注入RouteLocator的Bean,是为了解决YML文件配置太多,文件太大的问题。我们只要演示通过8877网关访问到外网的百度新闻网址。

/**
 * 配置了一个id为path_route_baidu的路由规则
 * 当访问地址http://localhost:8877/guonei时会自动转发到http://news.baidu.com/guonei
 */
@Configuration
public class GateWayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("path_route_baidu", new Function<PredicateSpec, Route.AsyncBuilder>() {
            @Override
            public Route.AsyncBuilder apply(PredicateSpec predicateSpec) {
                return predicateSpec.path("/guonei").uri("http://news.baidu.com/guonei");
            }
        });
        return routes.build();
    }
}

配置完毕,重新启动服务,我们访问 http://localhost:8877/guonei,可以成功转发到如下界面,说明我们GateWay通过编码的方式进行路由的映射配置。

动态路由配置

看我们的YML配置文件,我们配置的是http://localhost:6677是写死的,但是在我们微服务中生产者服务是不可能有一台机器的,所以说必须要进行负载均衡的配置。

默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。那我们就需要修改Gateway的YML配置文件,开启从注册中心动态创建路由的功能,利用微服务名称进行路由,匹配后提供服务的路由地址修改为生产者的服务名称,具体配置如下图:

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

重启服务后,几个服务都注册到注册中心了:

访问:http://localhost:8877/order/findOrdersByUserId?userId=1

Predicate断言

我们启动我们的cloud-gateway-service服务时,可以看到控制台有如下的界面:

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础框架的一部分,它包含许多内置的RoutePredicateFactory,所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂进行组合。Spring Cloud Gateway 创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route,Spring Cloud Gateway包含许多内置的Route Predicate Factoriess。所有的这些谓词都匹配HTTP的不同属性。多种谓工厂可以组合,并通过逻辑and。

然后在我们的YML的配置文件也可以看到路由配置,我们使用的是path,就是请求的Path(路径)匹配配置值。如下图:

我们常用的路由配置,就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。如下图:

After

判断时间在After配置的时间之后规则才生效

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

时间格式可以通过下面方式进行转换得到:

//创建一个Date对象
Date date = new Date();

//将ZonedId设置为默认值
ZoneId id = ZoneId.systemDefault();
//ZoneId id = ZoneId.of("Asia/Shanghai");

//将java.util.date转换为ZonedDateTime
System.out.println(ZonedDateTime.ofInstant(date.toInstant(), id));

Before

判断在Before之前路由配置才生效。

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

Between

在时间之内规则生效

spring:
  cloud:
    gateway:
      routes:
      - id: between_route
        uri: https://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,名字和正则表达式。

cookie中需要带有key并且符合value的正则表达式。不带cookie会404

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://example.org
        predicates:
          - Cookie=key, value

Header

请求头文件中带key的名称是 X-Request-Id,值是正则表达式\d+(数字)

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

Host

主机是yumbo.top或者huashengshu.top的子域名的规则生效,如果不带这个信息,则会返回404。

头文件中设置 Host。

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://example.org
        predicates:
          - Host=**.yumbo.top,**.huashengshu.top

Method

请求方法要设置GET或者POST,路由才会生效

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: https://example.org
        predicates:
          - Method=GET,POST

Path

要求路径要符合Path设置的规则路由才生效。

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: https://example.org
        predicates:
          - Path=/red/{segment},/blue/{segment}

Query

请求参数要包含green参数路由才生效。

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

RemoteAddr

如果请求的远程地址是192.168.1.10,则此路由匹配。

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

Weight

这条路径将把80%的流量转发到weighthigh.org,并将20%的流量转发到weighlow.org

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

更多详情查看官方文档:https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/#gateway-request-predicates-factories

注意:

  • 各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。
  • 一个请求满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。

Filter的使用

filter是Gateway的三大核心之一,路由过滤器可用于修改进入HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Gateway内置了多种路由过滤器,它们都由GatewayFilter工程类来产生。

Filter的作用

当我们有很多个服务时,比如下图中的user-service、goods-service、sales-service等服务,客户端请求各个服务的Api时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等。

1.png

对于这样重复的工作,有没有办法做的更好,答案是肯定的。在微服务的上一层加一个全局的权限控制、限流、日志输出的Api Gatewat服务,然后再将请求转发到具体的业务服务层。这个Api Gateway服务就是起到一个服务边界的作用,外接的请求访问系统,必须先通过网关层。

2.png

Filter的生命周期

Spring Cloud Gateway同zuul类似,生命周期只有pre和post。在路由处理之前,需要经过“pre”类型的过滤器处理,处理返回响应之后,可以由“post”类型的过滤器处理。在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等。

比如上图中的user-service,收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端。

3.png

与zuul不同的是,filter除了分为“pre”和“post”两种方式的filter外,在Spring Cloud Gateway中,filter从作用范围可分为另外两种,一种是针对于单个路由的gateway filter,它在配置文件中的写法同predicate(断言)类似;另外一种是针对于所有路由的global gateway filer。现在从作用范围划分的维度来讲解这两种filter。

GatewayFilter和GlobalFilter

Spring Cloud Gateway的Filter种类分为GatewayFilter(单一的)和GlobalFilter(全局的)

二者区别如下:

GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上。

GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。

GatewayFilter介

GatewayFilter工厂同Predicate工厂类似,都是在配置文件application.yml中配置,遵循了约定大于配置的思想,只需要在配置文件配置GatewayFilter Factory的名称,而不需要写全部的类名,比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader,而不是全部类名。在配置文件中配置的GatewayFilter Factory最终都会相应的过滤器工厂类处理。

Spring Cloud Gateway 内置的过滤器工厂一览表如下:

1.png

每一个过滤器工厂在官方文档都给出了详细的使用案例,现在的版本有30种,点击查看官网,如果不清楚的还可以在org.springframework.cloud.gateway.filter.factory看每一个过滤器工厂的源码。在我们实际的开发过程中,一般GatewayFilter是无法满足的,就需要我们自定义过滤器。

那下面我们自己开始自义定过滤器吧!

主要就是实现这两个接口GlobalFilter, Ordered,然后实现具体的业务逻辑,如下图:

@Component
public class MyLogGatewayFilter implements GlobalFilter, Ordered {

    private static Logger logger = LoggerFactory.getLogger(MyLogGatewayFilter.class);


    //你要访问我时候需要一个指定的用户名才能进行访问,
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        logger.info("************come in MyLogGatewayFilter " + new Date());
        //判断是否携带uname的key
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        //非法用户请离开
        if (uname == null) {
            logger.info("************用户名为null,非法用户");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        //合法用户进行下一个过滤链进行过滤验证
        return chain.filter(exchange);
    }

    //这个0数字代表加载过滤器的顺序,就是越小优先级越高,因为是全局的,所以必须是第一位的。
    @Override
    public int getOrder() {
        return 0;
    }
}

该过滤器主要是要访问的路径,需要携带参数uname,如果不携带就会校验不合法,会报错,无法访问,只有写且合法才会访问成功,我们访问http://localhost:8877/order/findOrdersByUserId?userId=1&uname=linhhongwei进行测试。

GlobalFilter介绍

Spring Cloud Gateway框架内置的GlobalFilter如下:

微信截图_20181204175448.png

上图中每一个GlobalFilter都作用在每一个router上,能够满足大多数的需求。但是如果遇到业务上的定制,可能需要编写满足自己需求的GlobalFilter。在下面的案例中将讲述如何编写自己GlobalFilter,该GlobalFilter会校验请求中是否包含了请求参数“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。代码如下:

public class TokenFilter implements GlobalFilter, Ordered {

    Logger logger = LoggerFactory.getLogger(TokenFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (token == null || token.isEmpty()) {
            logger.info("token is empty...");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

在上面的TokenFilter需要实现GlobalFilter和Ordered接口,这和实现GatewayFilter很类似。根据ServerWebExchange获取ServerHttpRequest,然后根据ServerHttpRequest中是否含有参数token,如果没有则完成请求,终止转发,否则执行正常的逻辑。

我们需要将TokenFilter在工程的启动类中注入到Spring Ioc容器中,代码如下:

@Bean
public TokenFilter tokenFilter(){
    return new TokenFilter();
}

重启网关应用,我们发现原来我们访问 http://localhost:8877/order/findOrdersByUserId?userId=1&uname=linhhongwei 是没有问题,现在却访问不了。查看控制台:

这个不就是TokenFilter打印的吗?没错,被TokenFilter拦截了,我们只需要在请求的链接后再加上token=123就可以了

 

过滤器规则

过滤规则 实例 说明
PrefixPath - PrefixPath=/app 在请求路径前加上app
RewritePath - RewritePath=/test, /app/test 访问localhost:9022/test,请求会转发到localhost:8001/app/test
SetPath - SetPath=/app/{path} 通过模板设置路径,转发的规则时会在路径前增加app,{path}表示原请求路径
RedirectTo   重定向
RemoveRequestHeader   去掉某个请求头信息

注:当配置多个filter时,优先定义的会被调用,剩余的filter将不会生效。

PrefixPath

对所有的请求路径添加前缀:

spring:
  cloud:
    gateway:
      routes:
        - id: prefixpath_route
          uri: https://example.org
          filters:
            - PrefixPath=/mypath

访问/hello的请求被发送到https://example.org/mypath/hello。

RedirectTo

重定向,配置包含重定向的返回码和地址:

spring:
  cloud:
    gateway:
      routes:
        - id: prefixpath_route
          uri: https://example.org
          filters:
            - RedirectTo=302, https://acme.org

RemoveRequestHeader

去掉某个请求头信息:

spring:
  cloud:
    gateway:
      routes:
        - id: removerequestheader_route
          uri: https://example.org
          filters:
            - RemoveRequestHeader=X-Request-Foo

去掉请求头信息 X-Request-Foo。

RemoveResponseHeader

去掉某个响应头信息:

spring:
  cloud:
    gateway:
      routes:
        - id: removerequestheader_route
          uri: https://example.org
          filters:
            - RemoveResponseHeader=X-Request-Foo

RemoveRequestParameter

去掉某个请求参数。

spring:
  cloud:
    gateway:
      routes:
        - id: removerequestparameter_route
          uri: https://example.org
          filters:
            - RemoveRequestParameter=red

RewritePath

改写路径

spring:
  cloud:
    gateway:
      routes:
      - id: rewrite_filter
        uri: http://localhost:8081
        predicates:
        - Path=/test/**
        filters:
        - RewritePath=/where(?<segment>/?.*), /test(?<segment>/?.*)

/where/... 改成 test/...

使用代码改写路径:

RouteLocatorBuilder.Builder builder = routeLocatorBuilder.routes();
        builder
                .route("path_rote_at_guigu", r -> r.path("/guonei")
                        .uri("http://news.baidu.com/guonei"))
                .route("csdn_route", r -> r.path("/csdn")
                        .uri("https://blog.csdn.net"))
                .route("blog3_rewrite_filter", r -> r.path("/blog3/**")
                        .filters(f -> f.rewritePath("/blog3/(?<segment>.*)", "/$\\{segment}"))
                        .uri("https://blog.csdn.net"))
                .route("rewritepath_route", r -> r.path("/baidu/**")
                        .filters(f -> f.rewritePath("/baidu/(?<segment>.*)", "/$\\{segment}"))
                        .uri("http://www.baidu.com"))

                .build();

SetPath

设置请求路径,与RewritePath类似。

spring:
  cloud:
    gateway:
      routes:
        - id: setpath_route
          uri: https://example.org
          predicates:
            - Path=/red/{segment}
          filters:
            - SetPath=/{segment}

如/red/blue的请求被转发到/blue。 

SetRequestHeader

设置请求头信息。

spring:
  cloud:
    gateway:
      routes:
      - id: setrequestheader_route
        uri: https://example.org
        filters:
        - SetRequestHeader=X-Request-Red, Blue

SetStatus

设置响应状态码。

spring:
  cloud:
    gateway:
      routes:
      - id: setstatusint_route
        uri: https://example.org
        filters:
        - SetStatus=401

StripPrefix

跳过指定路径。

spring:
  cloud:
    gateway:
      routes:
      - id: nameRoot
        uri: https://nameservice
        predicates:
        - Path=/name/**
        filters:
        - StripPrefix=2

请求/name/blue/red会转发到/red。

RequestSize

请求大小。

spring:
  cloud:
    gateway:
      routes:
      - id: request_size_route
        uri: http://localhost:8080/upload
        predicates:
        - Path=/upload
        filters:
        - name: RequestSize
          args:
            maxSize: 5000000

超过5M的请求会返回413错误。 

Default-filters

对所有请求添加过滤器。

spring:
  cloud:
    gateway:
      default-filters:
      - AddResponseHeader=X-Response-Default-Red, Default-Blue
      - PrefixPath=/httpbin

Gateway令牌桶限流

Spring Cloud Gateway官方就提供了RequestRateLimiterGatewayFilterFactory这个类,适用Redis和lua脚本实现了令牌桶的方式。具体实现逻辑在RequestRateLimiterGatewayFilterFactory类中,lua脚本在如下图所示的文件夹中:

接下来我们使用redis作为令牌桶来实现限流。步骤如下:

  • 引入依赖
  • 创建限流标识
  • 配置限流速率

1、引入依赖

<!--基于Redis实现限流-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

2、增加限流标识

限流通常要根据某一个参数值作为参考依据来限流,比如每个IP每秒钟只能访问2次,此时是以IP为参考依据,我们创建根据IP限流的对象,该对象需要实现接口KeyResolver:

public class IpKeyResolver implements KeyResolver {

    /***
     * 根据IP限流
     * @param exchange
     * @return
     */
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}

我们如果需要根据IP限流,则需要将IpKeyResolver的实例交给Spring容器管理。直接将其放在启动类下即可。

@Bean(name = "ipKeyResolver")
public KeyResolver userIpKeyResolver() {
    return new IpKeyResolver();
}

3、限流配置

核心配置文件中添加RequestRateLimiter配置:

spring:
  application:
    name: cloud-gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名称进行路由
      routes:
        - id: logistics_route #路由的id,没有规定规则但要求唯一,建议配合服务名
          #匹配后提供服务的路由地址,这里使用服务名
          #uri: http://localhost:6677
          uri: lb://cloud-gateway-logistics-service
          predicates:
            - Path=/logistics/get/** # 断言,路径相匹配的进行路由
          filters:
            # 指定过滤器
            - name: RequestRateLimiter
              args:
                # 指定限流标识
                key-resolver: '#{@ipKeyResolver}'
                # 速率限流
                redis-rate-limiter.replenishRate: 1
                # 能容纳的并发流量总数
                redis-rate-limiter.burstCapacity: 2
  #我们是依靠redis来进行限流的,当然需要配置redis
  redis:
    #单机配置
    host: localhost
    port: 6379

在上面的配置文件,配置了RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:

  • burstCapacity,令牌桶总容量。
  • replenishRate,令牌桶每秒填充平均速率。
  • key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。

此时我们请求限流的接口不停刷新进行测试,效果如下:

image-20211219125339926

配置跨域请求

GateWay在使用的过程中可能会遇到跨域的问题,这时候我们就需要相关的配置来解决这个问题。

方案一(不推荐)

添加一个CorsConfig配置类(如果不行,可以不写此类,和springcloud的版本有关,请使用第二种或第三种方案),但推荐使用配置文件的方式。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;

/**
 * <h5>描述:跨域访问控制,前后分离必配</h5>
 * 推荐采用配置文件的方式来实现
 */
@Configuration
public class CorsConfig {
  
  @Bean
  public CorsWebFilter corsFilter() {
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    // 允许任何域名使用
    corsConfiguration.addAllowedOrigin("*");
    // 允许任何头
    corsConfiguration.addAllowedHeader("*");
    // 允许任何方法(post、get等)
    corsConfiguration.addAllowedMethod("*");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
    source.registerCorsConfiguration("/**", corsConfiguration);

    return new CorsWebFilter(source);
  }
}

方案二

配置application.yml

spring:
  cloud:
    gateway:
      # 允许跨域请求配置
      globalcors:
        cors-configurations:
          '[/**]':
            # 允许任何域名使用
            allowedOrigins: "*"
            # 允许任何头
            allowedHeaders: "*"
            # 允许任何方法(post、get等)
            allowedMethods: "*"
            # sessionid 多次访问一致
            allowCredentials: true
        # 允许来自所有域名(allowedOrigins)的所有请求方式(allowedMethods)发出CORS请求
        add-to-simple-url-handler-mapping: true   # 允许来自所有域名(allowedOrigins)的所有请求方式(allowedMethods)发出CORS请求

方案三

配置application.properties

# 允许任何域名使用
spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowedOrigins=*
# 允许任何头
spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowedHeaders=*
# 允许任何方法(post、get等)
spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowedMethods=*
# sessionid 多次访问一致
spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowCredentials=true
# 允许来自所有域名(allowedOrigins)的所有请求方式(allowedMethods)发出CORS请求
spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true

模拟跨域

找一个有jquey的网站(如:https://www.jquery123.com/),F12打开控制台,在控制台中输入一个ajax请求,换上自己的请求地址和参数,发送即可:

$.get('http://localhost:8877/logistics/get?name=linhongwei');

控制台打印:

加上跨域配置后,重启服务,再次访问:

说明跨域设置配置成功。

 

到此,Gateway的学习就完了。敬请更多的文章,谢谢!!!

posted @ 2022-05-04 21:56  残城碎梦  阅读(845)  评论(0编辑  收藏  举报