SpringCloud下基于GateWay的服務(wù)網(wǎng)關(guān)實(shí)踐
Spring Cloud GateWay 作為分布式架構(gòu)下常見(jiàn)的服務(wù)網(wǎng)關(guān),為內(nèi)部各服務(wù)對(duì)外提供統(tǒng)一的API入口。同時(shí)還為對(duì)外提供服務(wù)的API提供統(tǒng)一的安全、鑒權(quán)、監(jiān)控等功能

基本實(shí)踐
目標(biāo)服務(wù)
為了便于演示網(wǎng)關(guān)服務(wù)的作用,這里我們先提供一個(gè)目標(biāo)服務(wù)——payment。其服務(wù)接口的Controller實(shí)現(xiàn)如下所示
@RestController
@RequestMapping("pay3")
public?class?PaymentController3?{
????@Value("${server.port}")
????private?String?serverPort;
????@GetMapping("/test1")
????public?String?test1()?{
????????String?uuid?=?UUID.randomUUID().toString();
????????String?msg?=?"[Payment?Service?-?test1],?port:"?+?serverPort
????????????+?",?uuid:?"?+?uuid;
????????return?msg;
????}
????@GetMapping("/test2")
????public?String?test2(@RequestParam?String?name)?{
????????String?msg?=?"[Payment?Service?-?test2],?port:"?+?serverPort
????????????+?",?name:?"?+?name;
????????return?msg;
????}
????@GetMapping("/hello1")
????public?String?hello1()?{
????????String?msg?=?"[Payment?Service?-?hello1],?port:"?+?serverPort;
????????return?msg;
????}
????@GetMapping("/hello2")
????public?String?hello2(@RequestParam?Integer?num)?{
????????String?msg?=?"[Payment?Service?-?hello2],?port:"?+?serverPort
????????????+?",?num:?"?+?num;
????????return?msg;
????}
}
啟動(dòng)payment服務(wù)的兩個(gè)實(shí)例,分別允許在8004、8005端口。這里我們使用Consul作為注冊(cè)中心。如下所示

