(六)springcloud 服务网关-Spring Cloud Gateway

时间:2022-11-28 20:03:00

Spring Cloud Gateway is built upon Spring Boot 2.0, Spring WebFlux, and Project Reactor. As a consequence many of the familiar synchronous libraries (Spring Data and Spring Security, for example) and patterns you may not apply when using Spring Cloud Gateway. If you are unfamiliar with these projects we suggest you begin by reading their documentation to familiarize yourself with some of the new concepts before working with Spring Cloud Gateway.

必须要补 Spring WebFlux 和 Project Reactor 的技术

Spring Cloud Gateway requires the Netty runtime provided by Spring Boot and Spring Webflux. It does not work in a traditional Servlet Container or built as a WAR.

Netty 也要补

Spring Cloud Gateway功能:

  • 基于Spring Framework 5,Project Reactor和Spring Boot 2.0构建
  • 能够匹配任何请求属性上的路由。
  • 谓词和过滤器特定于路线。
  • Hystrix断路器集成。
  • Spring Cloud DiscoveryClient集成
  • 易于编写谓词和过滤器
  • 请求率限制
  • 路径重写

初体验

依赖:

之前学的东西都直接用进来,去服务发现上注册,从集中配置上拉配置文件

<!-- 网关依赖 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置(bootstrap.yml):

  • 如果包含starter,但不想启用网关功能:spring.cloud.gateway.enabled=false(默认为true)
server:
  port: 9001

spring:
  application:
    name: gateway-server
  profiles:
    active: dev
  cloud:
    config:
      label: master
      profile: ${spring.profiles.active}
      discovery:
        service-id: config-server
        enabled: true

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/

启动类:

@SpringCloudApplication
@RestController
public class GatewayApplication {

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

    @Bean
    public RouteLocator myRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(p -> p
                        .path("/get")
                        .filters(f -> f.addRequestHeader("Hello", "World"))
                        .uri("http://localhost:4001/config/param"))
                .build();
    }
}

概念

Route:路由网关的基本构建块。它由ID,目标URI,谓词集合和过滤器集合定义。如果聚合谓词为真,则匹配路由

Predicate:这是一个Java 8函数断言。输入类型是Spring Framework ServerWebExchange。这允许开发人员匹配来自HTTP请求的任何内容,例如标头或参数。

Filters:这些是使用特定工厂构建的Spring Framework GatewayFilter实例。这里,可以在发送下游请求之前或之后修改请求和响应。

工作流程:

客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。此处理程序运行通过特定于请求的过滤器链发送请求。滤波器被虚线划分的原因是滤波器可以在发送代理请求之前或之后执行逻辑。执行所有“预”过滤器逻辑,然后进行代理请求。在发出代理请求之后,执行“post”过滤器逻辑。

在没有端口的路由中定义的URI将分别为HTTP和HTTPS URI获取默认端口设置为80和443。

Spring Cloud Gateway将路由作为Spring WebFlux HandlerMapping基础结构的一部分进行匹配。Spring Cloud Gateway包含许多内置的Route Predicate工厂。所有这些 Predicate 都匹配HTTP请求的不同属性。多路线谓词工厂可以组合并通过逻辑组合and

Predicate

Predicate来自于java8的接口。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。add–与、or–或、negate–非。

配置:

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

内置的Predicate

1、请求时间匹配

  • After Route Predicate Factory
    • - After=2017-01-20T17:42:47.789-07:00[America/Denver]
    • 与2017年1月20日17:42 Mountain Time(Denver)之后的所有请求相匹配
  • Before Route Predicate Factory
    • - Before=2017-01-20T17:42:47.789-07:00[America/Denver]
    • 与2017年1月20日17:42 Mountain Time(Denver)之前的所有请求相匹配
  • After Route Predicate Factory
    • - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
    • 与2017年1月20日17:42之后和2017年1月21日17:42之前的任何请求相匹配

2、Cookie匹配

  • Cookie Route Predicate Factory
    • - Cookie=chocolate, ch.p
    • 匹配请求具有名为chocolatewho的值与ch.p正则表达式匹配的cookie

3、Header匹配

  • Header Route Predicate Factory
    • - Header=X-Request-Id, \d+
    • 请求头X-Request-Id其值与\d+正则表达式匹配(具有一个或多个数字的值),则此路由匹配

4、Host匹配

  • Host Route Predicate Factory
    • - Host=**.somehost.org,**.anotherhost.org
    • This route would match if the request has a Host header has the value www.somehost.org or beta.somehost.org or www.anotherhost.org.
    • - {sub}.myhost.org
    • 将URI模板变量(sub如上例中定义的)提取为名称和值的映射,并将其放在ServerWebExchange.getAttributes()带有定义的键的位置ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE

5、Method匹配

  • Method Route Predicate Factory
    • - Method=GET
    • 匹配请求方法为GET的请求

6、Path匹配

  • Path Route Predicate Factory
    • - Path=/foo/{segment},/bar/{segment}
    • This route would match if the request path was, for example: /foo/1 or /foo/bar or /bar/baz.
    • 将URI模板变量(segment如上例中定义的)提取为名称和值的映射,并将其放在ServerWebExchange.getAttributes()带有定义的键的位置ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE

7、QueryParam匹配

  • Query Route Predicate Factory
    • - Query=baz
    • This route would match if the request contained a baz query parameter.
    • - Query=foo, ba.
    • 请求包含foo其值与ba.regexp 匹配的查询参数,则此路由将匹配,因此bar并且baz将匹配

8、Remote匹配

  • RemoteAddr Route Predicate Factory
    • - RemoteAddr=192.168.1.1/24
    • This route would match if the remote address of the request was, for example, 192.168.1.10.****

示例:

1、时间匹配

@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
  ZonedDateTime zonedDateTime = ZonedDateTime.now().plusDays(-1);
  String url = "http://localhost:4001/config/param";
  return builder.routes()
    .route("before-predicate",
           predicateSpec -> predicateSpec
           .before(zonedDateTime)
           .uri(url))
    .build();
}

2、Cookie匹配

@Bean
public RouteLocator routers(RouteLocatorBuilder builder) {
  String url = "http://localhost:4001/config/param";
  return builder.routes()
    .route(predicateSpec -> predicateSpec
           .cookie("cookieName", "^\\d+$")
           .uri(url))
    .build();
}

3、Host匹配

@Bean
public RouteLocator routers(RouteLocatorBuilder builder) {
  String url = "http://localhost:4001/config/param";
  return builder.routes()
    .route(predicateSpec -> predicateSpec.host("localhost").uri(url))
    .build();
}

4 ...

Filter

过滤器类型

1、按执行顺序分

pre过滤器:转发之前执行。参数校验、权限校验、流量监控、日志输出、协议转换等

post过滤器:转发之后执行。响应内容、响应头的修改,日志的输出,流量监控等

2、按作用范围分

global filter:所有路由

gateway filter:指定的路由

GatewayFilter

1、AddRequestHeaderGatewayFilterFactory:在匹配的请求上添加请求头,如示例中,添加请求头:X-Request-Foo;值:bar

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        filters:
        # 添加请求头 X-Request-Foo:Bar
        - AddRequestParameter=X-Request-Foo, Bar

2、AddRequestParameterGatewayFilterFactory:在匹配的请求上添加请求参数

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        filters:
        # 添加请求参数 foo=bar; 在query串中添加
        - AddRequestParameter=foo, bar

3、AddResponseHeaderGatewayFilterFactory:在匹配的请求上添加响应头

spring:
  cloud:
    gateway:
      routes:
      - id: add_response_header_route
        uri: https://example.org
        filters:
        # 添加响应头 X-Response-Foo:Bar
        - AddResponseHeader=X-Response-Foo, Bar

4、HystrixGatewayFilterFactory:将断路器引入网关路由

spring:
  cloud:
    gateway:
      routes:
      - id: hystrix_route
        uri: https://example.org
        filters:
        - Hystrix=myCommandName

5、HystrixGatewayFilterFactory:将断路器引入网关路由,保护您的服务免受级联故障的影响,并允许您在下游故障时提供回退响应。

6、PrefixPathGatewayFilterFactory:将为/mypath所有匹配请求的路径添加前缀

7、PreserveHostHeaderGatewayFilterFactory:设置路由过滤器将检查的请求属性,以确定是否应发送原始主机头,而不是http客户端确定的主机头。

8、RequestRateLimiterGatewayFilterFactory:RateLimiter实现来确定是否允许当前请求继续。如果不是,HTTP 429 - Too Many Requests则返回(默认情况下)状态。

9、RedirectToGatewayFilterFactory: takes a status and a url parameter. The status should be a 300 series redirect http code, such as 301. The url should be a valid url. This will be the value of the Location header

10、RemoveRequestHeaderGatewayFilterFactory:接受一个name参数。它是要删除的标头的名称

11、RemoveResponseHeaderGatewayFilterFactory:接受一个name参数。它是要删除的标头的名称

12、...

Global Filter

所有实例GlobalFilter和所有路由特定实例添加GatewayFilter到过滤器链中。这个组合的过滤器链按org.springframework.core.Ordered接口排序,可以通过实现getOrder()方法或使用@Order注释来设置。

