GateWay—網關介紹
所謂的API網關,就是指系統的統一入口,它封裝了應用程式的内部結構,為用戶端提供統一服務,一些與業務本身功能天關的公共邏輯可以在這裡實作,諸如認證、簽權、監控、路由轉發等等。添加上API網關之後,系統的架構圖變成了如下所示:
優點:
整體微服務結構圖,網關Gateway的位置
GateWay介紹
網關作為流量的入口,常用的功能包括路由轉發,權限校驗,限流等。
Spring Cloud Gateway 是Spring Cloud官方推出的第二代網關架構,定位于取代NetflexZuul 1.0。相比Zuul來說,Spring Clout Gateway)提供更優秀的性能,更強大的有功能
Spring Cloud Gateway是由WebFlux + Netty + Reactor實作的響應式的APl網關。它不能在傳統的 servlet容器中工作,也不能建構成war包.
Spring Clout Gateway旨在為微服務架構提供一種簡單且有效的API路由的管理方式,并基于Filter的方式提供網關的基本功能,例如說安全認證、監控、限流等等。
功能特征
- 基于Spring Framework 5,Project Reactor和 Spring Boot 2.0進行建構;
- 動态路由:能夠比對任何請求屬性;
- 支援路徑重寫;
- 內建 Spring Cloud服務發現功能(Nacos、Eruka) ;
- 可內建流控降級功能(Sentinel、Hystrix) ;
- 可以對路由指定易于編寫的 Predicate (斷言)和Filter(過濾器);
核心概念:
- 路由
路由是網關中最基礎的部分,路由資訊包括一個ID、一個目的URI、一組斷言工廠、一組Fiter組成。如果斷言為真,則說明請求的URL和配置的路由比對.
- 斷言(predicates)
Java8中的斷言函數,SprigCloud Gateway中的斷言函數類型是Sping5 o架構中的SererWebExchange。斷言函數允許開發者去定義比對Http request中的任何資訊,比如請求頭和參數等。
- 過濾器(Fllter)
SpringCloud Gateway中的filter分為Gateway Filler和Global Filter。Filter可以對請求和響應進行處理。
GateWay—初體驗
1.建立新的子項目gateway 引入依賴
<!-- gateway的依賴 springcloud開發 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2.編寫配置檔案
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
#gateway的配置
gateway:
#路由配置
routes:
- id: order_route #路由的唯一辨別,路由到order
uri: http://localhost:8020 #需要轉發的位址
#斷言規則 用于路由規則的比對
predicates:
- Path=/order-serv/**
# 請求 http://localhost:8088/order-serv/order/add路由到
# http://localhost:8020/order-serv/order/add
filters:
- StripPrefix=1 #轉發之前去掉第一層路徑
# http://localhost:8020/order/add
3.主啟動類啟動
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
通路之前啟動 order-nacos 和 stock-nacos子產品
order-nacos子產品 controller代碼
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
RestTemplate restTemplate;
@RequestMapping("/add")
public String add(){
System.out.println("下單成功!");
String msg = restTemplate.getForObject("http://stock-service/stock/reduct", String.class);
return "hello world"+msg;
}
}
stock-nacos子產品 controller代碼
@RestController
@RequestMapping("/stock")
public class StockController {
@Value("${server.port}")
String port;
@RequestMapping("/reduct")
public String reduct(){
System.out.println("扣減庫存");
return "扣減庫存"+port;
}
}
三個子產品全部啟動
測試通路 http://localhost:8088/order-serv/order/add
頁面顯示 hello world扣減庫存8021
GateWay整合nacos
現在在配置檔案中寫死了轉發路徑的位址,前面我們已經分析過位址寫死帶來的問題,接下來我們從注冊中心擷取此位址。
內建Nacos
1.再引入依賴
<!-- nacos服務注冊發現 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.編寫yml配置檔案
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
#gateway的配置
gateway:
#路由配置
routes:
- id: order_route #路由的唯一辨別,路由到order
uri: lb://order-service #需要轉發的位址 lb 使用nacos本地負載均衡政策 order-service 服務名
predicates:
- Path=/order-serv/**
filters:
- StripPrefix=1
#配置Nacos
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
測試通路 http://localhost:8088/order-serv/order/add
頁面顯示 hello world扣減庫存8021
簡寫形式
- 比較少用
- 自己去配置斷言和過濾器比較好
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
#gateway的配置
gateway:
discovery:
locator:
enabled: true #是否啟動自動識别nacos服務
#路由配置
#配置Nacos
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
測試通路 http://localhost:8088/order-service/order/add 用的是服務名
頁面顯示 hello world扣減庫存8021
GateWay—内置路由斷言工廠
作用 : 當請求gateway的時候,使用斷言對請求進行比對,如果比對成功就路由轉發,如果比對失敗就傳回404
類型 内置 、自定義
- 基于Datetime類型的斷言工廠
此類型的斷言根據時間做判斷,主要有三個:
AfterRoutePredicateFactory:接收一個日期參數,判斷請求日期是否晚于指定日期
BeforeRoutePredicateFactory:接收一個日期參數,判斷請求日期是否早于指定日期
BetweenRoutePredicateFactory:接收兩個日期參數,判斷請求日期是否在指定時間段内
predicates:
- Path=/order-serv/**
- After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
- 基于遠端位址的斷言工廠
RemoteAddrRoutePredicateFactory:接收一個IP位址段,判斷請求主機位址是否在位址段中
- RemoteAddr=192.168.1.1/24
- 基于Cookie的斷言工廠
CookieRoutePredicateFactory: 接收兩個參數,cookie名字和一個正規表達式。判斷請求cookie是否具有給定名稱且值與正規表達式比對。
- Cookie=chocolate,ch.
- 基于Header的斷言工廠
HeaderRoutePredicateFactory:接收兩個參數,标題名稱和正規表達式。判斷請求Header是否員有給定名稱且值與正規表達式比對.
- Header=X-Request-Id,\d+
- 基于Host的斷言工廠
HostRoutePredicateFactory:接收一個參數,主機名模式。判斷請求的Host是否滿足比對規則
- Host=**.testhost.org
- 基于Method請求方法的斷言工廠
MethodRoutePredicateFactory:接收一個參數,判斷請求類型是否跟指定的類型比對。
- Method=GET
- 基于Path請求路徑的斷言工廠
PathRoutePredicateFactory:接收一個參數,判斷請求的URI部分是否滿足路徑規則,
- 基于Query請求參數的斷言工廠
- Query=name,xushu|zhuge
#要有name參數 而且要等于xushu或者zhuge
GateWay—自定義路由斷言工廠
自定義路由斷言工廠需要繼承
AbstractRoutePredicateFactory
類,重寫 apply 方法的邏輯。在apply方法中可以通過 exchange.getRequest()拿到ServerhttptRequest對象,進而可以擷取到請求的參數、請求方式、請求頭等資訊.
1.必須spring元件 bean
2.類必須加上
RoutePredicateFactory
作為結尾
3.必須繼承
AbstractRoutePredicateFactory
4.必須聲明靜态内部類 聲明屬性來接受 配置檔案中的對應的斷言的資訊
5.需要結合
shortcutFieldOrder
進行綁定
6.通過apply進行邏輯判斷 true就是比對成功 false比對失敗
寫個自定義參數叫CheckAuth 值必須是xushu才可以通路
package com.tian.predicates;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(CheckAuthRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}
public Predicate<ServerWebExchange> apply(CheckAuthRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
if(config.getName().equals("xushu")){
return true;
}
return false;
}
};
}
//用來接收配置檔案中 斷言的資訊
@Validated
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
配置檔案
predicates:
#- Path=/order-serv/**
- Path=/order/**
- CheckAuth=xushu
#- CheckAuth=xushu2
通路 http://localhost:8088/order/add xushu就可以正确通路,xushu2頁面就會報404
GateWay—局部(内置、自定義)過濾器
添加請求頭
#gateway子產品配置檔案
spring:
cloud:
gateway:
routes:
- id: order_route
uri: http://localhost:8020
filters:
- AddRequestHeader=X-Request-color,red #添加請求頭
order-nacos子產品controller
@RequestMapping("/header")
public String header(@RequestHeader("X-Request-color") String color){
return color;
通路 http://localhost:8088/order/header 頁面顯示 red
自定義過濾器工廠
- 和斷言工廠差不多
配置檔案
filters:
- CheckAuth=fox,男
通路輸出CheckAuthGatewayFilterFactory===fox: 男
GateWay—全局過濾器
局部過濾器和全同過濾器差別:
局部:局部針對某個路由,需要在路由中進行配置
全局:針對所有路由請求,你定義就會投入使用
自定義全局過濾器
@Component
public class LogFilter implements GlobalFilter {
Logger log= LoggerFactory.getLogger(this.getClass());
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info(exchange.getRequest().getPath().value());
return chain.filter(exchange);
}
}
GateWay—請求日記記錄&跨域處理
要啟用Reactor Netty 通路日志,請設定
-Dreactor.netty.http.server.accessLogEnabled=true.
重新開機 控制台就會列印輸出日志
它必須是Java系統屬性,而不是 Spring Boot屬性。
跨域問題
配置檔案application.yml
#跨域配置
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': #允許跨域通路的資源
allowedOrigins: "https://docs.spring.io" #跨域允許來源
allowedMethods:
- GET
- POST
配置類配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter(){
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*"); //允許的method
config.addAllowedOrigin("*"); //允許的來源
config.addAllowedHeader("*"); //允許的請求頭參數
// 運作通路的資源
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**",config);
return new CorsWebFilter(source);
}
}
GateWay—整合Sentinel流控降級
api-gateway子產品
1.添加依賴
<!-- sentinel整合gateway -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!-- sentinel的依賴 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.配置檔案
spring:
cloud:
#配置sentinel
sentinel:
transport:
dashboard: 127.0.0.1:8888
3.啟動sentinel 控制台配置規則
4.通路
http://localhost:8088/order/add
現象 前兩次正常頁面顯示 第三次頁面顯示
Blocked by Sentinel: ParamFlowException
GateWay—整合Sentinel流控降級詳細配置
API管理
降級
處理方法(在降級規則那配置)
自定義異常
- 讓頁面顯示自定義異常資訊
package com.tian.config;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.HashMap;
@Configuration
public class GatewayConfig {
@PostConstruct
public void init(){
BlockRequestHandler blockRequestHandler=new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
HashMap<String, String> map = new HashMap<>();
map.put("code",HttpStatus.TOO_MANY_REQUESTS.toString());
map.put("message","限流了");
//自定義異常處理
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
或 yml配置
spring:
cloud:
sentinel:
scg:
fallback:
mode: response
response-body: "{code:'',message:''}"