網(wǎng)關(guān)服務(wù)
現(xiàn)在我們建立一個(gè)ApiGateWay服務(wù),用于實(shí)踐我們的服務(wù)網(wǎng)關(guān)。首先在POM中引入 spring-cloud-starter-gateway 依賴
<dependencyManagement>
??<dependencies>
????
????<dependency>
??????<groupId>org.springframework.bootgroupId>
??????<artifactId>spring-boot-dependenciesartifactId>
??????<version>2.2.2.RELEASEversion>
??????<type>pomtype>
??????<scope>importscope>
????dependency>
????
????<dependency>
??????<groupId>org.springframework.cloudgroupId>
??????<artifactId>spring-cloud-dependenciesartifactId>
??????<version>Hoxton.SR1version>
??????<type>pomtype>
??????<scope>importscope>
????dependency>
??dependencies>
dependencyManagement>
<dependencies>
??
??<dependency>
????<groupId>org.springframework.cloudgroupId>
????<artifactId>spring-cloud-starter-gatewayartifactId>
??dependency>
dependencies>
其配置文件如下所示。可以看到該服務(wù)網(wǎng)關(guān)也會(huì)被注冊(cè)到Consul中。當(dāng)然在ApiGateWay的POM中也需要引入Consul依賴。具體地,網(wǎng)關(guān)相關(guān)的配置即是通過(guò)spring.cloud.gateway配置項(xiàng)進(jìn)行配置
server:
??port:?9527
spring:
??application:
????name:?ApiGateWay
??cloud:
????#?注冊(cè)中心Consul配置
????consul:
??????#?Consul?地址信息
??????host:?127.0.0.1
??????port:?8500
??????discovery:
????????#?服務(wù)名
????????service-name:?${spring.application.name}
????#?網(wǎng)關(guān)GateWay配置
????gateway:
??????discovery:
????????locator:
??????????#?實(shí)現(xiàn)通過(guò)注冊(cè)中心動(dòng)態(tài)創(chuàng)建基于服務(wù)名的路由
??????????enabled:?true
??????routes:
??????????#?路由的唯一標(biāo)識(shí)
????????-?id:?payment_test1_route
??????????#?目標(biāo)地址
??????????uri:?http://localhost:8004
??????????#?路由匹配的謂詞條件
??????????predicates:
????????????#?Path謂詞,?根據(jù)請(qǐng)求路徑進(jìn)行匹配
????????????-?Path=/pay3/test1
????????-?id:?payment_test2_route
??????????#?基于注冊(cè)中心的動(dòng)態(tài)路由,?格式:?lb協(xié)議(lb://)+服務(wù)名
??????????uri:?lb://payment
??????????predicates:
????????????-?Path=/pay3/test2
其中:
spring.cloud.gateway.routes.id 配置項(xiàng)用于配置路由的唯一標(biāo)識(shí) spring.cloud.gateway.routes.uri 配置項(xiàng)用于路由匹配后進(jìn)行轉(zhuǎn)發(fā)的目標(biāo)地址 spring.cloud.gateway.routes.predicates 配置項(xiàng)用于設(shè)置路由匹配所需的謂詞條件。具體地,Path謂詞用于根據(jù)路徑進(jìn)行路由匹配
具體對(duì)于payment_test1_route而言,其目標(biāo)uri是具體地允許在某端口的服務(wù)。此舉顯然無(wú)法充分體現(xiàn)payment集群服務(wù)的作用。故可通過(guò) spring.cloud.gateway.discovery.locator.enabled 配置項(xiàng)實(shí)現(xiàn)通過(guò)注冊(cè)中心動(dòng)態(tài)創(chuàng)建基于服務(wù)名的路由。這也是為什么需要在ApiGateWay服務(wù)添加Consul依賴。事實(shí)上,GateWay還支持通過(guò)Java配置類的方式進(jìn)行路由配置,如下所示
@Configuration
public?class?GateWayConfig?{
????@Bean
????public?RouteLocator?routeLocator1(RouteLocatorBuilder?routeLocatorBuilder)?{
????????return?routeLocatorBuilder.routes()
????????????.route("payment_hello1_route",?r?->?r.path("/pay3/hello1")
????????????????.uri("http://localhost:8004")?)
????????????.route("payment_hello2_route",?r?->?r.path("/pay3/hello2")
????????????????.uri("lb://payment")?)
????????????.build();
????}
}
至此ApiGateWay服務(wù)就已經(jīng)基本完成了,其啟動(dòng)類如下所示
@SpringBootApplication
@EnableDiscoveryClient?//?使用Consul作為注冊(cè)中心時(shí)使用
public?class?ApiGateWayApplication?{
????public?static?void?main(String[]?args)?{
????????SpringApplication.run(ApiGateWayApplication.class,?args);
????}
}
啟動(dòng)ApiGateWay服務(wù),測(cè)試結(jié)果如下,符合預(yù)期

Predicate謂詞
根據(jù)前文可知,Predicate謂詞即是GateWay進(jìn)行路由轉(zhuǎn)發(fā)時(shí)所需滿足的匹配條件。這里對(duì)GateWay中常見(jiàn)的謂詞進(jìn)行介紹
After、Before、Between
After、Before、Between謂詞要求請(qǐng)求時(shí)的時(shí)間分別位于所配置時(shí)間之后、之前、之間才滿足匹配要求。配置示例如下所示
#?Case?1
predicates:
??#?請(qǐng)求時(shí)間在指定時(shí)間之后才滿足條件
??-?After=2021-09-06T21:51:37.485+08:00[Asia/Shanghai]
#?Case?2
predicates:
??#?請(qǐng)求時(shí)間在指定時(shí)間之前才滿足條件
??-?Before=2021-09-06T21:51:37.485+08:00[Asia/Shanghai]
#?Case?3
predicates:
??#?請(qǐng)求時(shí)間在指定時(shí)間之間才滿足條件
??-?Between=2021-09-06T21:51:37.485+08:00[Asia/Shanghai],?2021-09-06T22:30:37.485+08:00[Asia/Shanghai]
其中該配置值需要帶時(shí)區(qū)信息,可通過(guò)下述代碼獲取
public?class?Test1?{
????@Test
????public?static?void?main(String[]?args)?{
????????ZonedDateTime?zonedDateTime?=?ZonedDateTime.now();
????????System.out.println(zonedDateTime);
????}
}
測(cè)試結(jié)果如下所示

