Spring?Cloud?Gateway編碼實(shí)現(xiàn)任意地址跳轉(zhuǎn)
本篇概覽 作為《Spring Cloud Gateway實(shí)戰(zhàn)》系列的第十四篇,本文會繼續(xù)發(fā)掘Spring Cloud Gateway的潛力,通過編碼體驗(yàn)操控網(wǎng)關(guān)的樂趣,開發(fā)出一個實(shí)用的功能:讓Spring Cloud Gateway應(yīng)用在收到請求后,可以按照業(yè)務(wù)的需要跳轉(zhuǎn)到任意的地址去
一般路由規(guī)則
先來看一個普通的路由規(guī)則,如下所示,意思是將所有/hello/**的請求轉(zhuǎn)發(fā)到http://127.0.0.1:8082這個地址去:
spring: application: name: hello-gateway cloud: gateway: routes: - id: path_route uri: http://127.0.0.1:8082 predicates: - Path=/hello/**
上述規(guī)則的功能如下圖所示,假設(shè)這就是生產(chǎn)環(huán)境的樣子,192.168.50.99:8082是提供服務(wù)的后臺應(yīng)用:
特殊規(guī)則
以上是常規(guī)情況,但也有些特殊情況,要求SpringCloud Gateway把瀏覽器的請求轉(zhuǎn)發(fā)到不同的服務(wù)上去
如下圖所示,在之前的環(huán)境中增加了另一個服務(wù)(即藍(lán)色塊),假設(shè)藍(lán)色服務(wù)代表測試環(huán)境
瀏覽器發(fā)起的/hello/str請求中,如果header中帶有tag-test-user,并且值等于true,此時要求SpringCloud Gateway把這個請求轉(zhuǎn)發(fā)到測試環(huán)境
如果瀏覽器的請求header中沒有tag-test-user,SpringCloud Gateway需要像以前那樣繼續(xù)轉(zhuǎn)發(fā)到192.168.50.99:8082
很明顯,上述需求難以通過配置來實(shí)現(xiàn),因?yàn)檗D(zhuǎn)發(fā)的地址和轉(zhuǎn)發(fā)邏輯都是圍繞業(yè)務(wù)邏輯來定制的,這也就是本篇的目標(biāo):對同一個請求path,可以通過編碼轉(zhuǎn)發(fā)到不同的地方去
實(shí)現(xiàn)上述功能的具體做法是:自定義過濾器
設(shè)計
編碼之前先設(shè)計,把關(guān)鍵點(diǎn)想清楚再動手
今天咱們要開發(fā)一個SpringCloud Gateway應(yīng)用,里面新增一個自定義過濾器
實(shí)現(xiàn)這個功能需要三個知識點(diǎn)作為基礎(chǔ),也就是說,您會通過本篇實(shí)戰(zhàn)掌握以下知識點(diǎn):
- 自定義過濾器
- 自定義過濾器的配置參數(shù)和bean的映射
- 編碼構(gòu)造Route實(shí)例
用思維導(dǎo)圖將具體工作內(nèi)容展開,如下圖所示,咱們就按部就班的實(shí)現(xiàn)吧:
源碼下載
本篇實(shí)戰(zhàn)中的完整源碼可在GitHub下載到,地址和鏈接信息如下表所示(https://github.com/zq2599/blog_demos):
名稱 | 鏈接 | 備注 |
---|---|---|
項目主頁 | https://github.com/zq2599/blog_demos | 該項目在GitHub上的主頁 |
git倉庫地址(https) | https://github.com/zq2599/blog_demos.git | 該項目源碼的倉庫地址,https協(xié)議 |
git倉庫地址(ssh) | git@github.com:zq2599/blog_demos.git | 該項目源碼的倉庫地址,ssh協(xié)議 |
這個git項目中有多個文件夾,本篇的源碼在spring-cloud-tutorials文件夾下,如下圖紅框所示:
- spring-cloud-tutorials內(nèi)部有多個子項目,本篇的源碼在gateway-dynamic-route文件夾下,如下圖紅框所示:
編碼
新建名為gateway-dynamic-route的maven工程,其pom.xml內(nèi)容如下:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-cloud-tutorials</artifactId> <groupId>com.bolingcavalry</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>gateway-dynamic-route</artifactId> <dependencies> <dependency> <groupId>com.bolingcavalry</groupId> <artifactId>common</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies> <build> <plugins> <!-- 如果父工程不是springboot,就要用以下方式使用插件,才能生成正常的jar --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.bolingcavalry.gateway.GatewayDynamicRouteApplication</mainClass> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
啟動類是普通的SpringBoot啟動類:
package com.bolingcavalry.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class GatewayDynamicRouteApplication { public static void main(String[] args) { SpringApplication.run(GatewayDynamicRouteApplication.class,args); } }
接下來是本篇的核心,自定義過濾器類,代碼中已經(jīng)添加了詳細(xì)的注釋,有幾處要注意的地方稍后會提到:
package com.bolingcavalry.gateway.filter; import lombok.Data; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.cloud.gateway.route.Route; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; @Component @Slf4j public class BizLogicRouteGatewayFilterFactory extends AbstractGatewayFilterFactory<BizLogicRouteGatewayFilterFactory.BizLogicRouteConfig> { private static final String TAG_TEST_USER = "tag-test-user"; public BizLogicRouteGatewayFilterFactory() { super(BizLogicRouteConfig.class); } @Override public GatewayFilter apply(BizLogicRouteConfig config) { return (exchange, chain) -> { // 本次的請求對象 ServerHttpRequest request = exchange.getRequest(); // 調(diào)用方請求時的path String rawPath = request.getURI().getRawPath(); log.info("rawPath [{}]", rawPath); // 請求頭 HttpHeaders headers = request.getHeaders(); // 請求方法 HttpMethod httpMethod = request.getMethod(); // 請求參數(shù) MultiValueMap<String, String> queryParams = request.getQueryParams(); // 這就是定制的業(yè)務(wù)邏輯,isTestUser等于ture代表當(dāng)前請求來自測試用戶,需要被轉(zhuǎn)發(fā)到測試環(huán)境 boolean isTestUser = false; // 如果header中有tag-test-user這個key,并且值等于true(不區(qū)分大小寫), // 就認(rèn)為當(dāng)前請求是測試用戶發(fā)來的 if (headers.containsKey(TAG_TEST_USER)) { String tageTestUser = headers.get(TAG_TEST_USER).get(0); if ("true".equalsIgnoreCase(tageTestUser)) { isTestUser = true; } } URI uri; if (isTestUser) { log.info("這是測試用戶的請求"); // 從配置文件中得到測試環(huán)境的uri uri = UriComponentsBuilder.fromHttpUrl(config.getTestEnvUri() + rawPath).queryParams(queryParams).build().toUri(); } else { log.info("這是普通用戶的請求"); // 從配置文件中得到正式環(huán)境的uri uri = UriComponentsBuilder.fromHttpUrl(config.getProdEnvUri() + rawPath).queryParams(queryParams).build().toUri(); } // 生成新的Request對象,該對象放棄了常規(guī)路由配置中的spring.cloud.gateway.routes.uri字段 ServerHttpRequest serverHttpRequest = request.mutate().uri(uri).method(httpMethod).headers(httpHeaders -> httpHeaders = httpHeaders).build(); // 取出當(dāng)前的route對象 Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); //從新設(shè)置Route地址 Route newRoute = Route.async().asyncPredicate(route.getPredicate()).filters(route.getFilters()).id(route.getId()) .order(route.getOrder()).uri(uri).build(); // 放回exchange中 exchange.getAttributes().put(GATEWAY_ROUTE_ATTR,newRoute); // 鏈?zhǔn)教幚?,交給下一個過濾器 return chain.filter(exchange.mutate().request(serverHttpRequest).build()); }; } /** * 這是過濾器的配置類,配置信息會保存在此處 */ @Data @ToString public static class BizLogicRouteConfig { // 生產(chǎn)環(huán)境的服務(wù)地址 private String prodEnvUri; // 測試環(huán)境的服務(wù)地址 private String testEnvUri; } }
上述代碼中要注意的地方如下: BizLogicRouteConfig是過濾器的配置類,可以在使用過濾器時在配置文件中配置prodEnvUri和testEnvUri的值,在代碼中可以通過這兩個字段取得配置值
過濾器的工廠類名為BizLogicRouteGatewayFilterFactory,按照規(guī)則,過濾器的名字是BizLogicRoute 在apply方法中,重新創(chuàng)建ServerHttpRequest和Route對象,它們的參數(shù)可以按照業(yè)務(wù)需求隨意設(shè)置,然后再將這兩個對象設(shè)置給SpringCloud gateway的處理鏈中,接下來,處理鏈上的其他過濾拿到的就是新的ServerHttpRequest和Route對象了
配置
假設(shè)生產(chǎn)環(huán)境地址是http://127.0.0.1:8082,測試環(huán)境地址是http://127.0.0.1:8087,整個SpringCloud Gateway應(yīng)用的配置文件如下,可見使用了剛剛創(chuàng)建的過濾器,并且為此過濾器配置了兩個參數(shù):
server: #服務(wù)端口 port: 8086 spring: application: name: gateway-dynamic-route cloud: gateway: routes: - id: path_route uri: http://0.0.0.0:8082 predicates: - Path=/hello/** filters: - name: BizLogicRoute args: prodEnvUri: http://127.0.0.1:8082 testEnvUri: http://127.0.0.1:8087
至此,編碼完成了,啟動這個服務(wù)
開發(fā)和啟動后臺服務(wù),模擬生產(chǎn)和測試環(huán)境
接下來開始驗(yàn)證功能是否生效,咱們要準(zhǔn)備兩個后臺服務(wù):
模擬生產(chǎn)環(huán)境的后臺服務(wù)是provider-hello,監(jiān)聽端口是8082,其/hello/str接口的返回值是Hello World, 2021-12-12 10:53:09
模擬測試環(huán)境的后臺服務(wù)是provider-for-test-user,監(jiān)聽端口是8087,其/hello/str接口的返回值是Hello World, 2021-12-12 10:57:11 (from test enviroment)(和生產(chǎn)環(huán)境相比,返回內(nèi)容多了個(from test enviroment)),對應(yīng)Controller參考如下:
package com.bolingcavalry.provider.controller; import com.bolingcavalry.common.Constants; import org.springframework.web.bind.annotation.*; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; @RestController @RequestMapping("/hello") public class Hello { private String dateStr(){ return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()); } /** * 返回字符串類型 * @return */ @GetMapping("/str") public String helloStr() { return Constants.HELLO_PREFIX + ", " + dateStr() + " (from test enviroment)"; } }
以上兩個服務(wù),對應(yīng)的代碼都在我的Github倉庫中,如下圖紅框所示:
啟動gateway-dynamic-route、provider-hello、provider-for-test-user服務(wù)
此時,SpringCloud gateway應(yīng)用和兩個后臺服務(wù)都啟動完成,情況如下圖,接下來驗(yàn)證剛才開發(fā)的過濾器能不能像預(yù)期那樣轉(zhuǎn)發(fā):
驗(yàn)證
用postman工具向gateway-dynamic-route應(yīng)用發(fā)起一次請求,返回值如下圖紅框所示,證明這是provider-hello的響應(yīng),看來咱們的請求已經(jīng)正常到達(dá):
再發(fā)送一次請求,如下圖,這次在header中加入鍵值對,得到的結(jié)果是provider-for-test-user的響應(yīng)
至此,過濾器的開發(fā)和驗(yàn)證已經(jīng)完成,通過編碼,可以把外部請求轉(zhuǎn)發(fā)到任何咱們需要的地址去,并且支持參數(shù)配置,這個過濾器還有一定的可配置下,減少了硬編碼的比率,如果您正在琢磨如何深度操控SpringCloud Gateway,希望本文能給您一些參考!
到此這篇關(guān)于Spring Cloud Gateway編碼實(shí)現(xiàn)任意地址跳轉(zhuǎn)的文章就介紹到這了,更多相關(guān)Spring Cloud Gateway實(shí)現(xiàn)任意地址跳轉(zhuǎn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解Spring Cloud Gateway 限流操作
- spring cloud gateway使用 uri: lb://方式配置時,服務(wù)名的特殊要求
- 基于Nacos實(shí)現(xiàn)Spring Cloud Gateway實(shí)現(xiàn)動態(tài)路由的方法
- 解決spring cloud gateway 獲取body內(nèi)容并修改的問題
- springcloud gateway如何實(shí)現(xiàn)路由和負(fù)載均衡
- springcloud gateway聚合swagger2的方法示例
- 詳解SpringCloud Gateway之過濾器GatewayFilter
- Spring Cloud Gateway全局異常處理的方法詳解
- spring cloud gateway整合sentinel實(shí)現(xiàn)網(wǎng)關(guān)限流
相關(guān)文章
JAVA中通過自定義注解進(jìn)行數(shù)據(jù)驗(yàn)證的方法
java 自定義注解驗(yàn)證可自己添加所需要的注解,下面這篇文章主要給大家介紹了關(guān)于JAVA中通過自定義注解進(jìn)行數(shù)據(jù)驗(yàn)證的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08spring security集成cas實(shí)現(xiàn)單點(diǎn)登錄過程
這篇文章主要介紹了spring security集成cas實(shí)現(xiàn)單點(diǎn)登錄過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02SpringBoot @JsonDeserialize自定義Json序列化方式
這篇文章主要介紹了SpringBoot @JsonDeserialize自定義Json序列化方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10