天天看點

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

前兩篇部落格已經介紹了十一種路由過濾器工廠:

  • ​​Spring Cloud Alibaba:Gateway之路由過濾器工廠(一)​​
  • ​​Spring Cloud Alibaba:Gateway之路由過濾器工廠(二)​​

随着​

​Gateway​

​​的不斷更新,​

​Gateway​

​提供的路由過濾器工廠種類一直在增加,目前已經有三十多種了(不同版本可能不一樣)。

搭建工程

一個父​

​module​

​​和兩個子​

​module​

​​(​

​server module​

​​提供服務,​

​gateway module​

​實作網關)。

父​

​module​

​​的​

​pom.xml​

​:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kaven</groupId>
    <artifactId>alibaba</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <description>Spring Cloud Alibaba</description>
    <modules>
        <module>gateway</module>
        <module>server</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <spring-cloud-version>Hoxton.SR9</spring-cloud-version>
        <spring-cloud-alibaba-version>2.2.6.RELEASE</spring-cloud-alibaba-version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud-version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba-version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>      

server module

​pom.xml​

​:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.kaven</groupId>
        <artifactId>alibaba</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>server</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>      

​application.yml​

​:

server:
  port: 8085      

接口定義:

package com.kaven.alibaba.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MessageController {

    @GetMapping("/message")
    public String getMessage() {
        return "hello kaven, this is spring cloud alibaba";
    }
}      

啟動類:

package com.kaven.alibaba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}      

gateway module

​pom.xml​

​:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.kaven</groupId>
        <artifactId>alibaba</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>gateway</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>
</project>      

​application.yml​

​:

server:
  port: 8086

spring:
  cloud:
    gateway:
      routes:
        - id: server
          uri: http://localhost:8085
          predicates:
            - Path=/message      

啟動類:

package com.kaven.alibaba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}      

StripPrefix

​StripPrefix​

​​路由過濾器工廠接受一個參數(​

​parts​

​​),​

​parts​

​參數表示在将請求發送到下遊服務之前要從請求中剝離的路徑數量。

源碼相關部分:

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

增加路由過濾器的配置(全寫):

