項目位址
過濾器的概念這裡不展開說,簡而言之就是在路由的過程中為了做一些邏輯,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路徑過濾器
可以實作URL重寫,通過重寫URL可以實作隐藏實際路徑,提高安全性,用于使用者記憶和鍵入,易于被搜尋引擎收錄等優點。實作方式如下:
- 使用RewritePathGatewayFilterFactory
RewritePath網關過濾器工廠采用路徑正規表達式參數和替換參數,使用Java正規表達式來靈活的重寫請求路徑
這裡給出一個重寫路徑的例子:
server:
port: 8082
spring:
application:
name: service-gateway-server
cloud:
gateway:
#路由規則
routes:
- id: service-provider-gateway #路由ID,唯一
uri: lb://service-provider-gateway # lb:// 根據服務名稱從注冊中心擷取服務請求位址
predicates: # 斷言(判斷條件)
- Path=/provider/**, /api-gateway/** # 比對對應的URI的請求,将比對到的請求追加在目标URI之後
filters:
# 将/api-gateway/provider/getProductsById/1 重寫為 /provider/getProductsById/1
- RewritePath=/api-gateway(?<segment>/?.*),$\{segment}
為了友善示範,路由還按照舊的方式來配置,斷言裡的規則是,URL符合/provider/**, /api-gateway/**這樣的路徑就會被路由,而商品服務沒有api-gateway/這個路徑的資源,直接通路肯定會報錯,我們通過重寫路徑将/api-gateway/的路徑抹除
重新開機服務後測試,http://localhost:8082/api-gateway/provider/getProductsById/1 通路此路徑正确傳回結果
- 使用PrefixPathGatewayFilterFactory
使用PrefixPath來給每個比對的URI增加指定字首
server:
port: 8082
spring:
application:
name: service-gateway-server
cloud:
gateway:
#路由規則
routes:
- id: service-provider-gateway #路由ID,唯一
uri: lb://service-provider-gateway # lb:// 根據服務名稱從注冊中心擷取服務請求位址
predicates: # 斷言(判斷條件)
- Path=/** # 比對對應的URI的請求,将比對到的請求追加在目标URI之後
filters:
# 将getProductsById/1 重寫為 /provider/getProductsById/1
- PrefixPath=/provider
為了友善測試,斷言我改成了所有路徑都符合,通路http://localhost:8082/getProductsById/1能夠正常傳回
- 使用StripPrefixGatewayFilterFactory
StripPrefix網關過濾器工廠采用一個參數StripPrefix進行分割字首,表示将請求發送到下遊之前剝離的路徑個數
gateway:
#路由規則
routes:
- id: service-provider-gateway #路由ID,唯一
uri: lb://service-provider-gateway # lb:// 根據服務名稱從注冊中心擷取服務請求位址
predicates: # 斷言(判斷條件)
- Path=/** # 比對對應的URI的請求,将比對到的請求追加在目标URI之後
filters:
# 将/api/123/provider/getProductsById/1 剝離為 /provider/getProductsById/1 2表示分割兩個
- StripPrefix=2
就是開始計算剝離幾個路徑
- 使用SetPathGatewayFilterFactory
SetPath網關過濾器工廠采用路徑模闆參數。提供了一種通過允許模闆化路徑來操作請求路徑的簡單方法,使用了Spring Framework中的Uri模闆,允許比對多個字段
簡而言之就是在Path中配置的模闆參數可以在SetPath重新使用配置
gateway:
#路由規則
routes:
- id: service-provider-gateway #路由ID,唯一
uri: lb://service-provider-gateway # lb:// 根據服務名稱從注冊中心擷取服務請求位址
predicates: # 斷言(判斷條件)
- Path=/api/provider/getProductsById/{segment} # 比對對應的URI的請求,将比對到的請求追加在目标URI之後
filters:
# /api//provider/getProductsById/1 重新配置為 /provider/getProductsById/1
- SetPath=/provider/getProductsById/{segment}
注意這裡我将- Path 設定為:
Path=/api/provider/getProductsById/{segment}
後面的參數會存進一個Map中,能夠在SetPah中使用,結果是我通路:http://localhost:8082/api/provider/getProductsById/1 生效
- 使用AddRequestParameter
就是給URl加參數
修改一下接口,增加接收一個flag參數,并列印
@GetMapping(value = "getProductsById/{id}")
public Product getProductsById(@PathVariable("id") Long id,Integer flag) {
System.out.println("flag = " + flag);
return productService.selectProductById(id);
}
routes:
- id: service-provider-gateway #路由ID,唯一
uri: lb://service-provider-gateway # lb:// 根據服務名稱從注冊中心擷取服務請求位址
predicates: # 斷言(判斷條件)
- Path=/** # 比對對應的URI的請求,将比對到的請求追加在目标URI之後
filters:
# 給URL增加一個flag參數
- AddRequestParameter=flag,1
通路後發現控制台列印了 flag=1
- 使用SetStatus
SetStatus過濾器工廠采用單個狀态參數,它必須是有效的Spring HttpStatus,可以使用404這種狀态碼或者NOT_FOUND字元串表示,這個一般用于格式化狀态碼,比如沒有處理304,我就把304處理為404等等
filters:
# 所有請求傳回500
- SetStatus=500
請求接口發現雖然正常傳回了,但F12看下接口狀态已經是500了
GlobalFilter(全局過濾器)
全局過濾器不需要進行配置,作用在所有路由上。以下的過濾器其實已經生效了,一般我們都會重寫并且自定義規則
自定義網關過濾器
如果目前沒有符合自己需求的網關過濾器可以進行自定義。需要實作GatewayFilter,Ordered兩個接口
CustomGatewayFilter.java
package gateway.custom;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定義網關過濾器
*
* @author Oliver
* @version 1.0
* @date 2021/06/02 10:37:12
*/
public class CustomGatewayFilter implements GatewayFilter, Ordered {
/**
* 過濾器業務邏輯
* @return reactor.core.publisher.Mono<java.lang.Void>
* @date: 2021-06-02 10:38
* @author Oliver
**/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
RequestPath path = request.getPath();
System.out.println("path = " + path);
System.out.println("自定義網關執行");
//繼續向下執行,如果不符合條件可以不傳回,就不會走過濾器鍊了
return chain.filter(exchange);
}
/**
* 過濾器執行順序,數值越小,優先級越高
* @return int
* @date: 2021-06-02 10:39
* @author Oliver
**/
@Override
public int getOrder() {
return 0;
}
}
注冊自定義的過濾器
GatewayRoutesConfig.java
package gateway.config;
import gateway.custom.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;
/**
* 網關路由配置類
*
* @author Oliver
* @version 1.0
* @date 2021/06/02 10:45:07
*/
@Configuration
public class GatewayRoutesConfig {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder){
return builder.routes().route(r -> r
//斷言
.path("/provider/**")
//目标URI,路由到微服務位址
.uri("lb://service-provider-gateway")
//注冊自定義網關過濾器
.filters(new CustomGatewayFilter())
//路由 ID,唯一
.id("service-provider-gateway")).build();
}
}
自定義全局過濾器
需要實作兩個接口,GlobalFilter,Ordered,一般實作權限校驗、安全性檢查等,這裡模拟一個統一鑒權功能
package gateway.custom;
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;
import java.nio.charset.StandardCharsets;
/**
* 自定義全局過濾器
*
* @author Oliver
* @version 1.0
* @date 2021/06/02 10:56:56
*/
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
/**
* 過濾器業務邏輯
* @return reactor.core.publisher.Mono<java.lang.Void>
* @date: 2021-06-02 10:58
* @author Oliver
**/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定義全局過濾器");
//這個getFirst并不是擷取第一個參數,而是擷取參數,就是起了一個這個名字
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (token == null) {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type","application/json;charset=utf-8");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
String msg = "{\"message\":\""+HttpStatus.UNAUTHORIZED.getReasonPhrase()+"\"}";
DataBuffer wrap = response.bufferFactory().wrap(msg.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(wrap));
}
System.out.println("token is ok");
//繼續向下執行
return chain.filter(exchange);
}
/**
* 過濾器順序 資料越小,優先級越高
* @return int
* @date: 2021-06-02 10:58
* @author Oliver
**/
@Override
public int getOrder() {
return 0;
}
}