Spring Cloud Gateway 之 Filter

文章首發(fā)于公眾號(hào)《程序員果果》
地址:https://mp.weixin.qq.com/s/CFqKtIzP1ZzjioVZyGugeQ
本篇源碼:https://github.com/gf-huanchupk/SpringCloudLearning

簡(jiǎn)介

網(wǎng)關(guān)經(jīng)常需要對(duì)路由請(qǐng)求進(jìn)行過(guò)濾脑融,進(jìn)行一些操作,如鑒權(quán)之后構(gòu)造頭部之類(lèi)的,過(guò)濾的種類(lèi)很多积仗,如增加請(qǐng)求頭搓彻、增加請(qǐng)求 參數(shù) 、增加響應(yīng)頭和斷路器等等功能,這就用到了Spring Cloud Gateway 的 Filter贾节。

作用

當(dāng)我們有很多個(gè)服務(wù)時(shí)狞悲,比如下圖中的user-service撮抓、goods-service、sales-service等服務(wù)摇锋,客戶端請(qǐng)求各個(gè)服務(wù)的Api時(shí)丹拯,每個(gè)服務(wù)都需要做相同的事情站超,比如鑒權(quán)、限流乖酬、日志輸出等死相。

對(duì)于這樣重復(fù)的工作,可以在微服務(wù)的上一層加一個(gè)全局的權(quán)限控制咬像、限流算撮、日志輸出的Api Gateway服務(wù),然后再將請(qǐng)求轉(zhuǎn)發(fā)到具體的業(yè)務(wù)服務(wù)層县昂。這個(gè)Api Gateway服務(wù)就是起到一個(gè)服務(wù)邊界的作用肮柜,外接的請(qǐng)求訪問(wèn)系統(tǒng),必須先通過(guò)網(wǎng)關(guān)層倒彰。

生命周期

Spring Cloud Gateway 的 Filter 的生命周期不像 Zuul 的那么豐富素挽,它只有兩個(gè):“pre” 和 “post”。

  • PRE: 這種過(guò)濾器在請(qǐng)求被路由之前調(diào)用狸驳。我們可利用這種過(guò)濾器實(shí)現(xiàn)身份驗(yàn)證预明、在集群中選擇請(qǐng)求的微服務(wù)、記錄調(diào)試信息等耙箍。
  • POST:這種過(guò)濾器在路由到微服務(wù)以后執(zhí)行撰糠。這種過(guò)濾器可用來(lái)為響應(yīng)添加標(biāo)準(zhǔn)的 HTTP Header、收集統(tǒng)計(jì)信息和指標(biāo)辩昆、將響應(yīng)從微服務(wù)發(fā)送給客戶端等阅酪。

分類(lèi)

Spring Cloud Gateway 的 Filter 從作用范圍可分為另外兩種GatewayFilter 與 GlobalFilter。

  • GatewayFilter:應(yīng)用到單個(gè)路由或者一個(gè)分組的路由上汁针。
  • GlobalFilter:應(yīng)用到所有的路由上术辐。

Gateway filter

過(guò)濾器允許以某種方式修改傳入的HTTP請(qǐng)求或傳出的HTTP響應(yīng)。過(guò)濾器的作用域?yàn)樘囟酚墒┪蕖pring Cloud Gateway包含許多內(nèi)置的GatewayFilter工廠辉词。

官方文檔都給出了這些過(guò)濾器工廠詳細(xì)的使用案例,在這里我們講解2個(gè)來(lái)演示下使用猾骡。

AddRequestHeader GatewayFilter Factory

application.yml如下:

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: http://httpbin.org:80/get
        filters:
        - AddRequestHeader=X-Request-Foo, Bar
        predicates:
        - Method=GET

過(guò)濾器工廠會(huì)在匹配的請(qǐng)求頭加上一對(duì)請(qǐng)求頭瑞躺,名稱為X-Request-Foo,值為Bar兴想。

RewritePath GatewayFilter Factory

在Nginx服務(wù)啟中有一個(gè)非常強(qiáng)大的功能就是重寫(xiě)路徑幢哨,Spring Cloud Gateway默認(rèn)也提供了這樣的功能,這個(gè)功能是Zuul沒(méi)有嫂便。

application.yml如下:

