spring cloud gateway系列教程3—Global Filters

spring cloud gateway系列教程目錄

  1. spring cloud gateway系列教程1—Route Predicate
  2. spring cloud gateway系列教程2——GatewayFilter_上篇
  3. spring cloud gateway系列教程2——GatewayFilter_下篇
  4. spring cloud gateway系列教程3—Global Filters
  5. spring cloud gateway系列教程4—其他配置

Global Filters

GlobalFilter接口方法和GatewayFilter是一樣的苗分,GlobalFilter特別之處在于它的作用是全局的厌蔽。

1. Combined Global Filter and GatewayFilter Ordering

當(dāng)請求到來時(shí),Filtering Web Handler處理器會(huì)添加所有GlobalFilter實(shí)例和匹配的GatewayFilter實(shí)例到過濾器鏈中摔癣,通過對filterbean配置注解@Order奴饮,則過濾器鏈會(huì)對這些過濾器實(shí)例bean進(jìn)行排序纬向。

Spring Cloud Gateway將過濾器的邏輯按請求執(zhí)行點(diǎn)分為”pre"和"post"的一前一后處理,如果是高優(yōu)先級(jí)的過濾器戴卜,則在"pre"邏輯中最先執(zhí)行罢猪,在"post"邏輯中最后執(zhí)行。

ExampleConfiguration.java.

@Bean
@Order(-1)
public GlobalFilter a() {
    return (exchange, chain) -> {
        log.info("first pre filter");
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            log.info("third post filter");
        }));
    };
}

@Bean
@Order(0)
public GlobalFilter b() {
    return (exchange, chain) -> {
        log.info("second pre filter");
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            log.info("second post filter");
        }));
    };
}

@Bean
@Order(1)
public GlobalFilter c() {
    return (exchange, chain) -> {
        log.info("third pre filter");
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            log.info("first post filter");
        }));
    };
}

上面例子陸續(xù)會(huì)打印的是:

first pre filter
second pre filter
third pre filter
first post filter
second post filter
third post filter

2. Forward Routing Filter

ForwardRoutingFilter會(huì)查看exchange的屬性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的URI內(nèi)容叉瘩,如果url的scheme是forward膳帕,比如:forward://localendpoint,則它會(huì)使用Spirng的DispatcherHandler來處理這個(gè)請求薇缅。

源碼實(shí)現(xiàn):

@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);

        String scheme = requestUrl.getScheme();
        if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
            return chain.filter(exchange);
        }
        setAlreadyRouted(exchange);

        //TODO: translate url?

        if (log.isTraceEnabled()) {
            log.trace("Forwarding to URI: "+requestUrl);
        }

        return this.dispatcherHandler.handle(exchange);
    }

3. LoadBalancerClient Filter

LoadBalancerClientFilter會(huì)查看exchange的屬性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的URI內(nèi)容危彩,如果url的scheme是lb,比如:lb://myservice泳桦,或者是ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR屬性的內(nèi)容是lb汤徽,則它會(huì)使用Spring Cloud的LoadBalancerClient來將host轉(zhuǎn)化為實(shí)際的host和port,并以此替換屬性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的內(nèi)容灸撰,原來的URL則會(huì)被添加到ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR屬性的列表中谒府。

源碼實(shí)現(xiàn):

@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
        String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
        if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
            return chain.filter(exchange);
        }
        //preserve the original url
        addOriginalRequestUrl(exchange, url);

        log.trace("LoadBalancerClientFilter url before: " + url);

        final ServiceInstance instance = loadBalancer.choose(url.getHost());

        if (instance == null) {
            throw new NotFoundException("Unable to find instance for " + url.getHost());
        }

        URI uri = exchange.getRequest().getURI();

        // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
        // if the loadbalancer doesn't provide one.
        String overrideScheme = null;
        if (schemePrefix != null) {
            overrideScheme = url.getScheme();
        }

        URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);

        log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
        exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
        return chain.filter(exchange);
    }

application.yml.

spring:
  cloud:
    gateway:
      routes:
      - id: myRoute
        uri: lb://service
        predicates:
        - Path=/service/**

默認(rèn)情況下,如果LoadBalancer找不到服務(wù)實(shí)例浮毯,則會(huì)返回HTTP狀態(tài)碼503完疫,你也可以通過修改spring.cloud.gateway.loadbalancer.use404=true配置修改為返回狀態(tài)碼404

4. Netty Routing Filter

如果ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR屬性中的url的scheme是httphttps债蓝,則Netty Routing Filter才會(huì)執(zhí)行壳鹤,并使用Netty作為http請求客戶端對下游進(jìn)行代理請求。請求的響應(yīng)會(huì)放在exchange的ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR屬性中饰迹,以便后面的filter做進(jìn)一步的處理芳誓。

5. Netty Write Response Filter

如果NettyWriteResponseFilter發(fā)現(xiàn)exchange的ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR屬性中存在Netty的HttpClientResponse類型實(shí)例,在所有過濾器都執(zhí)行完畢后啊鸭,它會(huì)將響應(yīng)寫回到gateway客戶端的響應(yīng)中锹淌。

源碼實(shí)現(xiàn):

@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added
        // until the WebHandler is run
        return chain.filter(exchange).then(Mono.defer(() -> {
            HttpClientResponse clientResponse = exchange.getAttribute(CLIENT_RESPONSE_ATTR);

            if (clientResponse == null) {
                return Mono.empty();
            }
            log.trace("NettyWriteResponseFilter start");
            ServerHttpResponse response = exchange.getResponse();

            NettyDataBufferFactory factory = (NettyDataBufferFactory) response.bufferFactory();
            //TODO: what if it's not netty

            final Flux<NettyDataBuffer> body = clientResponse.receive()
                    .retain() //TODO: needed?
                    .map(factory::wrap);

            MediaType contentType = response.getHeaders().getContentType();
            return (isStreamingMediaType(contentType) ?
                    response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body));
        }));
    }

    //TODO: use framework if possible
    //TODO: port to WebClientWriteResponseFilter
    private boolean isStreamingMediaType(@Nullable MediaType contentType) {
        return (contentType != null && this.streamingMediaTypes.stream()
                        .anyMatch(contentType::isCompatibleWith));
    }

6. RouteToRequestUrl Filter

如果exchange的ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR屬性存放了Route對象,則RouteToRequestUrlFilter會(huì)根據(jù)基于請求的URI創(chuàng)建新的URI赠制,新的URI會(huì)更新到ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR屬性中赂摆。

如果URI有scheme前綴,比如:lb:ws://serviceid憎妙,lbscheme截取出來放到ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR屬性中库正,方便后面的filter使用曲楚。

源碼實(shí)現(xiàn):

@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
        if (route == null) {
            return chain.filter(exchange);
        }
        log.trace("RouteToRequestUrlFilter start");
        URI uri = exchange.getRequest().getURI();
        boolean encoded = containsEncodedParts(uri);
        URI routeUri = route.getUri();

        if (hasAnotherScheme(routeUri)) {
            // this is a special url, save scheme to special attribute
            // replace routeUri with schemeSpecificPart
            exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme());
            routeUri = URI.create(routeUri.getSchemeSpecificPart());
        }

        URI requestUrl = UriComponentsBuilder.fromUri(uri)
                .uri(routeUri)
                .build(encoded)
                .toUri();
        exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
        return chain.filter(exchange);
    }

    /* for testing */ static boolean hasAnotherScheme(URI uri) {
        return schemePattern.matcher(uri.getSchemeSpecificPart()).matches() && uri.getHost() == null
                && uri.getRawPath() == null;
    }

7. Websocket Routing Filter

如果請求URL的scheme是wswss的話厘唾,那么Websocket Routing Filter就會(huì)使用Spring Web Socket底層來處理對下游的請求轉(zhuǎn)發(fā)。

