SpringCloud系列(十九):网关Gateway特性,述语,自动配置与简单应用

star2017 1年前 ⋅ 394 阅读

即将开发的新平台在网关组件上选择了 GatewaySpring 官方主推 Gateway。Zuul 1 虽然很成熟,但不再维护,Zuul 2 已闭源。

网关组件,不管是 Zuul 还是 Gateway,其核心技术脱离不了 代理请求过滤器。本篇根据 Spring Cloud Gateway 官方文档对其功能,配置,应用进行详细描述。

Spring Cloud Gateway 是一个用于在 Spring MVC 之上构建 API 网关的组件,提供一种简单而有效的方式来路由到 API(使用路由来处理对后端服务的请求),并为它们提供跨领域的关注点,例如:安全,监控 / 指标 和 弹性。快速构建一个 Gateway,参考 Building a Gateway

特性

  • 基于 Spring Framework 5,Reactor 和 Spring Boot 2.0 项目
  • 能够匹配任何请求属性上的路由。
  • 基于特定路由的过滤器和判断式(Predicates )
  • 整合了熔断器 Hystrix Circuit Breaker
  • 整合了服务发现 Spring Cloud Discovery
  • 易于编写 Predicates 和 Filters
  • 支持请求限流 Request Rate Limiting
  • 支持路径重写 Path Rewriting

述语

  • Route(路由):网关的核心部件,由 ID目标 URI判断表达式集合过滤器集合定义,如果判断表达式为true,则匹配路由。
  • Predicate(判断表达式):是一个 Java 8 函数判断表达式,输入类型是 Spring Framework ServerWebExchange,用于匹配来自 HTTP 请求的任何内容,例如请求头或参数。
  • Filter(过滤器):由特定工厂构造的 Spring Framework GatewayFilter 实例,可用于转发请求(下游请求)之前或之后修改请求和响应。

自动配置

引入依赖

创建 Spring Cloud Gateway 网关服务,需要引入 spring-cloud-starter-gateway 依赖。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

其核心库是 spring-cloud-gateway-core,在该包的元信息目录下有一个 spring.factories 文件,文件中是一些 xxxAutoConfiguration 的类,会被 Spring Boot 扫描并注册为 Bean,依赖的 Bean 也会随之注册。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\
org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration

org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.gateway.config.GatewayEnvironmentPostProcessor

从上面文件内容中可看出 Gateway 提供了 类路径告警,负载均衡,监控/指标,集成 Redis,服务发现的自动配置,其中 GatewayAutoConfiguration是核心的自动配置类。可在 IDEA 工具中查看这些自动配置类的源码,了解其中的依赖配置。

