整體介紹
Spring Cloud Gateway是Spring Cloud官方推出的第二代網關框架,從官網給出的對比分析結果來看,Gateway比Zuul的性能要好很多,而且功能也更加豐富。
以下是官方對比Gateway、Zuul、Linkered的分析結果,可以看到Gateway是三個鐘效果性能最好的。
[圖片上傳失敗...(image-e737d0-1560949508627)]
從官網給出的圖中可以看到客戶端向Spring Cloud Gateway發出請求,然后網關轉發給代理的服務,然后在將服務響應的結果返回給客戶端。而且Gateway內部還有一系列的處理。
請求進來后,會首先由Gateway Handler Mapping進行處理,這里處理的過程中用到 predicate,通過的請求才發送到Gateway web handler做進一步處理。然后又會經過一系列的過濾器。過濾器和Zuul的類似,也有"pre"、"post"分類。
- "pre"代表在請求前之前進行過濾處理
- "post"代表在請求之后進行過濾處理
一般我們在執行“pre”過濾器時,會進行鑒權、限流、日志輸出等功能,以及請求頭的更改、協議的轉換;在請求之后執行“post”過濾器時,會對數據進行修改,比如響應頭、協議的轉換等。
整個過程中有兩個比較重要的概念就是predicate 和 filter,filter比較好理解,下面來介紹一下predicate。
predicate
predicate在JDK8中的定義如下:
Predicate<T> 接受一個輸入參數,返回一個布爾值結果。該接口包含多種默認方法來將Predicate組合成其他復雜的邏輯(比如:與,或,非)。可以用于接口請求參數校驗、判斷新老數據是否有變化需要進行更新操作。add--與、or--或、negate--非
predicate這種輸入類型屬于Spring體系中的ServerWebExchange,它允許我們匹配HTTP請求中的任何內容,比如請求頭或參數。而且Spring Cloud Gateway已經內置了很多Predict,這些Predict的源碼在org.springframework.cloud.gateway.handler.predicate包中。具體可參考如下:
(圖片來源網絡)
環境準備
準備三個工程
- leon-eureka 注冊中心項目
- leon-consumer 微服務項目
- leon-gateway 網關項目
因為spring cloud gateway項目只支持spring boot 2.0以上版本,所以對spring cloud的版本也有要求。本文中使用的整體版本環境為:
- spring boot :2.0.5
- spring cloud : Finchley.SR1
(以上項目創建不在贅述,可參考案例工程),運行效果后,保證leon-gateway、leon-consumer都已經注冊在leon-eureka服務上。
在leon-consumer中提供login接口,進行簡單的登錄驗證:
@GetMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
if ("leon".equals(username) && "888".equals(password)) {
return "登錄成功";
}
return "登錄失敗";
}
注意問題
在此版本上,添加注冊中心客戶端以來的包和版本
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
另外在leon-gateway中不能添加 web 依賴包,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
添加后啟動會報錯:
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 Gateway 是使用 netty+webflux實現,webflux與web是沖突的。
接下來我們在項目中實際使用gateway,通過gateway轉發路由,將請求轉發到leon-consumer中。
Route Predicate
Predicate的分類比較多,接下來在項目總一一使用。
3.1 After Route Predicate Factory
接收一個日期類型參數,在這個日期之后可以通過。
在配置文件(application.yml)中添加
spring:
application:
name: leon-gateway
cloud:
gateway:
routes:
- id: dev
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- After=2020-05-20T17:42:47.789-07:00[America/Denver]
- id 為路由route的唯一標識。(這次測試不添加也可以,后續測試多個route是否必須添加)
- uri 為轉發請求的地址
- predicates 為請求謂詞,此處是在指定時間之后
現在時間為2019-05,所以是在指定時間之前,此時啟動服務,我們訪問http://localhost:8085/
可以看到轉發請求后出現404錯誤,
然后我們修改時間為2018
spring:
application:
name: leon-gateway
cloud:
gateway:
routes:
- id: dev
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- After=2018-05-20T17:42:47.789-07:00[America/Denver]
重啟服務后再次訪問:http://localhost:8085/
可以看到已經轉發到leon-consumer的login接口,并能夠收到正確返回信息
3.2 Before Route Predicate Factory
在指定時間之前才能通過轉發,具體效果類似,不在贅述
spring:
cloud:
gateway:
routes:
- id: before_route
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- Before=2019-01-20T17:42:47.789-07:00[America/Denver]
3.3 Between Route Predicate Factory
在指定范圍時間之內的請求才能通過轉發。接收兩個日期參數,并且參數的形式,必須是較早的日期在前,較晚的日期在后,具體如下
spring:
cloud:
gateway:
routes:
- id: between_route
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
3.4 Cookie Route Predicate Factory
cookiel謂詞工廠接收兩個參數,Cookie名稱和值(也可以是正則表達式)。請求中必須包含給定名稱的cookie,并且cookie值要符合給定的值(正則規則)才能通過轉發。
通過postman添加cookie進行測試如下。
如果參數值不對同樣會報404問題。
3.5 Header Route Predicate Factory
同樣需要2個參數,一個是header名稱,另外一個header值(可以為正則表達式),匹配通過后才轉發
- id: header_route
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- Header=X-Request-Id, \d+
在Header中必須要有X-Request-Id名稱的參數,并且值要滿足正則規則,必須為數字
3.6 Host Route Predicate Factory
只接收一個參數,就是host name,可以使用"."來進行匹配,此參數在head中添加
- id: host_route
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- Host=**.leon.cn
3.7 Method Route Predicate Factory
接收一個參數,代表請求的類型。
- id: method_route
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- Method=GET
此時所有的GET請求都會轉發,如果是POST請求就會404
3.8 Path Route Predicate Factory
接收一個參數,就是路徑地址(可以為正則表達式)
- id: path_route
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- Path=/leon/{segment}
所有的請求路徑滿足/leon/{segment}的請求將會匹配并被路由,比如/leon/1 、/leon/bar的請求,將會命中匹配,并成功轉發。
3.9 Query Route Predicate Factory
需要兩個參數,一個是參數名,一個是參數值(正則表達式)。
- id: query_route
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- Query=name, leon.
上面配置了請求中含有參數name,并且name的值匹配leon.,則進行轉發
比如請求參數為name=leon8、name=leono、name=leon靜都可以匹配
也可以只加一個參數,代表只驗證參數名稱,不驗證參數值。只要包含指定名稱的參數即可通過轉發。
3.10 RemoteAddr Route Predicate Factory
接收一個字符串參數,此字符串代表地址列表(最少1個),只有是以上要求的IP地址發來的請求才通過轉發
- id: remoteaddr_route
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- RemoteAddr=172.17.153.1/24
以上配置表明只有IP地址為172.17.153.1、172.17.153.2、172.17.153.3、。。。172.17.153.24可以通過轉發。
3.11 組合
可以同時配置多個predicates,如果一個請求滿足多個路由的謂詞條件時,請求只會被首個成功匹配的路由轉發。
我們在leon-consumer項目中添加接口方法
@GetMapping("/info")
public String info() {
return "獲取信息成功";
}
然后在leon-gatewa重視添加兩個
spring:
application:
name: leon-gateway
cloud:
gateway:
routes:
- id: header_route
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- Header=X-Request-Id, \d+
- id: query_route
uri: http://localhost:8081/info
predicates:
- Query=name, leon.
這里我們同時添加了header_route、query_route,然后我們在postman中發送請求,同時滿足兩個條件:
結果可見是按照header_route來進行轉發。
我們把兩個條件順序互換:
cloud:
gateway:
routes:
- id: query_route
uri: http://localhost:8081/info
predicates:
- Query=name, leon.
- id: header_route
uri: http://localhost:8081/login?username=leon&password=888
predicates:
- Header=X-Request-Id, \d+
重啟服務,然后再次訪問,相同的請求這次被轉發到info接口。
當然如果設置多個路由謂詞,第一個滿足優先轉發,如果第一個不滿足會繼續往下判斷,遇到滿足的進行轉發,我們把請求條件改成不合適,則準發第二個接口。
以上的配置類似"or"的條件,我們還可以配置組合使用,達到"and"的效果,要同時滿足才能進行轉發。
cloud:
gateway:
routes:
- id: zuhe_route
uri: http://localhost:8081/info
predicates:
- Query=name, leon.
- Header=X-Request-Id, \d+
此時如果有一個參數設置不對,那么就不會進行轉發
Filter
Predict決定了請求由哪一個路由處理,在路由處理之前,需要經過“pre”類型的過濾器處理,處理返回響應之后,可以由“post”類型的過濾器處理。
在Spring Cloud Gateway中,filter從作用范圍可分為另外兩種,一種是針對于單個路由的gateway filter,它在配置文件中的寫法同predict類似;另外一種是針對于所有路由的global gateway filer,全局的filter。現在來分別介紹。
GatewayFilter
GatewayFilter的使用同Predicate類似,都是在配置文件application.yml中配置即可。這里選擇幾個常用介紹,更多的配置可參考官方文檔: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.0.0.RELEASE/single/spring-cloud-gateway.html
4.1 AddRequestHeader GatewayFilter Factory
在網關中添加Filter
cloud:
gateway:
routes:
- id: zuhe_route
uri: http://localhost:8081/info
predicates:
- Query=name, leon.
- Header=X-Request-Id, \d+
filters:
- AddRequestHeader=X-Request-Foo, Bar
這里我們添加了AddRequestHeader過濾器,在請求轉發后給HEADER中添加參數X-Request-Foo,值為:Bar。
改造leon-consuemr工程
@GetMapping("/info")
public String info(HttpServletRequest request) {
String header = request.getHeader("X-Request-Foo");
return "獲取信息成功:" + header;
}
可以看到,我們在路由轉發后的處理方法中獲取相關參數。然后發送請求,這個請求我們只滿足路由轉發條件,并沒有添加X-Request-Foo的HEADER參數,但是我們在轉發后服務處理中是可以獲取到的。
其他更多的設置暫不涉及,請參考官方文檔。
Global Filters
全局過濾器,不需要在配置文件中配置,作用在所有的路由上。Gatewqy內置的GlobalFilter如下:
(圖片來源網絡)
如果想要自己實現GlobalFilter也可以,實現GlobalFilter和Ordered接口即可。
public class GlobalFilter implements org.springframework.cloud.gateway.filter.GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("token");
if (token == null || token.isEmpty()) {
System.out.println("token為空");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
在啟動類中添加配置,將配置類加入容器管理。
@Bean
public GlobalFilter globalFilter() {
return new GlobalFilter();
}
此時發情請求,可以發現如果HEADER中沒有token參數,則無法通過轉發。
項目綜合使用
使用過Zuul的同學都了解,在Zuul中可以配置統一前置路由,比如現在我們想把所有路徑中包含/user的都轉發到leon-consumer工程去處理。
在以上的項目中繼續改造,在leon-consumer項目中添加前綴映射:
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
if ("leon".equals(username) && "888".equals(password)) {
return "登錄成功";
}
return "登錄失敗";
}
@GetMapping("/info")
public String info(HttpServletRequest request) {
String header = request.getHeader("X-Request-Foo");
return "獲取信息成功:" + header;
}
}
在leon-gateway中添加配置:
cloud:
gateway:
routes:
- id: path_route
uri: lb://leon-consumer #服務名,注意一定要以lb://開頭
predicates:
- Path=/user/{segment}
filters:
- AddRequestHeader=X-Request-Foo, Bar
discovery:
locator:
enabled: true #設置可以通過服務名獲取服務
lower-case-service-id: true #設置獲取服務可以通過小寫形式
在前面我們的uri都是直接寫好的具體的地址,現在的服務已經注冊到Eureka上,我們也可以通過服務名稱找到具體的服務。
添加配置
discovery:
locator:
enabled: true #設置可以通過服務名獲取服務
通過lb:指定即可。默認配置的名稱必須是全大寫,想要通過小寫識別,可添加配置
discovery:
locator:
lower-case-service-id: true #設置獲取服務可以通過小寫形式
配置完成后,重啟服務訪問: