1.gateway概念
- 网关就是当前微服务项目的"统一入口"
- 程序中的网关就是当前微服务项目对外界开放的统一入口
- 所有外界的请求都需要先经过网关才能访问到我们的程序
- 提供了统一入口之后,方便对所有请求进行统一的检查和管理
2. 网关的主要功能
- 将所有请求统一经过网关
- 网关可以对这些请求进行检查
- 网关方便记录所有请求的日志
- 网关可以统一将所有请求路由到正确的模块\服务上
“路由"的近义词就是"分配”
3. 工作原理
Spring Gateway的工作原理基于路由、断言(Predicates)和过滤器(Filters)三大核心概念:
- 路由(Route):定义了请求的转发规则,包括目标URL和匹配条件。
- 断言(Predicates):用于匹配HTTP请求的各种条件,如路径、头信息、参数等。只有匹配成功的请求才会被路由处理。
- 过滤器(Filters):在请求处理前后执行特定的逻辑,例如权限校验、日志记录
4. 配置和使用示例
spring:cloud:gateway:routes:- id: myrouteuri: http://example.compredicates:- Path=/api/**filters:- AddRequestHeader=X-Request-ID, \${requestId}
这个配置定义了一个路由,所有路径以/api/
开头的请求都会被转发到http://example.com
,并且在请求头中添加一个X-Request-ID
字段
springcloud gateway中配置uri有3种方式:
- ws(websocket)方式: uri: ws://localhost:9000
- http方式: uri: http://localhost:8130/
- lb(注册中心中服务名字)方式: uri: lb://brilliance-consumer
springcloud gatetway命名规范
能被gateway的lb方式识别到的命名规则为:
"[a-zA-Z]([a-zA-Z]|\\d|\\+|\\.|-)*:.*"
如果名字中有非*“a-zA-Z:.”*规则字符或者使用“_”,则会报错
5.网关gateway routes的组成
1. id:必须唯一
2. predicates(断言)
关键类源码分析:
package org.springframework.cloud.gateway.handler.predicate;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.http.server.PathContainer;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPattern.PathMatchInfo;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_PATH_CONTAINER_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.putUriTemplateVariables;
import static org.springframework.http.server.PathContainer.parsePath;/*** @author Spencer Gibb* @author Dhawal Kapil*/
public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {private static final Log log = LogFactory.getLog(PathRoutePredicateFactory.class);private static final String MATCH_TRAILING_SLASH = "matchTrailingSlash";private PathPatternParser pathPatternParser = new PathPatternParser();public PathRoutePredicateFactory() {super(Config.class);}private static void traceMatch(String prefix, Object desired, Object actual, boolean match) {if (log.isTraceEnabled()) {String message = String.format("%s \"%s\" %s against value \"%s\"", prefix, desired,match ? "matches" : "does not match", actual);log.trace(message);}}public void setPathPatternParser(PathPatternParser pathPatternParser) {this.pathPatternParser = pathPatternParser;}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("patterns", MATCH_TRAILING_SLASH);}@Overridepublic ShortcutType shortcutType() {return ShortcutType.GATHER_LIST_TAIL_FLAG;}@Overridepublic Predicate<ServerWebExchange> apply(Config config) {final ArrayList<PathPattern> pathPatterns = new ArrayList<>();synchronized (this.pathPatternParser) {pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchTrailingSlash());config.getPatterns().forEach(pattern -> {PathPattern pathPattern = this.pathPatternParser.parse(pattern);pathPatterns.add(pathPattern);});}return new GatewayPredicate() {@Overridepublic boolean test(ServerWebExchange exchange) {PathContainer path = (PathContainer) exchange.getAttributes().computeIfAbsent(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR,s -> parsePath(exchange.getRequest().getURI().getRawPath()));PathPattern match = null;for (int i = 0; i < pathPatterns.size(); i++) {PathPattern pathPattern = pathPatterns.get(i);if (pathPattern.matches(path)) {match = pathPattern;break;}}if (match != null) {traceMatch("Pattern", match.getPatternString(), path, true);PathMatchInfo pathMatchInfo = match.matchAndExtract(path);putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ATTR, match.getPatternString());String routeId = (String) exchange.getAttributes().get(GATEWAY_PREDICATE_ROUTE_ATTR);if (routeId != null) {// populated in RoutePredicateHandlerMappingexchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR, routeId);}return true;}else {traceMatch("Pattern", config.getPatterns(), path, false);return false;}}@Overridepublic Object getConfig() {return config;}@Overridepublic String toString() {return String.format("Paths: %s, match trailing slash: %b", config.getPatterns(),config.isMatchTrailingSlash());}};}@Validatedpublic static class Config {private List<String> patterns = new ArrayList<>();private boolean matchTrailingSlash = true;public List<String> getPatterns() {return patterns;}public Config setPatterns(List<String> patterns) {this.patterns = patterns;return this;}/*** @deprecated use {@link #isMatchTrailingSlash()}*/@Deprecatedpublic boolean isMatchOptionalTrailingSeparator() {return isMatchTrailingSlash();}/*** @deprecated use {@link #setMatchTrailingSlash(boolean)}*/@Deprecatedpublic Config setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) {setMatchTrailingSlash(matchOptionalTrailingSeparator);return this;}public boolean isMatchTrailingSlash() {return matchTrailingSlash;}public Config setMatchTrailingSlash(boolean matchTrailingSlash) {this.matchTrailingSlash = matchTrailingSlash;return this;}@Overridepublic String toString() {return new ToStringCreator(this).append("patterns", patterns).append(MATCH_TRAILING_SLASH, matchTrailingSlash).toString();}}}
package org.springframework.cloud.gateway.handler.predicate;import java.util.function.Consumer;
import java.util.function.Predicate;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.support.Configurable;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.cloud.gateway.support.ShortcutConfigurable;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate;/*** @author Spencer Gibb*/
@FunctionalInterface
public interface RoutePredicateFactory<C> extends ShortcutConfigurable, Configurable<C> {/*** Pattern key.*/String PATTERN_KEY = "pattern";// useful for javadsldefault Predicate<ServerWebExchange> apply(Consumer<C> consumer) {C config = newConfig();consumer.accept(config);beforeApply(config);return apply(config);}default AsyncPredicate<ServerWebExchange> applyAsync(Consumer<C> consumer) {C config = newConfig();consumer.accept(config);beforeApply(config);return applyAsync(config);}default Class<C> getConfigClass() {throw new UnsupportedOperationException("getConfigClass() not implemented");}@Overridedefault C newConfig() {throw new UnsupportedOperationException("newConfig() not implemented");}default void beforeApply(C config) {}Predicate<ServerWebExchange> apply(C config);default AsyncPredicate<ServerWebExchange> applyAsync(C config) {return toAsyncPredicate(apply(config));}default String name() {return NameUtils.normalizeRoutePredicateName(getClass());}}
自定义Vip路由断言工厂实现
package com.wemedia.gateway.config;import jakarta.validation.constraints.NotEmpty;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;/*** 自定义Vip路由断言工厂*/
@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {public VipRoutePredicateFactory(){super(Config.class);}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("param","value");}@Overridepublic Predicate<ServerWebExchange> apply(Config config) {return new GatewayPredicate() {@Overridepublic boolean test(ServerWebExchange serverWebExchange) {//localhost/search?q=hhh&user=jackmaServerHttpRequest request = serverWebExchange.getRequest();String first = request.getQueryParams().getFirst(config.param);return StringUtils.hasText(first)&&first.equals(config.value);}};}@Validatedpublic static class Config {@NotEmptyprivate String value;@NotEmptyprivate String param;public String getParam() {return param;}public void setParam(String param) {this.param = param;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}}
}在这里插入代码片
配置Vip断言
3. Filter(过滤器)
3.1 rewritePath(路径重写)
- 添加RewritePath过滤器,重写原先路径/readDb,在访问路径前面追加/api/order/readDb,不然网关无法直接访问/readDb;
- 添加AddReponseHeader过滤器,给响应头增加参数X-Response-ABC,值为123
3.2 默认过滤器filter:
增加默认过滤器default-filters, 参数Add-ReponseHeader=X-Reponse-Abc ,值为123 给所有服务的相应头中
3.3 全局过滤器GlobalFilter
package com.wemedia.gateway.filter;import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** 实现响应时间全局过滤器*/
@Component
@Slf4j
public class RTGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();String uri = request.getURI().toString();long start=System.currentTimeMillis();log.info("请求【{}】开始时间:{}",uri,start );//================以上是前置逻辑==============Mono<Void> filter = chain.filter(exchange).doFinally((result) -> {//================后置逻辑long end = System.currentTimeMillis();log.info("请求【{}】结束时间:{},耗时:{}ms", uri, end, end - start);});return filter;}@Overridepublic int getOrder() {return 0;}
}
过滤器filter 列表:
3.4 自定义过滤器工厂
关键源码分析
package org.springframework.cloud.gateway.filter.factory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;/*** @author Spencer Gibb*/
public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {@Overridepublic GatewayFilter apply(NameValueConfig config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return chain.filter(exchange).then(Mono.fromRunnable(() -> addHeader(exchange, config)));}@Overridepublic String toString() {return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this).append(config.getName(), config.getValue()).toString();}};}void addHeader(ServerWebExchange exchange, NameValueConfig config) {final String value = ServerWebExchangeUtils.expand(exchange, config.getValue());HttpHeaders headers = exchange.getResponse().getHeaders();// if response has been commited, no more response headers will bee added.if (!exchange.getResponse().isCommitted()) {headers.add(config.getName(), value);}}}
1.实现一次性token自定义过滤器工厂
package com.wemedia.gateway.filter;import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.UUID;/*** 实现一次性令牌的自定义过滤器工场*/
@Component
@Slf4j
public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {@Overridepublic GatewayFilter apply(NameValueConfig config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//每次响应之前,添加一个一次性令牌,支持uuid,jwt等格式return chain.filter(exchange).then(Mono.fromRunnable(()->{ServerHttpResponse response = exchange.getResponse();HttpHeaders headers = response.getHeaders();String value = config.getValue();if("uuid".equalsIgnoreCase(value)){value = UUID.randomUUID().toString();}if("jwt".equalsIgnoreCase(value)){value="";}headers.add(config.getName(),value);}));}};}
}
2.配置一次性token过滤器
3.访问api响应结果如下:
3.5 实现全局跨域
- 解决单机跨域方法:直接在每个controller上增加注解@CrossOrigin
- 在分布式系统上解决跨域问题,在gateway上统一处理跨域问题
配置全局跨域:
运行结果如下,增加了跨域处理: