Gateway介紹
-
Gateway是在Spring生態系統之上構建的API網關服務,基於Spring 5, Spring Boot 2和Project Reactor等技術。
Gateway旨在提供一 種簡單而有效的方式來對API進行路由,以及提供一 些強大的過濾器功能, 例如:熔斷、限流、重試等 -
Spring Cloud Gateway使用的Webflux中的reactor-netty響應式編程組件,底層使用了Netty通訊框架
-
主要功能有:反向代理,鑒權,流量控制,熔斷,日志監控
-
Spring Cloud Gateway具有如下特性:
- 基於Spring Framework 5, Project Relactor和Spring Boot 2.0進行構建;
- 動態路由:能夠匹配任何請求屬性;
- 可以對路由指定Predicate (斷言)和Filter (過濾器) ;
- 集成Hystrix的斷路器功能;
- 集成Spring Cloud服務發現功能;
- 易於編寫的Predicate (斷言)和Filter (過濾器) ;
- 請求限流功能; .
- 支持路徑重寫。
Gateway三個核心概念
-
Route路由
路由。路由是網關最基礎的部分,路由信息有一個ID、一個目的URL、一組斷言和一組Filter組成。如果斷言路由為真,則說明請求的URL和配置匹配 -
Predicate斷言
參考的是Java8中的斷言函數。Spring Cloud Gateway中的斷言函數輸入類型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的斷言函數允許開發者去定義匹配來自於http request中的任何信息,比如請求頭和參數等。如果請求與斷言相匹配則進行該路由。 -
Filter過濾
一個標准的Spring webFilter。Spring cloud gateway中的filter分為兩種類型的Filter,分別是Gateway Filter(路由過濾)和Global Filter(全局過濾)。過濾器Filter將會對請求和響應進行修改處理 -

Gateway執行流程
*
- 客戶端向Spring Cloud Gateway發出請求。然后在Gateway Handler Mapping中找到與請求相匹配的路由,將其發送到Gateway
Web Handler。Handler再通過指定的過濾器鏈來將請求發送到我們實際的服務執行業務邏輯,然后返回。
過濾器之間用虛線分開是因為過濾器可能會在發送代理請求之前( "pre" )或之后( "post" )執行業務邏輯。| I
Filter在 "pre" 類型的過濾器可以做參數校驗、權限校驗、流量監控、日志輸出、協議轉換等,
在"post"類型的過濾器中可以做響應內容、響應頭的修改,日志的輸出,流量監控等有着非常重要的作用。
Gateway使用
- 項目pom文件
<dependencies>
<!--新增gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 服務注冊無所謂,Eurkea,Nacos都可以 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 熱部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 項目yml文件
server:
port: 9524 #端口號
spring:
application:
name: cloud-gateway # 微服務注冊名稱
cloud:
gateway: #Gayeway配置
routes:
- id: payment_routh #路由的ID,沒有固定規則但要求唯一,建議配合服務名
uri: http://localhost:8007 #匹配后提供服務的路由地址
predicates:
- Path=/payment/get/** #斷言,路徑相匹配的進行路由
- id: payment_routh2
uri: http://localhost:8007
predicates:
- Path=/payment/lb/** #斷言,路徑相匹配的進行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7004.com:7004/eureka
- 項目主啟動類
package com.demo.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run( GateWayMain9527.class,args);
}
}
-
通過9524端口即可訪問8007端口下的微服務
-
還有一種方式是用代碼寫配置類
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
return routes.route("path_route1", r -> r.path("/guonei")
.uri("https://news.baidu.com/guonei"))
.build();
}
- 配置動態路由
server:
port: 9524
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #開啟從注冊中心動態創建路由的功能,利用微服務名進行路由
routes:
- id: payment_routh #路由的ID,沒有固定規則但要求唯一,建議配合服務名
#uri: http://localhost:8001 #匹配后提供服務的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #斷言,路徑相匹配的進行路由
- id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服務的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #斷言,路徑相匹配的進行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
Predicate的使用
- Spring Cloud Gateway將路由匹配作為Spring WebFlux HandlerMapping基礎架構的一部分。
Spring Cloud Gateway包括許多內置的Route Predicate工廠。所有這些Predicate都與HTTP請求的不同屬性匹配。多個Route
Predicate工廠可以進行組合Spring Cloud Gateway創建Route對象時,使用RoutePredicateFactory創建Predicate對象,Predicate對象可以賦值給Route。Gateway包含許多內置的Route Predicate Factories。所有這些謂詞都匹配HTTP請求的不同屬性。多種謂詞工廠可以組合,並通過邏輯and。 - 常用的斷言
- After Route Predicate
// java獲取時間格式
ZoneDateTime time = ZoneDateTime.now();//默認時區
ZoneDateTime time = ZoneDateTime.now(ZoneId.of("Amercia/New_York"));//獲取指定時區的時間
# 在配置的時間節點后匹配路由
- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Before Route Predicate
# 在配置的時間前和后匹配該路由
- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Between Route Predicate
# 在配置的時間段之間匹配路由
- Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Cookie Route Predicate
#請求必須帶上配置的cookie信息
-Cookie=username,demo
- Header Route Predicate
Header=X- Request-Id, \d+ # 請求頭要有X-Request - Id屬性並且值為整數的正則表達式
- Host Route Predicate
#請求主機地址,
- Host=**.demo.com
- Method Route Predicate
- Method=GET
-
Path Route Predicate
-
Query Route Predicate
- Query=username, \d+ #請求的url要有參數名稱並且是正整數才能路由
Filter過濾器的使用
package com.demo.springcloud.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.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
@Component
@Slf4j
// 需要實現這兩個接口
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("*********come in MyLogGateWayFilter: "+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("username");
if(StringUtils.isEmpty(username)){
log.info("*****用戶名為Null 非法用戶,(┬_┬)");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//給人家一個回應
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
內置過濾器

- 使用·
routes:
- id: service-edu
uri: lb://service-edu
predicates:
- Path=/user/**, /*/edu/**
filters:
- SetStatus=250 # 修改返回狀態碼
解決跨域問題
//在config包下
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
解決鑒權問題
package com.atguigu.guli.infrastructure.apigateway.filter;
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
//谷粒學院api接口,校驗用戶必須登錄
AntPathMatcher antPathMatcher = new AntPathMatcher();
if(antPathMatcher.match("/api/**/auth/**", path)) {
List<String> tokenList = request.getHeaders().get("token");
//沒有token
if(null == tokenList) {
ServerHttpResponse response = exchange.getResponse();
return out(response);
}
//token校驗失敗
Boolean isCheck = JwtUtils.checkJwtTToken(tokenList.get(0));
if(!isCheck) {
ServerHttpResponse response = exchange.getResponse();
return out(response);
}
}
//放行
return chain.filter(exchange);
}
//定義當前過濾器的優先級,值越小,優先級越高
@Override
public int getOrder() {
return 0;
}
private Mono<Void> out(ServerHttpResponse response) {
JsonObject message = new JsonObject();
message.addProperty("success", false);
message.addProperty("code", 28004);
message.addProperty("data", "");
message.addProperty("message", "鑒權失敗");
byte[] bytes = message.toString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
//指定編碼,否則在瀏覽器中會中文亂碼
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
//輸出http響應
return response.writeWith(Mono.just(buffer));
}
}