源码分析

  1. GatewayAutoConfiguration 类注解

    查看 GatewayAutoConfiguration 源码,可以看到类上有一些注解。

    @Configuration
    @ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
    @EnableConfigurationProperties
    @AutoConfigureBefore({ HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class })
    @AutoConfigureAfter({ GatewayLoadBalancerClientAutoConfiguration.class,
            GatewayClassPathWarningAutoConfiguration.class })
    @ConditionalOnClass(DispatcherHandler.class)
    public class GatewayAutoConfiguration {
        //.......
    }
    

    属性条件注解:spring.cloud.gateway.enabled 缺省值为 true,即添加了 Gateway 依赖后默认启用。在包根路径的 META-INF 目录中有一个 spring-configuration-metadata.json 元数据文件,也定义了该属性的默认值为 true

    GatewayLoadBalancerClientAutoConfigurationGatewayClassPathWarningAutoConfiguration 是两个前置自动配置,分别负责 负载均衡类检测并告警

    HttpHandlerAutoConfigurationWebFluxAutoConfiguration 是两个后置自动配置类,主要作用是 HTTP 前置处理。

  2. GatewayClassPathWarningAutoConfiguration

    Spring Cloud Gateway 并不与 Spring MVC 兼容,不需要添加 spring-boot-starter-web 依赖。

    GatewayClassPathWarningAutoConfiguration 负载检查来自 Spring MVC 的 DispatcherServletDispatcherHandler,如果存在, 就打印不兼容的 warn 信息,提示移除 spring-boot-starter-web 依赖。

  3. NettyConfiguration

    GatewayAutoConfiguration 里面首先是一个静态 NettyConfiguration 类,依赖了 reactor.netty.http.client.HttpClient

    NettyConfiguration 里面创建了一个 reactor.netty.http.client.HttpClient Bean,从这里可以看出,Gateway 的 HTTP 处理是基于 Netty 实现的。

    @Configuration
    @ConditionalOnClass(HttpClient.class)
    protected static class NettyConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public HttpClient httpClient(HttpClientProperties properties) {
            //......省略
        }
    }
    
  4. HttpClientProperties

    HttpClientProperties 是属性配置类,进源码进可看到,属性前缀是 spring.cloud.gateway.httpclient,主要参数有:

    /**
     * Configuration properties for the Netty {@link reactor.netty.http.client.HttpClient}
     */
    @ConfigurationProperties("spring.cloud.gateway.httpclient")
    public class HttpClientProperties {
    
        /** 连接超时, 默认是 45s. */
        private Integer connectTimeout;
    
        /** 响应超时 */
        private Duration responseTimeout;
    
        /** Pool configuration for Netty HttpClient */
        private Pool pool = new Pool();
    
        /** Proxy configuration for Netty HttpClient */
        private Proxy proxy = new Proxy();
    
        /** SSL configuration for Netty HttpClient */
        private Ssl ssl = new Ssl();
    
        //.....省略......
    }
    

    其中 Pool,Proxy,Ssl 都是其内部类,Pool 是静态的,为 Netty HttpClient 连接提供池化; Proxy 里设置代理需要用到的属性,如 host,port,username,password 等;Ssl 是 Ssl 加密请求认证相关设置。

  5. GatewayAutoConfiguration 及其内部类声明的一些 Bean

    内部类 NettyConfiguration 类里,声明了一其 Bean,如下:

    /** HttpClient 属性配置 */ 
    @Bean
    public HttpClientProperties httpClientProperties() {
        return new HttpClientProperties();
    }
    
    /** Netty路由过滤器
     *
     */ 
    @Bean
    public NettyRoutingFilter routingFilter(HttpClient httpClient,
                                            ObjectProvider<List<HttpHeadersFilter>> headersFilters,
                                            HttpClientProperties properties) {
        return new NettyRoutingFilter(httpClient, headersFilters, properties);
    }
    
    /** Netty响应过滤器 */ 
    @Bean
    public NettyWriteResponseFilter nettyWriteResponseFilter(GatewayProperties properties) {
        return new NettyWriteResponseFilter(properties.getStreamingMediaTypes());
    }
    
    /** 响应式 Netty WebSocket 客户端 */ 
    @Bean
    public ReactorNettyWebSocketClient reactorNettyWebSocketClient(HttpClient httpClient) {
        return new ReactorNettyWebSocketClient(httpClient);
    }
    

    Gateway 集成了熔断器 Hystrix,依赖于 spring-cloud-starter-netflix-hystrix组件,内部静态类 HystrixConfiguration 声明了与熔断相关的 Bean:

    @Configuration
    @ConditionalOnClass({HystrixObservableCommand.class, RxReactiveStreams.class})
    protected static class HystrixConfiguration {
        /**
         * HystrixGatewayFilter 工厂
         * 在网关中引入了 Hystrix 熔断器,保护服务不受级联故障影响
         */
        @Bean
        public HystrixGatewayFilterFactory hystrixGatewayFilterFactory(ObjectProvider<DispatcherHandler> dispatcherHandler) {
            return new HystrixGatewayFilterFactory(dispatcherHandler);
        }
        /**
         * FallbackHeadersGatewayFilter 工厂
         * 允许在异常回调的请求头中添加 Hystrix 或Spring Cloud CircuitBreaker 执行异常详细信息
         */
        @Bean
        public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() {
            return new FallbackHeadersGatewayFilterFactory();
        }
    }
    

    GatewayAutoConfiguration 自己声明很多的 Bean,大概可分为 转换器,监听器,路由定位器,属性类,头过滤器,全局过滤器,路由判断表达式工厂,GatewayFilter 过滤器,熔断器配置,监控/指标配置 这几种。

    核心还是围绕 请求响应 做定制化过滤,包括 主机IP,请求URI,请求头,请求体,响应头,响应体 等。

    /**
     * 字符串日期时间 转为 ZonedDateTime 的转换器
     * StringToZonedDateTimeConverter 内部就一个方法, 最终调 toZonedDateTime() 方法
     */
    @Bean
    public StringToZonedDateTimeConverter stringToZonedDateTimeConverter() {
        return new StringToZonedDateTimeConverter();
    }
    
    /**
     * 用于构建 RouteLocator
     */
    @Bean
    public RouteLocatorBuilder routeLocatorBuilder(ConfigurableApplicationContext context) {
        return new RouteLocatorBuilder(context);
    }
    
    /**
     * Gateway 属性, 前缀: spring.cloud.gateway
     * GatewayProperties 包含了多个路由定义和多个过滤器定义
     */
    @Bean
    @ConditionalOnMissingBean
    public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
        return new PropertiesRouteDefinitionLocator(properties);
    }
    
    /**
     * 在内存中的路由定义库, 默认配置
     * 可以想到,路由定义也应该可以存储在外部数据库中, 如 MySQL
     */
    @Bean
    @ConditionalOnMissingBean(RouteDefinitionRepository.class)
    public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
        return new InMemoryRouteDefinitionRepository();
    }
    
    /**
     * 路由定义定位器,作用获取路由定义
     */
    @Bean
    @Primary
    public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
        return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
    }
    
    /**
     * 路由定位器,作用是获取路由
     */
    @Bean
    public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
                                                    List<GatewayFilterFactory> GatewayFilters,
                                                    List<RoutePredicateFactory> predicates,
                                                    RouteDefinitionLocator routeDefinitionLocator,
                                                    @Qualifier("webFluxConversionService")
                                                    ConversionService conversionService) {
        return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters,
                                               properties, conversionService);
    }
    
    /**
     * 缓存路由定位器
     * @Primary 优先装配置 Bean, 这里即默认使用的是缓存路由定位器
     */
    @Bean
    @Primary
    //TODO: property to disable composite?
    public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
        return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
    }
    
    /**
     * 路由刷新监听器
     */
    @Bean
    public RouteRefreshListener routeRefreshListener(ApplicationEventPublisher publisher) {
        return new RouteRefreshListener(publisher);
    }
    
    /**
     * 一组 WebHandler 过滤器链
     */
    @Bean
    public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters) {
        return new FilteringWebHandler(globalFilters);
    }
    
    /**
     * 全局跨域属性
     */
    @Bean
    public GlobalCorsProperties globalCorsProperties() {
        return new GlobalCorsProperties();
    }
    
    /**
     * 路由判断处理映射,实现了 HandlerMapping
     */
    @Bean
    public RoutePredicateHandlerMapping routePredicateHandlerMapping(
        FilteringWebHandler webHandler, RouteLocator routeLocator,
        GlobalCorsProperties globalCorsProperties, Environment environment) {
        return new RoutePredicateHandlerMapping(webHandler, routeLocator,
                                                globalCorsProperties, environment);
    }
    
    // ConfigurationProperty beans(配置属性Bean)
    
    /**
     * Gateway 属性, 前缀是 spring.cloud.gateway
     */
    @Bean
    public GatewayProperties gatewayProperties() {
        return new GatewayProperties();
    }
    
    /**
     * 安全头属性, 前缀是 spring.cloud.gateway.filter.secure-headers
     */
    @Bean
    public SecureHeadersProperties secureHeadersProperties() {
        return new SecureHeadersProperties();
    }
    
    // HttpHeaderFilter beans(Http Header 过滤器 Bean)
    
    /**
     * 转发过滤器, 缺省是启用
     */
    @Bean
    @ConditionalOnProperty(name = "spring.cloud.gateway.forwarded.enabled", matchIfMissing = true)
    public ForwardedHeadersFilter forwardedHeadersFilter() {
        return new ForwardedHeadersFilter();
    }
    
    /**
     * 可以从转发的请求中删除头,前缀是 spring.cloud.gateway.filter.remove-hop-by-hop
     */
    @Bean
    public RemoveHopByHopHeadersFilter removeHopByHopHeadersFilter() {
        return new RemoveHopByHopHeadersFilter();
    }
    
    /**
     * HTTP 扩展头 X-Forwarded-For 过滤器
     * 前缀是 spring.cloud.gateway.x-forwarded, enabled 属性缺省值为 true, 即默认启用
     */
    @Bean
    @ConditionalOnProperty(name = "spring.cloud.gateway.x-forwarded.enabled", matchIfMissing = true)
    public XForwardedHeadersFilter xForwardedHeadersFilter() {
        return new XForwardedHeadersFilter();
    }
    
    // GlobalFilter beans(全局过滤器 Bean)
    
    @Bean
    public AdaptCachedBodyGlobalFilter adaptCachedBodyGlobalFilter() {
        return new AdaptCachedBodyGlobalFilter();
    }
    
    /**
     * URI 过滤器
     */
    @Bean
    public RouteToRequestUrlFilter routeToRequestUrlFilter() {
        return new RouteToRequestUrlFilter();
    }
    
    /**
     * 转发路由过滤器
     */
    @Bean
    public ForwardRoutingFilter forwardRoutingFilter(ObjectProvider<DispatcherHandler> dispatcherHandler) {
        return new ForwardRoutingFilter(dispatcherHandler);
    }
    
    /**
     * 转发路径过滤器
     */
    @Bean
    public ForwardPathFilter forwardPathFilter() {
        return new ForwardPathFilter();
    }
    
    /**
     * WebSocket HTTP 请求的握手实现
     */
    @Bean
    public WebSocketService webSocketService() {
        return new HandshakeWebSocketService();
    }
    
    /**
     * WebSocket HTTP 请求请求过滤器
     */
    @Bean
    public WebsocketRoutingFilter websocketRoutingFilter(WebSocketClient webSocketClient,
                                                         WebSocketService webSocketService,
                                                         ObjectProvider<List<HttpHeadersFilter>> headersFilters) {
        return new WebsocketRoutingFilter(webSocketClient, webSocketService, headersFilters);
    }
    
    /**
     * 权重计算过滤器
     */
    @Bean
    public WeightCalculatorWebFilter weightCalculatorWebFilter(Validator validator) {
        return new WeightCalculatorWebFilter(validator);
    }
    
    /*@Bean
        //TODO: default over netty? configurable
        public WebClientHttpRoutingFilter webClientHttpRoutingFilter() {
            //TODO: WebClient bean
            return new WebClientHttpRoutingFilter(WebClient.routes().build());
        }
    
        @Bean
        public WebClientWriteResponseFilter webClientWriteResponseFilter() {
            return new WebClientWriteResponseFilter();
        }*/
    
    // Predicate Factory beans(各种判断表达式工厂)
    
    /**
     * 指定日期之后的请求
     */
    @Bean
    public AfterRoutePredicateFactory afterRoutePredicateFactory() {
        return new AfterRoutePredicateFactory();
    }
    
    /**
     * 指定日期之前的请求
     */
    @Bean
    public BeforeRoutePredicateFactory beforeRoutePredicateFactory() {
        return new BeforeRoutePredicateFactory();
    }
    
    /**
     * 两个日期之间的请求
     */
    @Bean
    public BetweenRoutePredicateFactory betweenRoutePredicateFactory() {
        return new BetweenRoutePredicateFactory();
    }
    
    /**
     * Cookie 属性判断表达式工厂
     */
    @Bean
    public CookieRoutePredicateFactory cookieRoutePredicateFactory() {
        return new CookieRoutePredicateFactory();
    }
    
    /**
     * Header 属性判断表达式工厂
     */
    @Bean
    public HeaderRoutePredicateFactory headerRoutePredicateFactory() {
        return new HeaderRoutePredicateFactory();
    }
    
    /**
     * Host 属性判断表达式工厂
     */
    @Bean
    public HostRoutePredicateFactory hostRoutePredicateFactory() {
        return new HostRoutePredicateFactory();
    }
    
    /**
     * Method 请求方式判断表达式工厂
     */
    @Bean
    public MethodRoutePredicateFactory methodRoutePredicateFactory() {
        return new MethodRoutePredicateFactory();
    }
    
    /**
     * Path 请求路径 判断表达式工厂
     */
    @Bean
    public PathRoutePredicateFactory pathRoutePredicateFactory() {
        return new PathRoutePredicateFactory();
    }
    
    /**
     * 路径 判断表达式工厂
     */
    @Bean
    public QueryRoutePredicateFactory queryRoutePredicateFactory() {
        return new QueryRoutePredicateFactory();
    }
    
    /**
     * 必须的参数 判断表达式工厂
     */
    @Bean
    public ReadBodyPredicateFactory readBodyPredicateFactory() {
        return new ReadBodyPredicateFactory();
    }
    
    /**
     * 远程IP地址 判断表达式工厂
     */
    @Bean
    public RemoteAddrRoutePredicateFactory remoteAddrRoutePredicateFactory() {
        return new RemoteAddrRoutePredicateFactory();
    }
    
    /**
     * 权重计算 判断表达式工厂
     */
    @Bean
    @DependsOn("weightCalculatorWebFilter")
    public WeightRoutePredicateFactory weightRoutePredicateFactory() {
        return new WeightRoutePredicateFactory();
    }
    
    /**
     * CloudFoundry 路由服务判断表达式工厂 路由服务 */
    @Bean
    public CloudFoundryRouteServiceRoutePredicateFactory cloudFoundryRouteServiceRoutePredicateFactory() {
        return new CloudFoundryRouteServiceRoutePredicateFactory();
    }
    
    // GatewayFilter Factory beans(网关过滤器工厂 Bean)
    /**
     * 添加请求头
     */
    @Bean
    public AddRequestHeaderGatewayFilterFactory addRequestHeaderGatewayFilterFactory() {
        return new AddRequestHeaderGatewayFilterFactory();
    }
    /**
     * 添加请求参数
     */
    @Bean
    public AddRequestParameterGatewayFilterFactory addRequestParameterGatewayFilterFactory() {
        return new AddRequestParameterGatewayFilterFactory();
    }
    /**
     * 添加响应头
     */
    @Bean
    public AddResponseHeaderGatewayFilterFactory addResponseHeaderGatewayFilterFactory() {
        return new AddResponseHeaderGatewayFilterFactory();
    }
    
    /**
     * 修改请求体
     */
    @Bean
    public ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilterFactory(ServerCodecConfigurer codecConfigurer) {
        return new ModifyRequestBodyGatewayFilterFactory(codecConfigurer);
    }
    
    /**
     * 修改响应体
     */
    @Bean
    public ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory(ServerCodecConfigurer codecConfigurer) {
        return new ModifyResponseBodyGatewayFilterFactory(codecConfigurer);
    }
    
    /**
     * 路径前缀
     */
    @Bean
    public PrefixPathGatewayFilterFactory prefixPathGatewayFilterFactory() {
        return new PrefixPathGatewayFilterFactory();
    }
    
    /**
     * 设置一个属性头,默认:preserveHostHeader=true
     * 路由过滤器会检查该头,以确定是否发送原始的主机头,而不是由 HTTP客户端确定的主机头
     */
    @Bean
    public PreserveHostHeaderGatewayFilterFactory preserveHostHeaderGatewayFilterFactory() {
        return new PreserveHostHeaderGatewayFilterFactory();
    }
    
    /**
     * 重定向
     */
    @Bean
    public RedirectToGatewayFilterFactory redirectToGatewayFilterFactory() {
        return new RedirectToGatewayFilterFactory();
    }
    
    /**
     * 移除请求头
     */
    @Bean
    public RemoveRequestHeaderGatewayFilterFactory removeRequestHeaderGatewayFilterFactory() {
        return new RemoveRequestHeaderGatewayFilterFactory();
    }
    
    /**
     * 移除响应头
     */
    @Bean
    public RemoveResponseHeaderGatewayFilterFactory removeResponseHeaderGatewayFilterFactory() {
        return new RemoveResponseHeaderGatewayFilterFactory();
    }
    
    /**
     * 移除响应头
     */
    @Bean(name = PrincipalNameKeyResolver.BEAN_NAME)
    @ConditionalOnBean(RateLimiter.class)
    @ConditionalOnMissingBean(KeyResolver.class)
    public PrincipalNameKeyResolver principalNameKeyResolver() {
        return new PrincipalNameKeyResolver();
    }
    
    /**
     * 请求限流
     */
    @Bean
    @ConditionalOnBean({RateLimiter.class, KeyResolver.class})
    public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(RateLimiter rateLimiter, KeyResolver resolver) {
        return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver);
    }
    
    /**
     * 重写路径
     */
    @Bean
    public RewritePathGatewayFilterFactory rewritePathGatewayFilterFactory() {
        return new RewritePathGatewayFilterFactory();
    }
    
    /**
     * 重试
     */
    @Bean
    public RetryGatewayFilterFactory retryGatewayFilterFactory() {
        return new RetryGatewayFilterFactory();
    }
    
    /**
     * 设置路径
     */
    @Bean
    public SetPathGatewayFilterFactory setPathGatewayFilterFactory() {
        return new SetPathGatewayFilterFactory();
    }
    
    /**
     * 安全头
     */
    @Bean
    public SecureHeadersGatewayFilterFactory secureHeadersGatewayFilterFactory(SecureHeadersProperties properties) {
        return new SecureHeadersGatewayFilterFactory(properties);
    }
    
    /**
     * 设置请求头
     */
    @Bean
    public SetRequestHeaderGatewayFilterFactory setRequestHeaderGatewayFilterFactory() {
        return new SetRequestHeaderGatewayFilterFactory();
    }
    
    /**
     * 设置响应头
     */
    @Bean
    public SetResponseHeaderGatewayFilterFactory setResponseHeaderGatewayFilterFactory() {
        return new SetResponseHeaderGatewayFilterFactory();
    }
    
    /**
     * 重写响应头
     */
    @Bean
    public RewriteResponseHeaderGatewayFilterFactory rewriteResponseHeaderGatewayFilterFactory() {
        return new RewriteResponseHeaderGatewayFilterFactory();
    }
    
    /**
     * 设置HTTP状态码
     */
    @Bean
    public SetStatusGatewayFilterFactory setStatusGatewayFilterFactory() {
        return new SetStatusGatewayFilterFactory();
    }
    
    /**
     * 保存 Session
     */
    @Bean
    public SaveSessionGatewayFilterFactory saveSessionGatewayFilterFactory() {
        return new SaveSessionGatewayFilterFactory();
    }
    
    /**
     * 剥离路径前缀某几位
     */
    @Bean
    public StripPrefixGatewayFilterFactory stripPrefixGatewayFilterFactory() {
        return new StripPrefixGatewayFilterFactory();
    }
    
    /**
     * 根据请求头修改请求 URI
     */
    @Bean
    public RequestHeaderToRequestUriGatewayFilterFactory requestHeaderToRequestUriGatewayFilterFactory() {
        return new RequestHeaderToRequestUriGatewayFilterFactory();
    }
    
    /**
     * 请求大小限制
     * 如果请求大小超过允许值(默认 5M), 过滤器将阻赛请求
     */
    @Bean
    public RequestSizeGatewayFilterFactory requestSizeGatewayFilterFactory() {
        return new RequestSizeGatewayFilterFactory();
    }
    