如果Websocket也使用了負(fù)載均衡龙誊,則需要這樣配置:lb:ws://serviceid.

源碼實(shí)現(xiàn):

@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        changeSchemeIfIsWebSocketUpgrade(exchange);

        URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
        String scheme = requestUrl.getScheme();

        if (isAlreadyRouted(exchange) || (!"ws".equals(scheme) && !"wss".equals(scheme))) {
            return chain.filter(exchange);
        }
        setAlreadyRouted(exchange);


        HttpHeaders headers = exchange.getRequest().getHeaders();
        HttpHeaders filtered = filterRequest(getHeadersFilters(),
                exchange);

        List<String> protocols = headers.get(SEC_WEBSOCKET_PROTOCOL);
        if (protocols != null) {
            protocols = headers.get(SEC_WEBSOCKET_PROTOCOL).stream()
                    .flatMap(header -> Arrays.stream(commaDelimitedListToStringArray(header)))
                    .map(String::trim)
                    .collect(Collectors.toList());
        }

        return this.webSocketService.handleRequest(exchange,
                new ProxyWebSocketHandler(requestUrl, this.webSocketClient,
                        filtered, protocols));
    }

8. Making An Exchange As Routed

上面一些像ForwardRoutingFilter抚垃、Websocket Routing Filter的源碼中,都可以清楚看到gateway通過設(shè)置gatewayAlreadyRouted標(biāo)識(shí)這個(gè)請求是否已經(jīng)路由轉(zhuǎn)發(fā)出去了,無需其他filter重復(fù)路由鹤树,這樣就可以避免重復(fù)錯(cuò)誤的路由操作铣焊,保證了路由的實(shí)現(xiàn)靈活性。

ServerWebExchangeUtils.isAlreadyRouted檢查是否已被路由罕伯,ServerWebExchangeUtils.setAlreadyRouted標(biāo)記已被路由狀態(tài)曲伊。

這一章介紹了Spring Cloud Gateway官方的Global Filters使用場景,下一章講gateway其他配置的使用追他。

如果想查看其他spring cloud gateway的案例和使用坟募,可以點(diǎn)擊查看

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市邑狸,隨后出現(xiàn)的幾起案子懈糯,更是在濱河造成了極大的恐慌,老刑警劉巖单雾,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赚哗,死亡現(xiàn)場離奇詭異,居然都是意外死亡硅堆,警方通過查閱死者的電腦和手機(jī)屿储,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渐逃,“玉大人扩所,你說我怎么就攤上這事∑庸裕” “怎么了祖屏?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長买羞。 經(jīng)常有香客問我袁勺,道長,這世上最難降的妖魔是什么畜普? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任期丰,我火速辦了婚禮,結(jié)果婚禮上吃挑,老公的妹妹穿的比我還像新娘钝荡。我一直安慰自己,他們只是感情好舶衬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布埠通。 她就那樣靜靜地躺著,像睡著了一般逛犹。 火紅的嫁衣襯著肌膚如雪端辱。 梳的紋絲不亂的頭發(fā)上梁剔,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機(jī)與錄音舞蔽,去河邊找鬼荣病。 笑死,一個(gè)胖子當(dāng)著我的面吹牛渗柿,可吹牛的內(nèi)容都是我干的个盆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼朵栖,長吁一口氣:“原來是場噩夢啊……” “哼砾省!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起混槐,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤编兄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后声登,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狠鸳,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年悯嗓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了件舵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脯厨,死狀恐怖铅祸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情合武,我是刑警寧澤临梗,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站稼跳,受9級(jí)特大地震影響盟庞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汤善,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一什猖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧红淡,春花似錦不狮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至颈渊,卻和暖如春遂黍,著一層夾襖步出監(jiān)牢的瞬間终佛,已是汗流浹背俊嗽。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工雾家, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绍豁。 一個(gè)月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓芯咧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親竹揍。 傳聞我的和親對象是個(gè)殘疾皇子敬飒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349

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