spring:
  cloud:
    gateway:
      routes:
      - id: rewritepath_route
        uri: http://httpbin.org
        predicates:
        - Path=/foo/**
        filters:
        - RewritePath=/foo/(?<segment>.*), /$\{segment}

所有的/foo/**開(kāi)始的路徑都會(huì)命中配置的router捞镰。
請(qǐng)求http://httpbin.org/foo/get ,會(huì)轉(zhuǎn)到http://httpbin.org/get,結(jié)果如下:

{
  "args": {
    
  },
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "zh-CN,zh;q=0.9,zh-TW;q=0.8",
    "Cache-Control": "max-age=0",
    "Cookie": "Hm_lvt_0c0e9d9b1e7d617b3e6842e85b9fb068=1550127915; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%22168eada0ded53b-0d5d8c3ba9b7a2-10316653-1296000-168eada0dee3ad%22%2C%22%24device_id%22%3A%22168eada0ded53b-0d5d8c3ba9b7a2-10316653-1296000-168eada0dee3ad%22%2C%22props%22%3A%7B%7D%7D; _gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1",
    "Forwarded": "proto=http;host=\"127.0.0.1:8080\";for=\"127.0.0.1:62278\"",
    "Host": "httpbin.org",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36",
    "X-Forwarded-Host": "127.0.0.1:8080",
    "X-Forwarded-Prefix": "/foo"
  },
  "origin": "127.0.0.1, 124.74.78.150, 127.0.0.1",
  "url": "https://127.0.0.1:8080/get"
}

自定義GatewayFilter

Spring Cloud Gateway內(nèi)置了的過(guò)濾器工廠岸售,足夠是大部分場(chǎng)景使用几迄,而且我們可以實(shí)現(xiàn)GatewayFilter和Ordered 這兩個(gè)接口來(lái)自定義過(guò)濾器。代碼如下:

/**
 * 統(tǒng)計(jì)某個(gè)或者某種路由的處理時(shí)長(zhǎng)
 */
public class CustomerGatewayFilter implements GatewayFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger( CustomerGatewayFilter.class );
    private static final String COUNT_START_TIME = "countStartTime";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        exchange.getAttributes().put(COUNT_START_TIME, Instant.now().toEpochMilli() );
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    long startTime = exchange.getAttribute(COUNT_START_TIME);
                    long endTime=(Instant.now().toEpochMilli() - startTime);
                    log.info(exchange.getRequest().getURI().getRawPath() + ": " + endTime + "ms");
                })
        );
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

上述代碼中冰评,getOrder()方法是來(lái)給過(guò)濾器設(shè)定優(yōu)先級(jí)別的映胁,值越大則優(yōu)先級(jí)越低。需要將自定義的GatewayFilter 注冊(cè)到router中甲雅,代碼如下:

@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route(r -> r.path("/customer/**")
                    .filters(f -> f.filter(new CustomerGatewayFilter())
                            .addResponseHeader("X-Response-test", "test"))
                    .uri("http://httpbin.org:80/get")
                    .id("customer_filter_router")
            )
            .build();
}

使用 curl 測(cè)試解孙,命令行輸入:

curl http://localhost:8080/customer/555

控制臺(tái)輸出如下:

2019-02-22 10:39:47.840  INFO 1310 --- [ctor-http-nio-3] com.gf.config.CustomerGatewayFilter      : /customer/555: 314ms

自定義過(guò)濾器工廠

自定義GatewayFilter又有兩種實(shí)現(xiàn)方式,一種是上面的直接 實(shí)現(xiàn)GatewayFilter接口抛人,另一種是 自定義過(guò)濾器工廠(繼承AbstractGatewayFilterFactory類(lèi)) , 選擇自定義過(guò)濾器工廠的方式弛姜,可以在配置文件中配置過(guò)濾器了。

@Component
public class CustomerGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomerGatewayFilterFactory.Config> {