通过对 Gateway 的自动配置分析,基本上可以明白 Gateway 的核心机制原理了,后续更多是根据需求进行自定义配置。

应用示例

演示一个最简单的 Spring Cloud Gateway 示例。

创建应用

创建一个 Spring Boot 应用,用于集成 Spring Cloud Gateway 作为网关服务器,不需要 spring-boot-starter-web 依赖。

添加 Spring Cloud Gateway 组件,核心库 spring-cloud-starter-gateway ,熔断器组件 spring-cloud-starter-netflix-hystrix,引入监控/指标收集组件 。

注意:Spring Booot 版本必须与 Spring Cloud 版本兼容。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gateway.server</groupId>
    <artifactId>gateway-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway-server</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR3</spring-cloud.version>
    </properties>

    <dependencies>
        <!-- Gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- Hystrix -->
        <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.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <!-- Spring Cloud Dependencies -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

路由转发

下面示例的 routeLocator 方法采用一个 RouteLocatorBuilder,可以轻松地使用它来创建路由,还可在路由中添加 Predicates(判断表达式) 和 过滤器,以便可以在特定条件下进行路由处理,以及根据需要更改请求/响应

@Configuration
public class GatewayConfig {

    /**
     * GatewayAutoConfiguration 中声明了 RouteLocatorBuilder Bean
     *
     * @param builder
     * @return RouteLocator
     */
    /*@Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes().build();
    }*/

    /**
     * 创建一个路由定位器
     * 将 /get 路由到 http://httpbin.org:80
     * 并添加请求头,属性是 Hello, 值是 World
     *
     * @param builder
     * @return RouteLocator
     */
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {

        /**
         * Lambda 写法, 好简洁
         */
        return builder.routes()
                .route(p -> p
                        .path("/get")
                        .filters(f -> f.addRequestHeader("Hello", "World"))
                        .uri("http://httpbin.org:80"))
                .build();


        /**
         * 匿名内部类实现
         */
        /*Function<GatewayFilterSpec, UriSpec> uriSpecFunction = new Function<GatewayFilterSpec, UriSpec>() {
            @Override
            public UriSpec apply(GatewayFilterSpec gatewayFilterSpec) {
                gatewayFilterSpec.addRequestHeader("Hello","World");
                return gatewayFilterSpec;
            }
        };

        Function<PredicateSpec, Route.AsyncBuilder> builderFunction = new Function<PredicateSpec, Route.AsyncBuilder>() {
            @Override
            public Route.AsyncBuilder apply(PredicateSpec predicateSpec) {
                return predicateSpec.path("/get").filters(uriSpecFunction).uri("http://httpbin.org:80");
            }
        };
        return builder.routes().route(builderFunction).build();*/
    }
}

本地启动应用,本地访问:http://localhost:8080/get,收到的响应结果如下:

{
    "args": {},
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "Cache-Control": "no-cache",
        "Content-Length": "0",
        "Forwarded": "proto=http;host=\"localhost:8080\";for=\"0:0:0:0:0:0:0:1:52768\"",
        "Hello": "World",
        "Host": "httpbin.org",
        "Postman-Token": "6e1b345c-22a7-49da-8e7d-13b5aef48059",
        "User-Agent": "PostmanRuntime/7.15.0",
        "X-Amzn-Trace-Id": "Root=1-5e6ae769-74fc64205892b100203ca020",
        "X-Forwarded-Host": "localhost:8080"
    },
    "origin": "0:0:0:0:0:0:0:1, 218.17.0.242",
    "url": "http://localhost:8080/get"
}

可以看到响应数据中,多了自己添加的 Hello 头,值为 World

使用Hystrix

熔断器 可以防止服务异常在级联请求中传递,导致整个系统不可用的情况。

网关后面的服务可能会出现异常,或不可用等,这些不应直接响应给客户端,可以将创建的路由包装在熔断器中,Spring Cloud Gateway 支持集成 Hystrix 熔断器。

模拟请求超时

修改声明 路由定位器 的 Bean,添加熔断过滤器。

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        /**
         * 只要主机是 hystrix.com,就将请求路由到 httpbin
         * 并将请求包装进 HystrixCommand 中, Hystrix filter 可以使用 configuration 对象配置
         * 下面示例,只给 HystrixCommand 设置了 name 为 myHystrix
         */
        return builder.routes()
                .route(p -> p
                        .path("/get")
                        .filters(f -> f.addRequestHeader("Hello", "World"))
                        .uri("http://httpbin.org:80"))
                .route(p -> p
                        .host("*.hystrix.com")
                        .filters(f -> f.hystrix(config -> config.setName("myHystrix")))
                        .uri("http://httpbin.org:80")).
                        build();
    }
}

