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