網關的主要作用
- 協議轉換,路由轉發
- 流量聚合,對流量進行監控,日志輸出
- 作為整個系統的前端工程,對流量進行控制,有限流的作用
- 作為系統的前端邊界,外部流量只能通過網關才能訪問系統
- 可以在網關層做權限的判斷
- 可以在網關層做緩存
gateway的請求過程
客戶端向Spring Cloud Gateway發出請求。 如果Gateway Handler Mapping確定請求與路由匹配(這個時候就用到predicate),則將其發送到Gateway web handler處理。 Gateway web handler處理請求時會經過一系列的過濾器鏈。 過濾器鏈被虛線划分的原因是過濾器鏈可以在發送代理請求之前或之后執行過濾邏輯。 先執行所有“pre”過濾器邏輯,然后進行代理請求。 在發出代理請求之后,收到代理服務的響應之后執行“post”過濾器邏輯。這跟zuul的處理過程很類似。在執行所有“pre”過濾器邏輯時,往往進行了鑒權、限流、日志輸出等功能,以及請求頭的更改、協議的轉換;轉發之后收到響應之后,會執行所有“post”過濾器的邏輯,在這里可以響應數據進行了修改,比如響應頭、協議的轉換等。
三個關鍵字
- Route: 請求到網關之后轉發到那個服務。使用ID標識,路由到哪里的Url地址。還有斷言和過濾器的集合。
- Predicate(斷言): 是java8的斷言函數,由開發人員去匹配當此請求的任何信息。根據請求信息路由到那個服務。
- Fiilter(過濾器): 請求和響應都可以在過濾器中都可以修改。
整合注冊中心
- maven
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- application.yml
server:
port: 1105
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:1109
gateway:
discovery:
locator:
# true 表明gateway開啟服務注冊和發現的功能,並且spring cloud gateway自動根據服務發現為每一個服務創建了一個router,這個router將以服務名開頭的請求路徑轉發到對應的服務。
#如果使用自定義的路由規則需要將這里設置為false 不然會有兩個路由
enabled: true
# 是將請求路徑上的服務名配置為小寫(因為服務注冊的時候,向注冊中心注冊時將服務名轉成大寫的了
lowerCaseServiceId: true
- 啟動配置
@SpringBootApplication
@EnableDiscoveryClient
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
}
- 啟動報錯
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method modifyRequestBodyGatewayFilterFactory in org.springframework.cloud.gateway.config.GatewayAutoConfiguration required a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' in your configuration.
原因:
依賴沖突,spring-cloud-starter-gateway與spring-boot-starter-web和spring-boot-starter-webflux依賴沖突
解決:
排除 spring-boot-starter-web和spring-boot-starter-webflux依賴
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
- 增加一個web-demo工程注冊到nacos就可以了
predicate(斷言)簡介
在上面的處理過程中,有一個重要的點就是講請求和路由進行匹配,這時候就需要用到predicate,它是決定了一個請求走哪一個路由。
在上圖中,有很多類型的Predicate,比如說時間類型的Predicated,當只有滿足特定時間要求的請求會進入到此predicate中,並交由router處理;cookie類型的,指定的cookie滿足正則匹配,才會進入此router;以及host、method、path、querparam、remoteaddr類型的predicate,每一種predicate都會對當前的客戶端請求進行判斷,是否滿足當前的要求,如果滿足則交給當前請求處理。如果有很多個Predicate,並且一個請求滿足多個Predicate,則按照配置的順序第一個生效。
//在web-demo寫一個請求
@GetMapping(value = "/a")
public String a() {
return "demo a";
}
//gateway網關配置
routes:
#id標簽配置的是router的id,每個router都需要一個唯一的id,
- id: api-a
uri: lb://web-demo
#當是get請求的時候跳轉到web-demo服務,會將url后面的請求路徑攜帶過去
predicates:
- Method=GET
//url訪問 http://localhost:1105/a
Fiilter
當我們有很多個服務時,客戶端請求各個服務的Api時,每個服務都需要做相同的事情,比如鑒權、限流、日志輸出等。對於這種重復操作的工作,在微服務的上一層加一個全局的權限控制、限流、日志輸出的Api Gatewat服務,然后再將請求轉發到具體的業務服務層。這個Api Gateway服務就是起到一個服務邊界的作用,外接的請求訪問系統,必須先通過網關層。
Spring Cloud Gateway同zuul類似,有“pre”和“post”兩種方式的filter。客戶端的請求先經過“pre”類型的filter,然后將請求轉發到具體的業務服務,收到業務服務的響應之后,再經過“post”類型的filter處理,最后返回響應到客戶端。
與zuul不同的是,filter除了分為“pre”和“post”兩種方式的filter外,在Spring Cloud Gateway中,filter從作用范圍可分為另外兩種,一種是針對於單個路由的gateway filter,它在配置文件中的寫法同predict類似;另外一種是針對於所有路由的global gateway filer。
- gateway提供的過濾器
//請求頭增加參數
//web-demo服務增加請求
@GetMapping(value = "/demo")
public String addRequestHeader(@RequestHeader(value = "X-test", required = false) String username) {
System.out.println("X-test:" + username);
return "demo SUCCESS";
}
//網關配置
routes:
#id標簽配置的是router的id,每個router都需要一個唯一的id,
- id: api-a
#lb就是負載均衡從注冊中心獲取WEB-DEMO
uri: lb://web-demo
predicates:
- Method=GET
filters:
#經過次過濾器的時候會在請求頭上增加X-test值為test
- AddRequestHeader=X-test, test
//url訪問 http://localhost:1105/demo
//訪問路徑重寫
//web-demo服務增加請求
@GetMapping(value = "/demo/path")
public String demoPath() {
System.out.println("demoPath SUCCESS");
return "demoPath SUCCESS";
}
//網關配置
routes:
#id標簽配置的是router的id,每個router都需要一個唯一的id,
- id: api-a
uri: lb://web-demo
predicates:
#根據路由斷言
- Path=/a/**
filters:
#路徑重寫 網關訪問的是/a/** 現在改寫為/demo/**調用WEB-DEMO服務
- RewritePath=/a/(?<segment>.*),/demoPath/$\{segment}
//url訪問 http://localhost:1105/a/path
自定義Fiilter
- 局部過濾器
使用GatewayFilter,將應用注冊到單個路由或一個分組的路由上
//過濾器配置
@Component
@Slf4j
public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {
private static final String KEY = "flag";
/**
* 封裝yml傳進來的參數
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(KEY);
}
public RequestTimeGatewayFilterFactory() {
super(Config.class);
}
/**
* 實現攔截邏輯
*/
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
//pre過濾器
exchange.getAttributes().put("requestTimeGateway", System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
//post過濾器
Long startTime = exchange.getAttribute("requestTimeGateway");
if (startTime != null) {
log.debug("請求路徑:{},耗時:{}ms",exchange.getRequest().getURI().getRawPath(),System.currentTimeMillis() - startTime);
if (config.isFlag()) {
log.debug("參數:{}",exchange.getRequest().getQueryParams());
}
}
})
);
};
}
/**
* 參數
*/
public static class Config {
private boolean flag;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
}
routes:
#id標簽配置的是router的id,每個router都需要一個唯一的id,
- id: api-a
uri: lb://web-demo
predicates:
#根據路由斷言
- Path=/a/**
filters:
#路徑重寫 網關訪問的是/a/** 現在改寫為/demo/**調用web-demo服務
- RewritePath=/a/(?<segment>.*),/demo/$\{segment}
#是否開啟請求參數的輸出
- RequestTime=false
- 全局過濾器
可用在鑒權,日志攔截等。無需在配置文件中配置,作用在所有路由上。最終通過GatewayFilterAdapter包裝成GatewayFilterChain可識別的過濾器,它為請求業務以及路由的URI轉換為真實業務服務的請求地址的核心過濾器,不需要配置,系統初始化時加載,並作用在每個路由上。
@Component
@Slf4j
public class TokenFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.debug("pre:token ");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.debug("post類型的過濾器");
}));
}
@Override
public int getOrder() {
return 0;
}
}