上面示例的 RouteLocator 有两个路由,其中第二个路由添加了主机(host)判断。

本地启动应用,使用 Postman 本地访问:http://localhost:8080/delay/3,添加请求头:*KEY=Host,VALUE=www.hystrix.com* 。请求的路径 /delay/3 是 HTTPBin 的延迟 API,用于模拟请求超时。结果如下:

{
    "timestamp": "2020-03-13T06:22:04.412+0000",
    "path": "/delay/3",
    "status": 504,
    "error": "Gateway Timeout",
    "message": "Response took longer than configured timeout",
    "requestId": "058dde67-1",
    "trace": "org.springframework.cloud.gateway.support.TimeoutException\r\n\tSuppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: \nError has been observed at the following site(s):\n\t|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]\n\t|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]\n\t|_ checkpoint ⇢ HTTP GET \"/delay/3\" [ExceptionHandlingWebHandler]\nStack trace:\r\n"
}

请求超时,报的 504 的状态码,error 内容是 Gateway Timeout。此示例会直接把异常返回给客户端。

实现熔断回调

给上面请求超时的示例添加熔断回调地址,修改 RouteLocator 配置。如下:

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {

        /**
         * 给熔断器增加熔断回调URI
         */
        return builder.routes()
                .route(p -> p
                        .path("/get")
                        .filters(f -> f.addRequestHeader("Hello", "World"))
                        .uri("http://httpbin.org:80"))
                .route(p -> p
                        .host("*.hystrix.com")
                        .filters(f -> f.hystrix(config -> config
                                .setName("myHystrix")
                                .setFallbackUri("forward:/fallback")))
                        .uri("http://httpbin.org:80"))
                .build();
    }
}

