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上統一處理跨域問題
配置全局跨域:
運行結果如下,增加了跨域處理: