即将开发的新平台在网关组件上选择了 Gateway,Spring 官方主推 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 工具中查看这些自动配置类的源码,了解其中的依赖配置。
源码分析
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
。GatewayLoadBalancerClientAutoConfiguration 和 GatewayClassPathWarningAutoConfiguration 是两个前置自动配置,分别负责 负载均衡 和 类检测并告警。
HttpHandlerAutoConfiguration 和 WebFluxAutoConfiguration 是两个后置自动配置类,主要作用是 HTTP 前置处理。
GatewayClassPathWarningAutoConfiguration
Spring Cloud Gateway 并不与 Spring MVC 兼容,不需要添加 spring-boot-starter-web 依赖。
GatewayClassPathWarningAutoConfiguration 负载检查来自 Spring MVC 的 DispatcherServlet 和 DispatcherHandler,如果存在, 就打印不兼容的 warn 信息,提示移除 spring-boot-starter-web 依赖。
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) { //......省略 } }
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 加密请求认证相关设置。
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。
相关参考
注意:本文归作者所有,未经作者允许,不得转载