前兩篇部落格已經介紹了十一種路由過濾器工廠:
- 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
參數表示在将請求發送到下遊服務之前要從請求中剝離的路徑數量。
源碼相關部分:
增加路由過濾器的配置(全寫):
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
。
Retry
Retry
路由過濾器工廠接受以下參數:
-
:應該嘗試的重試次數。retries
-
:要重試的一系列狀态碼,用series
表示。HttpStatus.Series
-
:應該重試的statuses
狀态碼,用HTTP
表示。HttpStatus
-
:應該重試的methods
方法,用HTTP
表示。HttpMethod
-
:應該重試的抛出異常清單。exceptions
-
:為重試配置的指數退避,在退避間隔backoff
之後執行重試,其中firstBackoff * (factor ^ n)
是重試次數減一(即n
的取值範圍為n
)。如果[0,retries-1]
已配置,則應用的最大退避限制為maxBackoff
。如果maxBackoff
為真,則使用basedOnPreviousValue
計算退避(也不能超過prevBackoff * factor
)。maxBackoff
修改路由過濾器的配置:
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
類)。
修改接口:
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
中的輸出如下圖所示:
也是符合預期的。
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));
源碼相關部分:
修改路由過濾器的配置:
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
成功(請求負載沒有超過限制)。
通路
http://127.0.0.1:8086/message
失敗(請求負載超過限制)。
PAYLOAD_TOO_LARGE
和
REQUEST_ENTITY_TOO_LARGE
(即将被抛棄)表示同一個狀态碼,而網關響應的是
PAYLOAD_TOO_LARGE
(上面的源碼截圖),
Postman
顯示的是
REQUEST_ENTITY_TOO_LARGE
,可能是部落客的
Postman
版本的原因。
響應頭裡面的錯誤資訊顯示請求負載限制是
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
參數。
源碼相關部分:
由源碼可知,請求沒有主機頭時,也會給請求添加指定值的主機頭。
修改路由過濾器的配置:
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
,請求的主機頭就會被修改成指定的值。
請求沒有主機頭時,也會給請求添加指定值的主機頭。
注釋掉路由過濾器,請求的主機頭是本地的套接字。
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";
}
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
過濾器。