簡介
本文主要是用來學(xué)習(xí)Spring Cloud gateway網(wǎng)關(guān)使用的;API網(wǎng)關(guān)作為后端服務(wù)的統(tǒng)一入口,可提供請求路由、協(xié)議轉(zhuǎn)換、安全認(rèn)證、服務(wù)鑒權(quán)、流量控制、日志監(jiān)控等服務(wù)。
概覽
gateway網(wǎng)關(guān)處理流程圖如下:

- 請求發(fā)送到網(wǎng)關(guān),DispatcherHandler是Http請求的中央分發(fā)器,將請求匹配到相應(yīng)的HandlerMapping;
- 請求和處理器之間有一個映射關(guān)系,網(wǎng)關(guān)將會對請求進(jìn)行路由,handler會匹配RoutePredicateHandlerMapping,以匹配到對應(yīng)的Route;
- 經(jīng)過RoutePredicateHandlerMapping處理后,請求會發(fā)送到Web處理器,該WebHandler代理了一系列網(wǎng)關(guān)過濾器和全局過濾,此時會對請求或者響應(yīng)頭進(jìn)行處理;
- 最后轉(zhuǎn)發(fā)到具體的代理服務(wù);
DispatcherHandler--->RoutePredicateHandlerMapping
RoutePredicateHandlerMapping---->FilteringWebHandler
FilteringWebHandler----->DefaultGatewayFilterChain
Route路由
什么是路由?一個路由就代表一個真實的下游請求路徑,路由在gateway里面有兩個重要的輔助概念-路由定義和定位器;
- 路由定義RouteDefinition:路由定義是Gateway Properties中的屬性,可以定義獲取路由的方式;
- 定位器Locator:定位器分為路由定位器RouteLocatorRouteDefinitionLocator,
RouteLocator用來獲取routes,而RouteDefinitionLocator則用來獲RouteDefinitions;
RouteDefinition路由定義
路由定義有五個基本屬性:
- id: 路由id,默認(rèn)為uuid;
- predicates: 路由斷言定義列表;
- fliters:該路由需要經(jīng)過的過濾器;
- URI:該路由請求的路徑;
- order:優(yōu)先級 ;
定位器
RouteLocator
用來獲取路由的定位器,其源碼如下:
public interface RouteLocator {
Flux<Route> getRoutes();
}
其內(nèi)置了3個路由定位器實現(xiàn)類:
- CachingRouteLocator:具備緩存功能的路由定位器,其內(nèi)具有監(jiān)聽RefreshRoutesEvent事件,并能及時刷新緩存;
@EventListener(RefreshRoutesEvent.class)
/* for testing */ void handleRefresh() {
refresh();
}
- CompositeRouteLocator:組合路由定位器,其內(nèi)有代理了一系列的定位器,來實現(xiàn)路由查詢;
- RouteDefinitionRouteLocator:基于路由定義的路由定位器,根據(jù)路由定義定位器獲取路由定義并將其轉(zhuǎn)換成對應(yīng)的路由;代碼如下:
public Flux<Route> getRoutes() {
return this.routeDefinitionLocator.getRouteDefinitions()
.map(this::convertToRoute)
//TODO: error handling
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition matched: " + route.getId());
}
return route;
});
}
也可以自定義一個路由定位器,如下代碼:
@SpringBootApplication
public class DemogatewayApplication {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/get")
.uri("http://httpbin.org"))//將所有/get開頭的請求路徑轉(zhuǎn)發(fā)到httpbin.org
.route("host_route", r -> r.host("*.myhost.org")
.uri("http://httpbin.org"))//將域名以myhost.org結(jié)尾的請求轉(zhuǎn)發(fā)
.route("rewrite_route", r -> r.host("*.rewrite.org")
.filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
.uri("http://httpbin.org"))//將域名以myhost.org結(jié)尾的請求轉(zhuǎn)發(fā)并進(jìn)行路徑重寫,保留/foo/后面路徑;
.route("hystrix_route", r -> r.host("*.hystrix.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
.uri("http://httpbin.org"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.route("limit_route", r -> r
.host("*.limited.org").and().path("/anything/**")
.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
.uri("http://httpbin.org"))
.build();
}
}
路由定義定位器
路由定義定位器用來獲取路由定義的,路由定義可以在配置文件中進(jìn)行設(shè)置,gateway內(nèi)置了三個路由定義定位器:
- CompositeRouteDefinitionLocator:其內(nèi)代理了一系列RouteDefinitionLocators,通過其來獲取路由定義集合;
- RouteDefinitionRepository & InMemoryRouteDefinitionRepository:該定位器實現(xiàn)了路由定義的增刪改查等操作,gateway內(nèi)置端點接口會用到這些操作,同時也可以通過該路由定義定位器可以實現(xiàn)路由倉庫,來動態(tài)更新路由定義信息;
- CachingRouteDefinitionLocator:帶有緩存功能路由定義定位器;
- DiscoveryClientRouteDefinitionLocator:服務(wù)發(fā)現(xiàn)路由定義定位器,通過服務(wù)發(fā)現(xiàn)機(jī)制以及Spel表達(dá)式來動態(tài)更新路由定義信息;獲取路由源碼如下:
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
//獲取Spel表達(dá)式
SpelExpressionParser parser = new SpelExpressionParser();
Expression includeExpr = parser.parseExpression(properties.getIncludeExpression());
Expression urlExpr = parser.parseExpression(properties.getUrlExpression());
Predicate<ServiceInstance> includePredicate;
if (properties.getIncludeExpression() == null || "true".equalsIgnoreCase(properties.getIncludeExpression())) {
includePredicate = instance -> true;
} else {
includePredicate = instance -> {
Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);
if (include == null) {
return false;
}
return include;
};
}
//獲取service clients以及其instances,通過serviceId替換的方式獲取路由定義
return Flux.fromIterable(discoveryClient.getServices())
.map(discoveryClient::getInstances)
.filter(instances -> !instances.isEmpty())
.map(instances -> instances.get(0))
.filter(includePredicate)
.map(instance -> {
String serviceId = instance.getServiceId();
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
String uri = urlExpr.getValue(evalCtxt, instance, String.class);
routeDefinition.setUri(URI.create(uri));
final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties);
for (PredicateDefinition original : this.properties.getPredicates()) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
predicate.addArg(entry.getKey(), value);
}
routeDefinition.getPredicates().add(predicate);
}
for (FilterDefinition original : this.properties.getFilters()) {
FilterDefinition filter = new FilterDefinition();
filter.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
filter.addArg(entry.getKey(), value);
}
routeDefinition.getFilters().add(filter);
}
return routeDefinition;
});
}
- PropertiesRouteDefinitionLocator: 通過配置文件來獲取路由定義集合,其源碼如下:
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(this.properties.getRoutes());
}
Predicate斷言
Predicate通過Predicate函數(shù)式接口來判斷當(dāng)前請求是否滿足選擇條件,Predicate分為以下條件劃分為不同的路由斷言工廠類
Datetime類型
根據(jù)日期判斷是否滿足路由選擇條件,其有三個工廠類:
- AfteRoutePredicateFactory: 接收一個日期參數(shù),判斷請求日期是否晚于指定日志;
- BeforeRoutePredicateFactory:接收一個日期參數(shù),判斷請求日期是否早于指定日期;
- BetweenRoutePredicateFactory:接收兩個日期參數(shù),判斷請求日期是否位于之間;
其配置如下:
spring:
cloud:
gateway:
routes:
- id: after_route_id
uri: http://www.baidu.com
predicates:
- After= 2018-12-30T23:59:59.789+08:00[Asia/Shanghai]
Cookie類型
根據(jù)請求的Cookie正則匹配是否通過選擇條件;
唯一工廠類:CookieRoutePredicateFactory;
其配置實例如下:
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
Header頭斷言類型
請求頭屬性正則匹配是否通過選擇,唯一工廠類:HeaderRoutePredicateFactory,其配置如下:
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
Host域名斷言類型
Host域名正則匹配,是否通過選擇,唯一工廠類:HostRoutePredicateFactory,
其配置如下:
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
Method斷言類型
基于方法類型來進(jìn)行相應(yīng)匹配選擇,唯一工廠類MethodRoutePredicateFactory;
其配置如下:
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET
Path斷言類型
Path斷言類型會通過接受的兩個參數(shù)來判斷是否匹配選擇,唯一工廠類:PathRoutePredicateFactory,其配置如下:
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Path=/foo/{segment},/bar/{segment}
Query查詢類型
Query類型通過接受兩個參數(shù):一個要求的參數(shù)和一個可選的regexp表達(dá)式,來匹配是否選擇,其工廠類為QueryRoutePredicateFactory,配置示例如下:
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=baz,ba.
路由必須滿足有一個baz的查詢參數(shù),且該參數(shù)值必須滿足ba.表達(dá)式,方可通過請求;
RemoteAddr類型
該類型通過設(shè)置一系列的IPv4或者IPv6地址以及子網(wǎng)掩碼形式的字符串,來進(jìn)行匹配,其唯一工廠類為:RemoteAddrRoutePredicateFactory;
其配置如下:
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
路由請求必須滿足192.168.1.1/24指定的ip域內(nèi)方可通過選擇;
Weight類型
Weight類型通過設(shè)置權(quán)重,來進(jìn)行路由選擇,其配置如下:
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
上例說明路由百分之80將發(fā)向https://weighthigh.org,百分20發(fā)向https://weightlow.org
Filter過濾器
配置文件
fliter需要在配置文件bootstrap.yml中進(jìn)行相應(yīng)設(shè)置,或者手動定義一個實現(xiàn)了Filter;
配置文件設(shè)置filter屬性如下:
gateway:
discovery:
locator:
enabled: true
predicates:
- Path='/api/**/'+serviceId+'/**'
filters:
- name: RewritePath
args:
regexp: "'/api/.*/' + serviceId + '/(?<remaining>.*)'"
replacement: "'/${remaining}'"
通過設(shè)置name便可調(diào)用相應(yīng)的過濾器工廠類創(chuàng)建一個過濾器,上例中,前綴RewritePath便會通過RewritePathGatewayFilterFactory創(chuàng)建一個GatewayFilter對象,來進(jìn)行相應(yīng)過濾操作;
GlobalFilter
gateway網(wǎng)關(guān)Filter分為以下Global和Define,其中Gobal有以下幾種(按ordered從小到大):
AdaptCachedBodyGlobalFilter(Integer.Min_VALU):通過適配緩存requestbody參數(shù)來實現(xiàn)request的重新構(gòu)建;
NettyWriteResponseFilter(-1):將當(dāng)前請求的響應(yīng)進(jìn)行輸出到服務(wù)調(diào)用方;
ForwardPathFilter(0):解析路徑并將路徑轉(zhuǎn)發(fā)
GatewayMetricsFilter(0):網(wǎng)關(guān)性能測量器,可以用來收集網(wǎng)關(guān)請求參數(shù),方便創(chuàng)建一個Grafana Dashbord;
RouteToRequestUrlFilter(10000):轉(zhuǎn)換路由中的URI
LoadBalancerClientFilter(10100):通過負(fù)載均衡客戶端根據(jù)路由的URL解析轉(zhuǎn)換成真實的請求URL
WebsocketRoutingFilter(Integer.MAX_VALUE-1):負(fù)責(zé)處理Websocket類型請求響應(yīng)信息;
ForwardRoutingFilter(Integer.MAX_VALUE):其根據(jù) forward:// 前綴( Scheme )過濾處理,將請求轉(zhuǎn)發(fā)到當(dāng)前網(wǎng)關(guān)實例本地接口。
NettyRoutingFilter(Integer.MAX_VALUE)):通過HttpClient客戶端轉(zhuǎn)發(fā)真實的URL請求到服務(wù)提供方,并將響應(yīng)寫入到當(dāng)前的請求響應(yīng)中;