Cookie
Cookie謂詞要求請(qǐng)求攜帶相應(yīng)的cookie信息,其還支持正則表達(dá)式。配置示例如下所示
#?Case?1
predicates:
??#?請(qǐng)求需攜帶cookie信息,key為id,value為2345
??-?Cookie=id,?2345
#?Case?2
predicates:
??#?請(qǐng)求需攜帶cookie信息,key為id,value為一個(gè)或多個(gè)數(shù)字
??-?Cookie=id,?\d+
這里使用Case 2的謂詞進(jìn)行驗(yàn)證,效果如下,符合預(yù)期

Header
Header謂詞要求請(qǐng)求攜帶相應(yīng)的請(qǐng)求頭,其還支持正則表達(dá)式。配置示例如下所示
#?Case?1
predicates:
??#?請(qǐng)求需攜帶請(qǐng)求頭屬性,屬性名為X-Request-Id,value為9978
??-?Header=X-Request-Id,?9978
#?Case?2
predicates:
??#?請(qǐng)求需攜帶請(qǐng)求頭屬性,屬性名為X-Request-Id,value為一個(gè)或多個(gè)數(shù)字
??-?Header=X-Request-Id,?\d+
這里使用Case 2的謂詞進(jìn)行驗(yàn)證,效果如下,符合預(yù)期

Method
Method謂詞要求請(qǐng)求的方法類型滿足指定類型,其支持多個(gè)值。配置示例如下所示
#?Case?1
predicates:
??#?請(qǐng)求方法類型需要為?POST?或?GET
??-?Method=POST,?GET
Filter過(guò)濾器
Filter過(guò)濾器可以實(shí)現(xiàn)對(duì)于HTTP請(qǐng)求、響應(yīng)的修改。根據(jù)過(guò)濾器的執(zhí)行時(shí)機(jī)可分為兩類:pre、post,其分別會(huì)在請(qǐng)求被執(zhí)行前和被執(zhí)行后進(jìn)行調(diào)用。而根據(jù)類型可分為兩類:GateWayFilter、GlobalFilter。前者作用于某個(gè)具體的路由下;后者則會(huì)有條件地作用于全部路由
GateWayFilter
對(duì)于GateWayFilter而言,其使用方式與Predicate謂詞類似,直接在配置文件通過(guò)filters進(jìn)行配置即可。這里以GateWayFilter中的AddRequestParameter過(guò)濾器為例進(jìn)行說(shuō)明,其會(huì)添加參數(shù)到請(qǐng)求上。我們對(duì)payment_test2_route路由添加該過(guò)濾器,配置如下所示
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?payment_test2_route
??????????uri:?lb://payment
??????????predicates:
????????????-?Path=/pay3/test2
??????????filters:
????????????#?添加name參數(shù)的值為T(mén)ony
????????????-?AddRequestParameter=name,?Tony
測(cè)試結(jié)果如下所示,符合預(yù)期

GlobalFilter
而對(duì)于GlobalFilter而言,日常更多的是自定義全局過(guò)濾器,以滿足一些個(gè)性化的需求。這里以鑒權(quán)為例通過(guò)實(shí)現(xiàn)GlobalFilter、Ordered接口來(lái)自定義一個(gè)全局過(guò)濾器,實(shí)現(xiàn)如下所示
public?class?CustomGlobalFilter?implements?GlobalFilter,?Ordered?{
????@Override
????public?Mono?filter(ServerWebExchange?exchange,?GatewayFilterChain?chain)? {
????????//?獲取請(qǐng)求頭Token
????????String?token?=?exchange.getRequest().getHeaders().getFirst("token");
????????//?Token為空,?鑒權(quán)失敗
????????if(?StringUtils.isBlank(token)?)?{
????????????exchange.getResponse().setStatusCode(?HttpStatus.FORBIDDEN?);
????????????return?exchange.getResponse().setComplete();
????????}
????????//?Token校驗(yàn)通過(guò),?繼續(xù)傳遞請(qǐng)求
????????return?chain.filter(exchange);
????}
????/**
?????*?值越小,?優(yōu)先級(jí)越高
?????*?@return
?????*/
????@Override
????public?int?getOrder()?{
????????return?0;
????}
}
測(cè)試結(jié)果如下所示,符合預(yù)期

