簡介 RouteLocator


如果路由的情況比較複雜,必要時可以自行實作 RouteLocator,以程式碼的方式來實現路由。例如,底下的 RouteLocator 可以將 http:/localhost:5555/openhome/xxxx 都路由到我的網站:

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();
    }
}

透過 RouteLocatorBuilderroutes,可以逐一建立路由,每呼叫 route 一次可建立一條路由規則,p 的型態是 PredicateSpec,可以透過它的 predicate 來進行斷言,要實作的介面是 Java 8 的 Predicate,在上頭取得了路徑,判斷是不是 /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();
}