1、LoadBalancerClientFilter:交换属性查找一个URI ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR。如果url有一个lb方案(即lb://myservice),它将使用Spring Cloud LoadBalancerClient将名称(myservice在前面的示例中)解析为实际的主机和端口,并替换相同属性中的URI。未修改的原始URL将附加到ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR属性中的列表中。过滤器还将查看ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR属性以查看它是否等于lb,然后应用相同的规则。

2、...

DiscoveryClient Route Definition Locator

The Gateway can be configured to create routes based on services registered with a DiscoveryClient compatible service registry.

1、set spring.cloud.gateway.discovery.locator.enabled=true and make sure a DiscoveryClient implementation is on the classpath and enabled (such as Netflix Eureka, Consul or Zookeeper).

2、Configuring Predicates and Filters For DiscoveryClient Routes

3、自定义DiscoveryClient路由使用的谓词和/或过滤器,可以通过设置spring.cloud.gateway.discovery.locator.predicates[x]和来自定义spring.cloud.gateway.discovery.locator.filters[y]

spring.cloud.gateway.discovery.locator.predicates[0].name:Path
spring.cloud.gateway.discovery.locator.predicates[0].args [pattern]:“'/'+ serviceId +'/ **'”
spring.cloud.gateway.discovery.locator.predicates[1].name:Host
spring.cloud.gateway.discovery.locator.predicates[1].args [pattern]:“'**。foo.com'”
spring.cloud.gateway.discovery.locator.filters[0].name:Hystrix
spring.cloud.gateway.discovery.locator.filters[0].args [name]:serviceId
spring.cloud.gateway.discovery.locator.filters[1].name:RewritePath
spring.cloud.gateway.discovery.locator.filters[1].args [regexp]:“'/'+ serviceId +'/(?<remaining>.*)'”
spring.cloud.gateway.discovery.locator.filters[1].args [replacement]:“'/ $ {remaining}'”

动态路由

监控端点:

GET /actuator/gateway/globalfilters:检索应用于所有路由的全局过滤器

GET /actuator/gateway/routefilters:查看路由过滤器

POST /actuator/gateway/refresh:刷新路由缓存

GET /actuator/gateway/routes:检索网关中定义的路由

GET /actuator/gateway/routes/{id}:检索特定路径的路由信息

POST/DELETE /gateway/routes/{id_route_to_create}:创建或删除特定路由

1、初始化路由

无论是yml还是代码,这些配置最终都是被封装到RouteDefinition对象中。

一个RouteDefinition有个唯一的ID,如果不指定,就默认是UUID,多个RouteDefinition组成了gateway的路由系统。

所有路由信息在系统启动时就被加载装配好了,并存到了内存里

涉及到的相关bean

RouteDefinitionRepository: 从存储器中加载路由
public interface RouteDefinitionLocator {

    Flux<RouteDefinition> getRouteDefinitions();
  
}

2、实现自己的加载逻辑,这里从redis中加载

添加依赖:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

创建RedisRouteDefinitionRepository,实现RouteDefinitionRepository

@Component
@Slf4j
@AllArgsConstructor
@SuppressWarnings("all")
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    private static final String ROUTE_KEY = "gateway_route_key";

    private final RedisTemplate redisTemplate;

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinition.class));
        List<RouteDefinition> values = redisTemplate.opsForHash().values(ROUTE_KEY);
        log.debug("redis 中路由定义条数: {}, {}", values.size(), values);
        return Flux.fromIterable(values);
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(r -> {
            log.info("保存路由信息{}", r);
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.opsForHash().put(ROUTE_KEY, r.getId(), r);
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        routeId.subscribe(id -> {
            log.info("删除路由信息{}", id);
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.opsForHash().delete(ROUTE_KEY, id);
        });
        return Mono.empty();
    }
}

路由的定义可以任何来源初始化到redis,这里只模拟

@PostConstruct
public void main() {
  RouteDefinition definition = new RouteDefinition();
  definition.setId("id");
  URI uri = UriComponentsBuilder.fromHttpUrl("http://localhost:4001/config/param").build().toUri();
  definition.setUri(uri);

  //定义第一个断言
  PredicateDefinition predicate = new PredicateDefinition();
  predicate.setName("Path");

  Map<String, String> predicateParams = new HashMap<>(8);
  predicateParams.put("pattern", "/get");
  predicate.setArgs(predicateParams);

  //定义Filter
  FilterDefinition filter = new FilterDefinition();
  filter.setName("AddRequestHeader");
  Map<String, String> filterParams = new HashMap<>(8);
  //该_genkey_前缀是固定的,见org.springframework.cloud.gateway.support.NameUtils类
  filterParams.put("_genkey_0", "header");
  filterParams.put("_genkey_1", "addHeader");
  filter.setArgs(filterParams);

  FilterDefinition filter1 = new FilterDefinition();
  filter1.setName("AddRequestParameter");
  Map<String, String> filter1Params = new HashMap<>(8);
  filter1Params.put("_genkey_0", "param");
  filter1Params.put("_genkey_1", "addParam");
  filter1.setArgs(filter1Params);

  definition.setFilters(Arrays.asList(filter, filter1));
  definition.setPredicates(Arrays.asList(predicate));

  System.out.println("definition:" + JSONUtil.toJsonStr(definition));
  redisTemplate.setKeySerializer(new StringRedisSerializer());
  redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinition.class));
  redisTemplate.opsForHash().put(ROUTE_KEY, "router1", definition);
}

启动网关,装配RedisRouteDefinitionRepository后,spring调用main(),创建一条路由

GET /actuator/gateway/routes:查看当前路由信息

3、也可以使用http请求实现,这里就不写了。