天天看點

Spring Cloud系列_33 Gateway過濾器

項目位址

過濾器的概念這裡不展開說,簡而言之就是在路由的過程中為了做一些邏輯,Gateway提供過濾器給開發者使用

Spring Cloud Gateway 過濾器根據作用範圍劃分為GatewayFilter和GlobalFilter,二者差別如下:

  1. GatewayFilter(網關過濾器):需要通過spring.cloud.routes.filters配置在具體的路由下,隻作用在目前路由上或通過spring.cloud.default-filters配置在全局,作用在所有路由上
  2. 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(全局過濾器)

全局過濾器不需要進行配置,作用在所有路由上。以下的過濾器其實已經生效了,一般我們都會重寫并且自定義規則

Spring Cloud系列_33 Gateway過濾器

自定義網關過濾器

如果目前沒有符合自己需求的網關過濾器可以進行自定義。需要實作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;
    }
}
           

繼續閱讀