    private static final Logger log = LoggerFactory.getLogger( CustomerGatewayFilterFactory.class );
    private static final String COUNT_START_TIME = "countStartTime";

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("enabled");
    }

    public CustomerGatewayFilterFactory() {
        super(Config.class);
        log.info("Loaded GatewayFilterFactory [CustomerGatewayFilterFactory]");
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            if (!config.isEnabled()) {
                return chain.filter(exchange);
            }
            exchange.getAttributes().put(COUNT_START_TIME, System.currentTimeMillis());
            return chain.filter(exchange).then(
                    Mono.fromRunnable(() -> {
                        Long startTime = exchange.getAttribute(COUNT_START_TIME);
                        if (startTime != null) {
                            StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
                                    .append(": ")
                                    .append(System.currentTimeMillis() - startTime)
                                    .append("ms");
                            sb.append(" params:").append(exchange.getRequest().getQueryParams());
                            log.info(sb.toString());
                        }
                    })
            );
        };
    }

    public static class Config {
        /**
         * 控制是否開(kāi)啟統(tǒng)計(jì)
         */
        private boolean enabled;

        public Config() {}

        public boolean isEnabled() {
            return enabled;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }

}

application.yml 中 網(wǎng)關(guān)路由配置如下:

spring:
  profiles: elapse_route
  cloud:
    gateway:
      routes:
        - id: elapse_route
          uri: http://httpbin.org:80/get
          filters:
          - Customer=true
          predicates:
          - Method=GET

使用 curl 測(cè)試妖枚,命令行輸入:

curl http://localhost:8080/customer?foo=1

控制臺(tái)輸出如下:

2019-02-23 13:37:08.769  INFO 1700 --- [ctor-http-nio-3] c.g.config.CustomerGatewayFilterFactory  : /customer: 585ms params:{foo=[1]}

Global filter

Spring Cloud Gateway框架內(nèi)置的GlobalFilter如下:

內(nèi)置的 GlobalFilter 能夠滿足大多數(shù)的需求了廷臼,但是如果遇到特殊情況,內(nèi)置滿足不了我們的需求绝页,還可以自定義GlobalFilter荠商。

自定義GlobalFilter

下面的我們自定義一個(gè)GlobalFilter,去校驗(yàn)所有請(qǐng)求的請(qǐng)求參數(shù)中是否包含“token”续誉,如何不包含請(qǐng)求參數(shù)“token”則不轉(zhuǎn)發(fā)路由莱没,否則執(zhí)行正常的邏輯。

/**
 * Token 校驗(yàn)全局過(guò)濾器
 */
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger( AuthorizeFilter.class );

    private static final String AUTHORIZE_TOKEN = "token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst( AUTHORIZE_TOKEN );
        if ( StringUtils.isBlank( token )) {
            log.info( "token is empty ..." );
            exchange.getResponse().setStatusCode( HttpStatus.UNAUTHORIZED );
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

}

使用 curl 測(cè)試酷鸦,命令行輸入:

curl http://localhost:8080/customer?foo=1

控制臺(tái)輸出如下:

token is empty ...

源碼下載 : https://github.com/gf-huanchupk/SpringCloudLearning/tree/master/chapter13/springcloud-gateway-filter

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饰躲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子臼隔,更是在濱河造成了極大的恐慌嘹裂,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摔握,死亡現(xiàn)場(chǎng)離奇詭異寄狼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)盒发,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)例嘱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人宁舰,你說(shuō)我怎么就攤上這事∩莼耄” “怎么了蛮艰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)雀彼。 經(jīng)常有香客問(wèn)我壤蚜,道長(zhǎng)即寡,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任袜刷,我火速辦了婚禮聪富,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘著蟹。我一直安慰自己墩蔓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布萧豆。 她就那樣靜靜地躺著奸披,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涮雷。 梳的紋絲不亂的頭發(fā)上阵面,一...
    開(kāi)封第一講書(shū)人閱讀 52,682評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音洪鸭,去河邊找鬼样刷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛览爵,可吹牛的內(nèi)容都是我干的颂斜。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拾枣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沃疮!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起梅肤,我...
    開(kāi)封第一講書(shū)人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤司蔬,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后姨蝴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體俊啼,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年左医,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了授帕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浮梢,死狀恐怖跛十,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秕硝,我是刑警寧澤芥映,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響奈偏,放射性物質(zhì)發(fā)生泄漏坞嘀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一惊来、第九天 我趴在偏房一處隱蔽的房頂上張望丽涩。 院中可真熱鬧,春花似錦裁蚁、人聲如沸矢渊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)昆淡。三九已至,卻和暖如春刽严,著一層夾襖步出監(jiān)牢的瞬間昂灵,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工舞萄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眨补,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓倒脓,卻偏偏與公主長(zhǎng)得像撑螺,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子崎弃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容