上面给熔断设置了回调地址,转发到网关的 /fallback 路径。在网关增加 /fallback 接口端点。如下:

@RestController
public class FallbackController {

    @RequestMapping("/fallback")
    public Mono<String> fallback() {
        return Mono.just("fallback");
    }
}

使用 Postman 再次发送请求,注意添加 Host 头,响应结果如下:

# response body
fallback

# response header
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/plain;charset=UTF-8

从响应结果可以看到,当出现异常触发熔断后,会转发调用回退接口,而不是把异常信息直接给到客户端。当然内部是可以获取到后端服务的异常信息的,通常记录日志并发出告警。

注意事项

上面的示例中,对请求判断的表达式,路由的目标地址(uri)都是硬编码,而在实际开发应设置在配置文件中,或存储在外部数据库中。

在属性文件中设置,创建一个实体类,属性名对应,将配置文件中的属性值注入到实体类的属性中,如下:

@Configuration
@ConfigurationProperties(prefix = "spring.cloud.gateway.dest.uri")
public class UriConfiguration {

    private String httpbin;

    public String getHttpbin() {
        return httpbin;
    }

    public void setHttpbin(String httpbin) {
        this.httpbin = httpbin;
    }
}

application.properties

spring.cloud.gateway.dest.uri.httpbin=http://httpbin.org:80

在使用时,就可以注入属性类的 Bean,直接拿来使用:

@Configuration
public class GatewayRouteConfig {

    // 注入属性配置 Bean
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder, UriConfiguration uriConfiguration) {
        String httpUri = uriConfiguration.getHttpbin();
        return builder.routes()
                .route(p -> p
                        .path("/get")
                        .filters(f -> f.addRequestHeader("Hello", "World"))
                        .uri(httpUri))
                .route(p -> p
                        .host("*.hystrix.com")
                        .filters(f -> f
                                .hystrix(config -> config
                                        .setName("mycmd")
                                        .setFallbackUri("forward:/fallback")))
                        .uri(httpUri))
                .build();
    }
}

也可以将路由相关信息存储到外部数据库中,应用启动优先去数据库查找路由信息数据,再注册成 Bean。

相关参考

  1. Building a Gateway
  2. 第二代网关 Gateway 搭建流程
更多内容请访问:IT源点

全部评论: 0

    我有话说: