Spring Cloud 系列之 Gateway 服務閘道器(三)
本篇文章為系列文章,未讀第一集的同學請猛戳這裡:
- Spring Cloud 系列之 Gateway 服務閘道器(一)
- Spring Cloud 系列之 Gateway 服務閘道器(二)
本篇文章講解 Gateway 閘道器過濾器和全域性過濾器以及自定義過濾器。
過濾器
Spring Cloud Gateway 根據作用範圍劃分為 GatewayFilter
和 GlobalFilter
,二者區別如下:
GatewayFilter
:閘道器過濾器,需要通過spring.cloud.routes.filters
配置在具體路由下,只作用在當前路由上或通過spring.cloud.default-filters
GlobalFilter
:全域性過濾器,不需要在配置檔案中配置,作用在所有的路由上,最終通過GatewayFilterAdapter
包裝成GatewayFilterChain
可識別的過濾器,它為請求業務以及路由的 URI 轉換為真實業務服務請求地址的核心過濾器,不需要配置系統初始化時載入,並作用在每個路由上。
閘道器過濾器 GatewayFilter
點選連結觀看:閘道器過濾器視訊(獲取更多請關注公眾號「哈嘍沃德先生」)
閘道器過濾器用於攔截並鏈式處理 Web 請求,可以實現橫切與應用無關的需求,比如:安全、訪問超時的設定等。修改傳入的 HTTP 請求或傳出 HTTP 響應。Spring Cloud Gateway 包含許多內建的閘道器過濾器工廠一共有 22 個,包括頭部過濾器、 路徑類過濾器、Hystrix 過濾器和重寫請求 URL 的過濾器, 還有引數和狀態碼等其他型別的過濾器。根據過濾器工廠的用途來劃分,可以分為以下幾種:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter 和 Hystrix。
接下來我們舉例說明其中一部分如何使用,其餘等大家工作中需要應用時再查詢資料學習或者諮詢我也可以。
Path 路徑過濾器
Path 路徑過濾器可以實現 URL 重寫,通過重寫 URL 可以實現隱藏實際路徑提高安全性,易於使用者記憶和鍵入,易於被搜尋引擎收錄等優點。實現方式如下:
RewritePathGatewayFilterFactory
RewritePath 閘道器過濾器工廠採用路徑正則表示式引數和替換引數,使用 Java 正則表示式來靈活地重寫請求路徑。
spring: application: name: gateway-server # 應用名稱 cloud: gateway: # 路由規則 routes: - id: product-service # 路由 ID,唯一 uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址 predicates: # 斷言(判斷條件) # 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後 - Path=/product/**, /api-gateway/** filters: # 閘道器過濾器 # 將 /api-gateway/product/1 重寫為 /product/1 - RewritePath=/api-gateway(?<segment>/?.*), $\{segment}
訪問:http://localhost:9000/api-gateway/product/1 結果如下:
PrefixPathGatewayFilterFactory
PrefixPath 閘道器過濾器工廠為匹配的 URI 新增指定字首。
spring:
application:
name: gateway-server # 應用名稱
cloud:
gateway:
# 路由規則
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址
predicates: # 斷言(判斷條件)
# 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
- Path=/**
filters: # 閘道器過濾器
# 將 /1 重寫為 /product/1
- PrefixPath=/product
訪問:http://localhost:9000/1 結果如下:
StripPrefixGatewayFilterFactory
StripPrefix 閘道器過濾器工廠採用一個引數 StripPrefix,該引數表示在將請求傳送到下游之前從請求中剝離的路徑個數。
spring:
application:
name: gateway-server # 應用名稱
cloud:
gateway:
# 路由規則
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址
predicates: # 斷言(判斷條件)
# 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
- Path=/**
filters: # 閘道器過濾器
# 將 /api/123/product/1 重寫為 /product/1
- StripPrefix=2
訪問:http://localhost:9000/api/123/product/1 結果如下:
SetPathGatewayFilterFactory
SetPath 閘道器過濾器工廠採用路徑模板引數。 它提供了一種通過允許模板化路徑段來操作請求路徑的簡單方法,使用了 Spring Framework 中的 uri 模板,允許多個匹配段。
spring:
application:
name: gateway-server # 應用名稱
cloud:
gateway:
# 路由規則
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址
predicates: # 斷言(判斷條件)
# 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
- Path=/api/product/{segment}
filters: # 閘道器過濾器
# 將 /api/product/1 重寫為 /product/1
- SetPath=/product/{segment}
訪問:http://localhost:9000/api/product/1 結果如下:
Parameter 引數過濾器
AddRequestParameter 閘道器過濾器工廠會將指定引數新增至匹配到的下游請求中。
spring:
application:
name: gateway-server # 應用名稱
cloud:
gateway:
# 路由規則
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址
predicates: # 斷言(判斷條件)
# 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
- Path=/api-gateway/**
filters: # 閘道器過濾器
# 將 /api-gateway/product/1 重寫為 /product/1
- RewritePath=/api-gateway(?<segment>/?.*), $\{segment}
# 在下游請求中新增 flag=1
- AddRequestParameter=flag, 1
修改商品服務的控制層程式碼。
package com.example.controller;
import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
/**
* 根據主鍵查詢商品
*
* @param id
* @return
*/
@GetMapping("/{id}")
public Product selectProductById(@PathVariable("id") Integer id, String flag) {
System.out.println("flag = " + flag);
return productService.selectProductById(id);
}
}
訪問:http://localhost:9000/api-gateway/product/1 控制檯結果如下:
flag = 1
Status 狀態過濾器
SetStatus 閘道器過濾器工廠採用單個狀態引數,它必須是有效的 Spring HttpStatus。它可以是整數 404 或列舉 NOT_FOUND 的字串表示。
spring:
application:
name: gateway-server # 應用名稱
cloud:
gateway:
# 路由規則
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址
predicates: # 斷言(判斷條件)
# 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
- Path=/api-gateway/**
filters: # 閘道器過濾器
# 將 /api-gateway/product/1 重寫為 /product/1
- RewritePath=/api-gateway(?<segment>/?.*), $\{segment}
# 任何情況下,響應的 HTTP 狀態都將設定為 404
- SetStatus=404 # 404 或者對應的列舉 NOT_FOUND
訪問:http://localhost:9000/api-gateway/product/1 結果如下:
全域性過濾器 GlobalFilter
全域性過濾器不需要在配置檔案中配置,作用在所有的路由上,最終通過 GatewayFilterAdapter 包裝成 GatewayFilterChain 可識別的過濾器,它是請求業務以及路由的 URI 轉換為真實業務服務請求地址的核心過濾器,不需要配置系統初始化時載入,並作用在每個路由上。
自定義過濾器
即使 Spring Cloud Gateway
自帶許多實用的 GatewayFilter Factory、Gateway Filter、Global Filter
,但是在很多情景下我們仍然希望可以自定義自己的過濾器,實現一些騷操作。
自定義閘道器過濾器
自定義閘道器過濾器需要實現以下兩個介面 :GatewayFilter
,Ordered
。
建立過濾器
CustomGatewayFilter.java
package com.example.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定義閘道器過濾器
*/
public class CustomGatewayFilter implements GatewayFilter, Ordered {
/**
* 過濾器業務邏輯
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定義閘道器過濾器被執行");
return chain.filter(exchange); // 繼續向下執行
}
/**
* 過濾器執行順序,數值越小,優先順序越高
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
註冊過濾器
package com.example.config;
import com.example.filter.CustomGatewayFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 閘道器路由配置類
*/
@Configuration
public class GatewayRoutesConfiguration {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes().route(r -> r
// 斷言(判斷條件)
.path("/product/**")
// 目標 URI,路由到微服務的地址
.uri("lb://product-service")
// 註冊自定義閘道器過濾器
.filters(new CustomGatewayFilter())
// 路由 ID,唯一
.id("product-service"))
.build();
}
}
訪問
註釋配置檔案中所有閘道器配置,重啟並訪問:http://localhost:9000/product/1 控制檯結果如下:
自定義閘道器過濾器被執行
自定義全域性過濾器
自定義全域性過濾器需要實現以下兩個介面 :GlobalFilter
,Ordered
。通過全域性過濾器可以實現許可權校驗,安全性驗證等功能。
建立過濾器
實現指定介面,新增 @Component
註解即可。
CustomGlobalFilter.java
package com.example.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定義全域性過濾器
*/
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
/**
* 過濾器業務邏輯
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定義全域性過濾器被執行");
return chain.filter(exchange); // 繼續向下執行
}
/**
* 過濾器執行順序,數值越小,優先順序越高
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
訪問
訪問:http://localhost:9000/product/1 控制檯結果如下:
自定義全域性過濾器被執行
統一鑑權
接下來我們在閘道器過濾器中通過 token 判斷使用者是否登入,完成一個統一鑑權案例。
建立過濾器
AccessFilter.java
package com.example.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 許可權驗證過濾器
*/
@Component
public class AccessFilter implements GlobalFilter, Ordered {
private Logger logger = LoggerFactory.getLogger(AccessFilter.class);
/**
* 過濾器業務邏輯
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 獲取請求引數
String token = exchange.getRequest().getQueryParams().getFirst("token");
// 業務邏輯處理
if (null == token) {
logger.warn("token is null...");
ServerHttpResponse response = exchange.getResponse();
// 響應型別
response.getHeaders().add("Content-Type", "application/json; charset=utf-8");
// 響應狀態碼,HTTP 401 錯誤代表使用者沒有訪問許可權
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 響應內容
String message = "{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}";
DataBuffer buffer = response.bufferFactory().wrap(message.getBytes());
// 請求結束,不在繼續向下請求
return response.writeWith(Mono.just(buffer));
}
// 使用 token 進行身份驗證
logger.info("token is OK!");
return chain.filter(exchange);
}
/**
* 過濾器執行順序,數值越小,優先順序越高
*
* @return
*/
@Override
public int getOrder() {
return 1;
}
}
訪問
訪問:http://localhost:9000/product/1 結果如下:
訪問:http://localhost:9000/product/1?token=abc123 結果如下:
下一篇我們講解 Gateway 閘道器如何實現限流、整合Sentinel實現限流以及高可用閘道器環境搭建,記得關注噢~
本文采用 知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協議
。
大家可以通過 分類
檢視更多關於 Spring Cloud
的文章。