如果路由的情況比較複雜,必要時可以自行實作 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();
}
}
透過 RouteLocatorBuilder
的 routes
,可以逐一建立路由,每呼叫 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();
}