SpringCloud系列(二十一):网关Gateway元数据,超时,跨域,HTTPS,监控,问题定位

star2017 1年前 ⋅ 703 阅读

Spring Cloud Gateway 网关除了核心的路由判断表达式(predicate :有的译为谓词)和过滤器外,还有一些其它的设置,以便于可以更好的配置和应用 Gateway。

例如,元数据配置,超时处理,跨域处理,HTTPS 安全配置,监控/指标 收集,问题定位等。可以添加一些全局配置,可以收集健康数据,便于快速定位问题等等。

元数据

可以通过使用元数据来给每个路由配置额外的参数(对所有路由起效的全局参数)。示例如下:

spring:
  cloud:
    gateway:
      routes:
      - id: route_with_metadata
        uri: https://example.org
        metadata:
          optionName: "OptionValue"
          compositeObject:
            name: "value"
          iAmNumber: 1

可以从 Exchange 中获取的元数据属性。如下示例:

Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
// get all metadata properties
route.getMetadata();
// get a single metadata property
route.getMetadata(someKey);

路由定义

SpringCloudGateway 的配置由 RouteDefinitionLocator 实例的集合驱动。下面所示 RouteDefinitionLocator接口:

public interface RouteDefinitionLocator {
    Flux<RouteDefinition> getRouteDefinitions();
}

默认情况下,通过 Spring Boot’s @ConfigurationProperties 机制,一个 PropertiesRouteDefinitionLocator会加载配置属性。

PropertiesRouteDefinitionLocator 继承自 RouteDefinitionLocator,在网关自动配置类 GatewayAutoConfiguration 中将其注册为了 Bean,注入了 GatewayProperties

较早的配置示例均使用快捷方式,该快捷方式使用位置参数而不是命名参数。 以下两个示例是等效的:

spring:
  cloud:
    gateway:
      routes:
      - id: setstatus_route
        uri: https://example.org
        filters:
        - name: SetStatus #命名参数
          args:
            status: 401
      - id: setstatusshortcut_route
        uri: https://example.org
        filters:
        - SetStatus=401    #位置参数

对于网关的某些用法,属性是足够的,但是某些生产用例将从外部资源(例如 数据库)加载配置中受益。

未来的里程碑版本将基于 Spring Data Repositories(例如 Redis,MongoDB 和 Cassandra)使用 RouteDefinitionLocator 实现。

超时处理

Gateway 可以为所有路由 或 重写指定的路由配置 HTTP 超时(连接超时 和 响应超时)。

全局超时

配置全避局 HTTP 超时,有两个参数可配置:connect-timeoutresponse-timeout

  • connect-timeout:必须指定位 毫秒(milliseconds)单位
  • response-timeout:必须指定位 java.time.Duration 类型

全局 HTTP 超时示例:

spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 1000
        response-timeout: 5s

前置路由超时

配置前置路由(per-route)超时,有两个参数可用:

  • connect-timeout:必须指定为 毫秒 单位。
  • response-timeout:必须指定为 毫秒 单位。

per-route http timeouts configuration via configuration

spring:
  cloud:
    gateway:
      routes:
       - id: per_route_timeouts
        uri: https://example.org
        predicates:
          - name: Path
            args:
              pattern: /delay/{timeout}
        metadata:
          response-timeout: 200
          connect-timeout: 200

per-route timeouts configuration using Java DSL

import static org.springframework.cloud.gateway.support.RouteMetadataUtils.CONNECT_TIMEOUT_ATTR;
import static org.springframework.cloud.gateway.support.RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR;

      @Bean
      public RouteLocator customRouteLocator(RouteLocatorBuilder routeBuilder){
         return routeBuilder.routes()
               .route("test1", r -> {
                  return r.host("*.somehost.org").and().path("/somepath")
                        .filters(f -> f.addRequestHeader("header1", "header-value-1"))
                        .uri("http://someuri")
                        .metadata(RESPONSE_TIMEOUT_ATTR, 200)
                        .metadata(CONNECT_TIMEOUT_ATTR, 200);
               })
               .build();
      }

