Spring Cloud Gateway實踐體驗

總體介紹

Spring Cloud Gateway是Spring Cloud官方推出的第二代網關框架,從官網給出的對比分析結果來看,Gateway比Zuul的性能要好不少,並且功能也更加豐富。 如下是官方對比Gateway、Zuul、Linkered的分析結果,能夠看到Gateway是三個鍾效果性能最好的。 html


從官網給出的圖中能夠看到客戶端向Spring Cloud Gateway發出請求,而後網關轉發給代理的服務,而後在將服務響應的結果返回給客戶端。並且Gateway內部還有一系列的處理。java

請求進來後,會首先由Gateway Handler Mapping進行處理,這裏處理的過程當中用到 predicate,經過的請求才發送到Gateway web handler作進一步處理。而後又會通過一系列的過濾器。過濾器和Zuul的相似,也有"pre"、"post"分類。web

  • "pre"表明在請求前以前進行過濾處理
  • "post"表明在請求以後進行過濾處理 通常咱們在執行「pre」過濾器時,會進行鑑權、限流、日誌輸出等功能,以及請求頭的更改、協議的轉換;在請求以後執行「post」過濾器時,會對數據進行修改,好比響應頭、協議的轉換等。 整個過程當中有兩個比較重要的概念就是predicatefilter,filter比較好理解,下面來介紹一下predicate。

predicate

predicate在JDK8中的定義以下:正則表達式

Predicate 接受一個輸入參數,返回一個布爾值結果。該接口包含多種默認方法來將Predicate組合成其餘複雜的邏輯(好比:與,或,非)。能夠用於接口請求參數校驗、判斷新老數據是否有變化須要進行更新操做。add--與、or--或、negate--非spring

predicate這種輸入類型屬於Spring體系中的ServerWebExchange,它容許咱們匹配HTTP請求中的任何內容,好比請求頭或參數。並且Spring Cloud Gateway已經內置了不少Predict,這些Predict的源碼在org.springframework.cloud.gateway.handler.predicate包中。具體可參考以下:cookie

(圖片來源網絡)

環境準備

準備三個工程網絡

  • leon-eureka 註冊中心項目
  • leon-consumer 微服務項目
  • leon-gateway 網關項目

由於spring cloud gateway項目只支持spring boot 2.0以上版本,因此對spring cloud的版本也有要求。本文中使用的總體版本環境爲:app

  • spring boot :2.0.5
  • spring cloud : Finchley.SR1

(以上項目建立不在贅述,可參考案例工程),運行效果後,保證leon-gateway、leon-consumer都已經註冊在leon-eureka服務上。 框架

在leon-consumer中提供login接口,進行簡單的登陸驗證:ide

@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=leon八、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.一、172.17.153.二、172.17.153.三、。。。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中配置便可。這裏選擇幾個經常使用介紹,更多的配置可參考官方文檔: cloud.spring.io/spring-clou…

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 #設置獲取服務能夠經過小寫形式
複製代碼

配置完成後,重啓服務訪問:

相關文章
相關標籤/搜索