Spring Cloud Gateway之RouteLocator簡介

banq發表於2019-02-01

本文來自於openhome,點選標題見原文,Spring 5以後引入了Spring Cloud Gateway作為路由閘道器,類似Nginx,其複雜的路由規則可透過程式碼實現,這就是RouteLocator用處所在。

底下的RouteLocator可以將http:/localhost:5555/openhome/xxxx都路由到openhome.cc的網站:

package cc.openhome;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
       return builder.routes()
           .route(p -> p
               .predicate(exchange -> exchange.getRequest().getPath().subPath(0).toString().startsWith(("/openhome/")))
               .filters(f -> f.rewritePath("/openhome/(?<remaining>.*)", "/${remaining}"))
               .uri("https://openhome.cc"))
           .build();
    }
}


透過RouteLocatorBuilder的routes,可以逐一建立路由,每呼叫route一次可建立一條路由規則,p的代表是PredicateSpec,可以透過它的predicate來進行斷言,要實現的介面就是Java 8的Predicate,透過exchange取得了路徑,然後判斷它是不是以/openhome/開頭,對於簡單的情況,也可以透過PredicateSpec一些方法如path等來進行斷言。

filters是用來設定過濾器,rewritePath方法會使用內建的過濾器重寫路徑,實際上,也可以自行實現GatewayFilter例項,並透過filter方法來設定,例如同樣的功能,然而自定於GatewayFilter的話會是:

package cc.openhome;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.server.reactive.ServerHttpRequest;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;

@SpringBootApplication
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
       return builder.routes()
        .route(p -> p
            .predicate(exchange -> exchange.getRequest().getPath().subPath(0).toString().startsWith(("/openhome/")))
            .filters(f -> f.filter((exchange, chain) -> {
                ServerHttpRequest req = exchange.getRequest();
                addOriginalRequestUrl(exchange, req.getURI());
                String path = req.getURI().getRawPath();
                String newPath = path.replaceAll("/openhome/(?<remaining>.*)", "/${remaining}");
                ServerHttpRequest request = req.mutate()
                        .path(newPath)
                        .build();

                exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());

                return chain.filter(exchange.mutate().request(request).build());
            }))
            .uri("https://openhome.cc"))
        .build();
    }
}

GatewayFilter 實際上只有一個方法要實現:

Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);


這感覺有點像是org.springframework.web.server.WebFilter對吧!事實上也是抄它的沒錯,GatewayFilter的原始碼註解中就這麼寫了:


/**
 * Contract for interception-style, chained processing of Web requests that may
 * be used to implement cross-cutting, application-agnostic requirements such
 * as security, timeouts, and others. Specific to a Gateway
 *
 * Copied from WebFilter
 *
 * @author Rossen Stoyanchev
 * @since 5.0
 */
public interface GatewayFilter extends ShortcutConfigurable {


在〈使用Spring Cloud Gateway〉中談過,Spring Cloud Gateway內建了一些斷言器與過濾器工廠類別,可以參考它們的實現,其中也包含了斷言器與過濾器的實現邏輯。

斷言器相關的原始碼可參考org/springframework/cloud/gateway/handler,過濾器相關的原始碼可參考org/springframework/cloud/gateway/filter

例如,上面的範例中,GatewayFilter的實現就是從RewritePathGatewayFilterFactory中抄出來的。

透過Builder等方法,使用內建的斷言或過濾還是比較方便的,例如,相同的需求如下定義,還是比較方便的,例如路徑斷言可以透過path指定Ant路徑模式:

@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
   return builder.routes()
           .route(p -> 
               p.path("/openhome/**")
                .filters(f -> f.rewritePath("/openhome/(?<remaining>.*)", "/${remaining}"))
                .uri("https://openhome.cc")
            ).build();
}


底下是個能夠完成〈使用Spring Cloud Gateway〉相同路由的示範:

@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
   return builder.routes()
           .route(p -> 
               p.path("/api/acct/**")
                .filters(f -> f.stripPrefix(2))
                .uri("lb://acctsvi")
           )
           .route(p -> 
               p.path("/api/msg/**")
                .filters(f -> f.stripPrefix(2))
                .uri("lb://msgsvi")
           )
           .route(p -> 
               p.path("/api/email/**")
                .filters(f -> f.stripPrefix(2))
                .uri("lb://emailsvi")
           )
           .build();
}

 

相關文章