流式路由API

为了在 Java 中实现简单的配置,RouteLocatorBuilder bean 包含了一个流式的API。如下示例:

// static imports from GatewayFilters and RoutePredicates
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder, ThrottleGatewayFilterFactory throttle) {
    return builder.routes()
            .route(r -> r.host("**.abc.org").and().path("/image/png")
                .filters(f ->
                        f.addResponseHeader("X-TestHeader", "foobar"))
                .uri("http://httpbin.org:80")
            )
            .route(r -> r.path("/image/webp")
                .filters(f ->
                        f.addResponseHeader("X-AnotherHeader", "baz"))
                .uri("http://httpbin.org:80")
                .metadata("key", "value")
            )
            .route(r -> r.order(-1)
                .host("**.throttle.org").and().path("/get")
                .filters(f -> f.filter(throttle.apply(1,
                        1,
                        10,
                        TimeUnit.SECONDS)))
                .uri("http://httpbin.org:80")
                .metadata("key", "value")
            )
            .build();
}

此样式还允许更多的自定义路由判断表达式(谓词断言)。通过 RouteDefinitionLocator Bean 定义的判断表达式使用逻辑 and 进行组合。通过使用流式 Java API,可以在 Predicate 类上使用 and()or()negate() 运算符。

DiscoveryClient Routes

DiscoveryClient 路由定义定位器

可以将网关配置为基于在 DiscoveryClient 兼容服务注册表中注册的服务(服务注册和服务发现)来创建路由。

要启用此配置,设置 spring.cloud.gateway.discovery.locator.enabled=true,并确保在类路径上有一个 DiscoveryClient 实现(例如,Netflix Eureka,Consul,或 Zookeeper)且已启用。

为 DiscoveryClient 路由配置判断表达式和过滤器

默认情况下,Gateway 为使用 DiscoveryClient 创建的路由定义单个判断表达式(谓词)和过滤器。