predicates:
            - Path=/**
          filters:
            - name: StripPrefix
              args:
                parts: 2      

或(簡寫):

predicates:
            - Path=/**
          filters:
            - StripPrefix=2      

通路​

​http://127.0.0.1:8086/1/2/message​

​​,會被路由到​

​http://127.0.0.1:8085/message​

​。

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

Retry

​Retry​

​路由過濾器工廠接受以下參數:

  • ​retries​

    ​:應該嘗試的重試次數。
  • ​series​

    ​​:要重試的一系列狀态碼,用​

    ​HttpStatus.Series​

    ​表示。
  • ​statuses​

    ​​:應該重試的​

    ​HTTP​

    ​狀态碼,用​

    ​HttpStatus​

    ​表示。
  • ​methods​

    ​​:應該重試的​

    ​HTTP​

    ​方法,用​

    ​HttpMethod​

    ​表示。
  • ​exceptions​

    ​:應該重試的抛出異常清單。
  • Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)
  • ​backoff​

    ​​:為重試配置的指數退避,在退避間隔​

    ​firstBackoff * (factor ^ n)​

    ​之後執行重試,其中​

    ​n​

    ​是重試次數減一(即​

    ​n​

    ​的取值範圍為​

    ​[0,retries-1]​

    ​)。如果​

    ​maxBackoff​

    ​已配置,則應用的最大退避限制為​

    ​maxBackoff​

    ​。如果​

    ​basedOnPreviousValue​

    ​為真,則使用​

    ​prevBackoff * factor​

    ​計算退避(也不能超過​

    ​maxBackoff​

    ​)。
  • Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

修改路由過濾器的配置:

predicates:
            - Path=/message
          filters:
            - name: Retry
              args:
                retries: 5
                statuses: Internal_Server_Error
                methods: GET,POST
                backoff:
                  firstBackoff: 300ms
                  maxBackoff: 1300ms
                  factor: 2
                  basedOnPreviousValue: false      

​Internal_Server_Error​

​​對應​

​500​

​​狀态碼(詳見​

​HttpStatus​

​類)。

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

修改接口:

AtomicLong time = new AtomicLong(0);

    @GetMapping("/message")
    public String getMessage(HttpServletResponse response) {
        if(time.get() != 0) {
            System.out.println(System.currentTimeMillis() - time.get());
        }
        time.set(System.currentTimeMillis());
        response.setStatus(500);
        return "hello kaven, this is spring cloud alibaba";
    }      

通路​

​http://127.0.0.1:8086/message​

​​,在​

​server module​

​中會輸出如下所示的重試間隔毫秒數(接近網關設定的重試間隔時間):

327
623
1216
1308
1307      

是符合預期的。

​HTTP​

​系列狀态碼的枚舉類:

public enum Series {
        INFORMATIONAL(1),
        SUCCESSFUL(2),
        REDIRECTION(3),
        CLIENT_ERROR(4),
        SERVER_ERROR(5);
        ...
    }      

如下所示的配置可以對​

​5XX​

​系列狀态碼的響應進行請求重試。

predicates:
            - Path=/message
          filters:
            - name: Retry
              args:
                retries: 5
                series: SERVER_ERROR   # 預設值
                backoff:
                  firstBackoff: 300ms
                  maxBackoff: 1300ms
                  factor: 2
                  basedOnPreviousValue: true      

通路​

​http://127.0.0.1:8086/message​

​,結果是類似的。

修改路由過濾器的配置:

predicates:
            - Path=/message
          filters:
            - name: Retry
              args:
                retries: 5
                exceptions: java.util.concurrent.TimeoutException,java.lang.ClassNotFoundException
                backoff:
                  firstBackoff: 300ms
                  maxBackoff: 1300ms
                  factor: 2
                  basedOnPreviousValue: true      

修改接口:

AtomicLong time = new AtomicLong(0);

    @GetMapping("/message")
    public String getMessage(@RequestParam("isThrow") boolean isThrow) throws ClassNotFoundException {
        if(time.get() != 0) {
            System.out.println(System.currentTimeMillis() - time.get());
        }
        time.set(System.currentTimeMillis());
        if(isThrow) throw new ClassNotFoundException();
        return "hello kaven, this is spring cloud alibaba";
    }      

通路​

​http://127.0.0.1:8086/message​

​​,在​

​server module​

​中的輸出如下圖所示:

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)
Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

也是符合預期的。

RequestSize

當請求大小大于允許的限制時,​

​RequestSize​

​​路由過濾器工廠可以限制請求到達下遊服務。過濾器接受一個​

​maxSize​

​​參數,​

​maxSize​

​​是一個​

​DataSize​

​​類型,是以值可以被定義為一個數字,後跟一個可選的​

​DataUnit​

​​字尾,例如​

​KB​

​​或​

​MB​

​​,位元組的預設值為​

​B​

​。它是以位元組為機關定義的請求的允許大小限制。

BYTES("B", DataSize.ofBytes(1L)),
    KILOBYTES("KB", DataSize.ofKilobytes(1L)),
    MEGABYTES("MB", DataSize.ofMegabytes(1L)),
    GIGABYTES("GB", DataSize.ofGigabytes(1L)),
    TERABYTES("TB", DataSize.ofTerabytes(1L));      

源碼相關部分:

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

修改路由過濾器的配置:

predicates:
            - Path=/message
          filters:
            - name: RequestSize
              args:
                maxSize: 5KB      

請求負載限制為​

​5KB​

​。

修改接口:

@GetMapping("/message")
    public String getMessage() {
        return "hello kaven, this is spring cloud alibaba";
    }      

通路​

​http://127.0.0.1:8086/message​

​成功(請求負載沒有超過限制)。

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

通路​

​http://127.0.0.1:8086/message​

​失敗(請求負載超過限制)。

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

​PAYLOAD_TOO_LARGE​

​​和​

​REQUEST_ENTITY_TOO_LARGE​

​​(即将被抛棄)表示同一個狀态碼,而網關響應的是​

​PAYLOAD_TOO_LARGE​

​​(上面的源碼截圖),​

​Postman​

​​顯示的是​

​REQUEST_ENTITY_TOO_LARGE​

​​,可能是部落客的​

​Postman​

​版本的原因。

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)
Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

響應頭裡面的錯誤資訊顯示請求負載限制是​

​5.1KB​

​​,很顯然是換算不一緻的問題(過濾器使用​

​1000​

​​,而​

​DataSize​

​​使用​

​1024​

​):

private static String getReadableByteCount(long bytes) {
    int unit = 1000;
    if (bytes < unit) {
      return bytes + " B";
    }
    int exp = (int) (Math.log(bytes) / Math.log(unit));
    String pre = Character.toString(PREFIX.charAt(exp - 1));
    return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
  }      
private static final long BYTES_PER_KB = 1024L;
    private static final long BYTES_PER_MB = 1048576L;
    private static final long BYTES_PER_GB = 1073741824L;
    private static final long BYTES_PER_TB = 1099511627776L;      

SetRequestHostHeader

在某些情況下,可能需要覆寫請求的主機頭。在這種情況下,​

​SetRequestHostHeader​

​​路由過濾器工廠可以用指定的值替換現有的主機頭。過濾器接受一個​

​host​

​參數。

源碼相關部分:

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

由源碼可知,請求沒有主機頭時,也會給請求添加指定值的主機頭。

修改路由過濾器的配置:

predicates:
            - Path=/message
          filters:
            - name: SetRequestHostHeader
              args:
                host: kaven.top      

修改接口:

@GetMapping("/message")
    public String getMessage(HttpServletRequest httpServletRequest) {
        StringBuilder result = new StringBuilder("hello kaven, this is spring cloud alibaba\n");
        result.append(getKeyAndValue(httpServletRequest));
        return result.toString();
    }

    // 擷取header中key和value組成的StringBuilder
    private StringBuilder getKeyAndValue(HttpServletRequest httpServletRequest) {
        StringBuilder result = new StringBuilder();
        Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            String value = httpServletRequest.getHeader(key);
            result.append(key).append(" : ").append(value).append("\n");
        }
        return result;
    }      

通路​

​http://127.0.0.1:8086/message​

​,請求的主機頭就會被修改成指定的值。

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

請求沒有主機頭時,也會給請求添加指定值的主機頭。

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

注釋掉路由過濾器,請求的主機頭是本地的套接字。

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

ModifyRequestBody

使用​

​ModifyRequestBody​

​​過濾器在網關向下遊服務發送請求​

​Body​

​​之前修改請求​

​Body​

​​。此過濾器目前隻能通過使用​

​Java DSL​

​進行配置。

在​

​gateway module​

​​中添加一個​

​bean​

​:

@Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        String uri = "http://127.0.0.1:8085";
        return builder.routes()
                .route("server", r -> r.path("/message")
                        .filters(f -> f.modifyRequestBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE,
                                        (exchange, s) -> Mono.just(s.toUpperCase())))
                        .uri(uri))
                .build();
    }      

其實就是用代碼的形式來定義路由的處理流程,是以,之前介紹過的路由過濾器都可以通過這種形式添加到路由中。​

​server​

​​是路由的​

​id​

​​,​

​path​

​​方法是給路由添加​

​Path​

​​斷言,​

​modifyRequestBody​

​​方法是給路由添加​

​ModifyRequestBody​

​​路由過濾器,這些​

​lambda​

​​表達式應該容易看懂,​

​modifyRequestBody​

​​方法的第一個參數表示請求​

​Body​

​​輸入時需要轉換的類型,第二個參數表示請求​

​Body​

​​輸出時需要轉換的類型,而第三個參數就是請求發送到下遊服務時的新​

​Content-Type​

​​ 請求頭,最後一個參數就是用于修改請求​

​Body​

​​的方法(這裡就是将字元串全部變成大寫),​

​uri​

​方法是給路由指定路由比對成功後的請求轉發路徑。

  • ​​Spring Cloud Alibaba:Gateway網關 & 路由斷言工廠​​

修改接口:

@GetMapping("/message")
    public String getMessage(@RequestBody String message) {
        return message + "\nhello kaven, this is spring cloud alibaba\n";
    }      
Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)

ModifyResponseBody

使用​

​ModifyResponseBody​

​​過濾器在響應​

​Body​

​​發送回用戶端之前對其進行修改。此過濾器目前也隻能通過使用​

​Java DSL​

​進行配置。

在​

​gateway module​

​​中添加一個​

​bean​

​​(和之前那個​

​bean​

​類似):

@Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        String uri = "http://127.0.0.1:8085";
        return builder.routes()
                .route("server", r -> r.path("/message")
                        .filters(f -> f.modifyResponseBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE,
                                        (exchange, s) -> Mono.just(s.toUpperCase())))
                        .uri(uri))
                .build();
    }      

通過​

​modifyResponseBody​

​​方法給路由添加​

​ModifyResponseBody​

​過濾器。

Spring Cloud Alibaba:Gateway之路由過濾器工廠(三)