Spring Cloud Gateway之RouteLocator简介


本文来自于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();
}