默认判断表达式是用 /service ID/** 样式定义的路径判断表达式(path predicate ),其中 serviceId 来自DiscoveryClient 发现的服务的 ID

默认过滤器是使用正则表达式 /serviceId/(?<remaining>.*)/${remaining}替换的重写路径过滤器(rewrite path filter)。在将请求转发到下游服务之前,将服务ID 从路径中剥离。

如果要自定义 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}'"

注意:上面的配置来自官网,但属性名后的分隔符 : 应该为 =

跨域配置

可以配置 Gateway 控制跨域行为。全局 CORS 配置是一个 URL模式的 Map,配置类是 org.springframework.cloud.gateway.config.GlobalCorsProperties,属性前缀是 spring.cloud.gateway.globalcors,属性名是 corsConfigurations。 如下示例:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "https://docs.spring.io"
            allowedMethods:
            - GET

上面示例:CORS跨域请求配置是,允许来自 docs.spring.io 域名(主机)的所有 GET 请求,开放所有请求路径。

要为某些网关路由判断表达式(predicate:谓词)未处理的请求提供相同的 CORS 配置,设置spring.cloud.gateway.globalcors.add-To-simple-url-handler-mapping属性设置为true。若因路由判断表达式由于 HTTP 请求方式是 OPTIONS 而无法判断为 true 时,但又要支持 CORS 跨域请求,这非常有用。

TLS / SSL

网关可以通过遵循常规的 Spring server configuration 来监听 HTTPS 上的请求。如下所示:

application.yml

server:
  ssl:
    enabled: true
    key-alias: scg
    key-store-password: scg1234
    key-store: classpath:scg-keystore.p12
    key-store-type: PKCS12

可以将网关路由路由到 HTTP 和 HTTPS 后端。 如果要路由到 HTTPS 后端,则可以使用以下配置将网关配置为信任所有下游证书:

application.yml

spring:
  cloud:
    gateway:
      httpclient:
        ssl:
          useInsecureTrustManager: true

使用不安全的信任管理器(trust manager)不适用于生产。 对于生产部署,可以使用以下配置为网关配置一组可以信任的已知证书:

application.yml

spring:
  cloud:
    gateway:
      httpclient:
        ssl:
          trustedX509Certificates:
          - cert1.pem
          - cert2.pem

如果未为 Spring Cloud Gateway 提供受信任的证书,则使用默认的信任库(可以通过设置javax.net.ssl.trustStore系统属性来覆盖它)。

TLS 握手

网关维护一个用于路由到后端的客户端池(client pool)。当通过 HTTPS 进行通信时,客户端发起 TLS 握手。许多超时与此握手关联。可以按如下方式配置这些超时(默认显示):

application.yml

spring:
  cloud:
    gateway:
      httpclient:
        ssl:
          handshake-timeout-millis: 10000
          close-notify-flush-timeout-millis: 3000
          close-notify-read-timeout-millis: 0

Netty访问日志

要启用 响应式 Netty 访问日志,设置 -Dreactor.netty.http.server.accessLogEnabled=true

注意:必须是 Java 系统属性,而不是 Spring Boot 属性。

可以将日志记录系统(logging system)配置为具有单独的访问日志文件。 以下示例创建一个 Logback 配置:

<appender name="accessLog" class="ch.qos.logback.core.FileAppender">
    <file>access_log.log</file>
    <encoder>
        <pattern>%msg%n</pattern>
    </encoder>
</appender>
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="accessLog" />
</appender>

<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
    <appender-ref ref="async"/>
</logger>

问题定位

使用 SpringCloudGateway 时可能出现的常见问题。

日志级别

Log Levels:以下 loggers 可能包含 DEBUGTRACE 级别的有价值的故障排除信息:

  • org.springframework.cloud.gateway
  • org.springframework.http.server.reactive
  • org.springframework.web.reactive
  • org.springframework.boot.autoconfigure.web
  • reactor.netty
  • redisratelimiter

窃听:Wiretap

Reactor Netty HttpClientHttpServer 可以启用窃听。 当将reactor.netty日志级别设置为 DEBUGTRACE 时,且开启了窃听,将开启信息的日志记录,例如,通过发送和接收的 headerbody

要启用 wiretap,需为 httpserver设置 spring.cloud.gateway.httpserver.wiretap=true;为 httpclient 设置spring.cloud.gateway.httpclient.wiretap=true

开发人员指南

下面是写编写网关的自定义组件的基本指南。

自定义路由判断表达式工厂

要自定义路由判断表达式(Route Predicate),需要实现 RoutePredicateFactory接口,可以继承一个名为 AbstractRoutePredicateFactory的抽象类。

MyRoutePredicateFactory.java

public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<HeaderRoutePredicateFactory.Config> {

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

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        // grab configuration from Config object
        return exchange -> {
            //grab the request
            ServerHttpRequest request = exchange.getRequest();
            //take information from the request to see if it
            //matches configuration.
            return matches(config, request);
        };
    }

    public static class Config {
        //Put the configuration properties for your filter here
    }

}

自定义 GatewayFilter工厂

要写一个 GatewayFilter,必须实现 GatewayFilterFactory。可以继承一个名为 AbstractGatewayFilterFactory的抽象类。如下示例:

PreGatewayFilterFactory.java:前置过滤器

public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory<PreGatewayFilterFactory.Config> {

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

    @Override
    public GatewayFilter apply(Config config) {
        // grab configuration from Config object
        return (exchange, chain) -> {
            //If you want to build a "pre" filter you need to manipulate the
            //request before calling chain.filter
            ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
            //use builder to manipulate the request
            return chain.filter(exchange.mutate().request(request).build());
        };
    }

    public static class Config {
        //Put the configuration properties for your filter here
    }
}

PostGatewayFilterFactory.java:后置过滤器

public class PostGatewayFilterFactory extends AbstractGatewayFilterFactory<PostGatewayFilterFactory.Config> {

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

    @Override
    public GatewayFilter apply(Config config) {
        // grab configuration from Config object
        return (exchange, chain) -> {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                ServerHttpResponse response = exchange.getResponse();
                //Manipulate the response in some way
            }));
        };
    }

    public static class Config {
        //Put the configuration properties for your filter here
    }

}

自定义全局过滤器

自定义全局过滤器(global filter),必须实现 GlobalFilter 接口,这会应用于所有请求的过滤。

下面示例全局前置和后置过滤器:

@Bean
public GlobalFilter customGlobalFilter() {
    return (exchange, chain) -> exchange.getPrincipal()
        .map(Principal::getName)
        .defaultIfEmpty("Default User")
        .map(userName -> {
          //adds header to proxied request
          exchange.getRequest().mutate().header("CUSTOM-REQUEST-HEADER", userName).build();
          return exchange;
        })
        .flatMap(chain::filter);
}

@Bean
public GlobalFilter customGlobalPostFilter() {
    return (exchange, chain) -> chain.filter(exchange)
        .then(Mono.just(exchange))
        .map(serverWebExchange -> {
          //adds header to response
          serverWebExchange.getResponse().getHeaders().set("CUSTOM-RESPONSE-HEADER",
              HttpStatus.OK.equals(serverWebExchange.getResponse().getStatusCode()) ? "It worked": "It did not work");
          return serverWebExchange;
        })
        .then();
}

使用Spring MVC或Webflux构建一个简单的Gateway

注意:以下描述了替代样式的网关。不同于先前文档中的描述。

Spring Cloud Gateway 提供了一个名为 ProxyExchange 的实用对象。可以在常规 Spring web handler(Web 处理器)中使用它作为方法参数。

它通过镜像 HTTP 动词的方法来支持基本的下游 HTTP Exchange。对于 MVC,它还支持通过 forward() 方法转发到本地处理器。要使用 ProxyExchange,需要类路径中引入正确的模块(spring-cloud-gateway-mvcspring-cloud-gateway-webflux)。

以下 MVC 示例将 请求 代理到 /test下游远程服务器:

@RestController
@SpringBootApplication
public class GatewaySampleApplication {

    @Value("${remote.home}")
    private URI home;

    @GetMapping("/test")
    public ResponseEntity<?> proxy(ProxyExchange<byte[]> proxy) throws Exception {
        return proxy.uri(home.toString() + "/image/png").get();
    }
}

下面是 Webflux 的一个相同的示例:

@RestController
@SpringBootApplication
public class GatewaySampleApplication {

    @Value("${remote.home}")
    private URI home;

    @GetMapping("/test")
    public Mono<ResponseEntity<?>> proxy(ProxyExchange<byte[]> proxy) throws Exception {
        return proxy.uri(home.toString() + "/image/png").get();
    }
}

ProxyExchange 上的便捷方法使处理器(handler)方法可以发现并增强传入请求的 URI 路径。

例如,可能希望提取路径的后缀以将它们传递到下游:

@GetMapping("/proxy/path/**")
public ResponseEntity<?> proxyPath(ProxyExchange<byte[]> proxy) throws Exception {
  String path = proxy.path("/proxy/path/");
  return proxy.uri(home.toString() + "/foos/" + path).get();
}

网关处理程序方法可以使用 Spring MVCWebflux 的所有功能。 结果,例如,可以注入请求标头和查询参数,并且可以使用映射批注中的声明来约束传入的请求。 更多参考 Spring MVC 中有关 @RequestMapping 的文档。

可以使用 ProxyExchange 上的header()方法将 HTTP 头添加到下游服务的响应中。

还可以通过将映射器(mapper)添加到get()方法(和其他方法)来操纵响应头(以及响应中您需要的任何其他内容)。 映射器是一种函数,它接受传入的 ResponseEntity 并将其转换并传出。

为不将敏感头(sensitive)(默认为 cookieauthorization)和代理(proxy)x-forwarded-*)头传递到下游服务提供了一流支持。

更多内容请访问:IT源点

相关文章推荐

全部评论: 0

    我有话说: