Skip to content

Commit 4cad353

Browse files
authored
feat(adapter): new SpringCloudGateway v6 adapter for supporting the SpringCloud 2025 version (#3542)
* feat: support Spring Cloud Alibaba 2025 * Add maven property to fix ci failure * doc: Update README.md
1 parent ca59645 commit 4cad353

20 files changed

+1277
-0
lines changed

sentinel-adapter/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<module>sentinel-spring-webflux-adapter</module>
2828
<module>sentinel-api-gateway-adapter-common</module>
2929
<module>sentinel-spring-cloud-gateway-adapter</module>
30+
<module>sentinel-spring-cloud-gateway-v6x-adapter</module>
3031
<module>sentinel-web-adapter-common</module>
3132
<module>sentinel-spring-webmvc-adapter</module>
3233
<module>sentinel-spring-webmvc-v6x-adapter</module>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Sentinel Spring Cloud Gateway Adapter
2+
3+
Sentinel provides integration module with Spring Cloud Gateway.
4+
The integration module is based on the Sentinel Reactor Adapter.
5+
> This module is quite similar to sentinel-spring-cloud-gateway-adapter and The difference is that this module has made some adaptations for webflux 6.x.
6+
> The usage is consistent with sentinel-spring-cloud-gateway-adapter, with the difference being that the maven dependency is different.
7+
8+
Add the following dependency in `pom.xml` (if you are using Maven):
9+
10+
```xml
11+
<dependency>
12+
<groupId>com.alibaba.csp</groupId>
13+
<artifactId>sentinel-spring-cloud-gateway-v6x-adapter</artifactId>
14+
<version>x.y.z</version>
15+
</dependency>
16+
```
17+
18+
Then you only need to inject the corresponding `SentinelGatewayFilter` and `SentinelGatewayBlockExceptionHandler` instance
19+
in Spring configuration. For example:
20+
21+
```java
22+
@Configuration
23+
public class GatewayConfiguration {
24+
25+
private final List<ViewResolver> viewResolvers;
26+
private final ServerCodecConfigurer serverCodecConfigurer;
27+
28+
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
29+
ServerCodecConfigurer serverCodecConfigurer) {
30+
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
31+
this.serverCodecConfigurer = serverCodecConfigurer;
32+
}
33+
34+
@Bean
35+
@Order(-1)
36+
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
37+
// Register the block exception handler for Spring Cloud Gateway.
38+
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
39+
}
40+
41+
@Bean
42+
@Order(-1)
43+
public GlobalFilter sentinelGatewayFilter() {
44+
return new SentinelGatewayFilter();
45+
}
46+
}
47+
```
48+
49+
The gateway adapter will regard all `routeId` (defined in Spring properties) and all customized API definitions
50+
(defined in `GatewayApiDefinitionManager` of `sentinel-api-gateway-adapter-common` module) as resources.
51+
52+
You can register various customized callback in `GatewayCallbackManager`:
53+
54+
- `setBlockHandler`: register a customized `BlockRequestHandler` to handle the blocked request. The default implementation is `DefaultBlockRequestHandler`, which returns default message like `Blocked by Sentinel: FlowException`.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>sentinel-adapter</artifactId>
7+
<groupId>com.alibaba.csp</groupId>
8+
<version>1.8.8</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>sentinel-spring-cloud-gateway-v6x-adapter</artifactId>
13+
<packaging>jar</packaging>
14+
15+
<properties>
16+
<java.source.version>17</java.source.version>
17+
<java.target.version>17</java.target.version>
18+
<spring.cloud.gateway.version>4.3.0</spring.cloud.gateway.version>
19+
<spring.boot.version>3.5.0</spring.boot.version>
20+
<spring.version>6.2.7</spring.version>
21+
<skip.spring.v6x.test>false</skip.spring.v6x.test>
22+
</properties>
23+
24+
<dependencies>
25+
<dependency>
26+
<groupId>com.alibaba.csp</groupId>
27+
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
28+
</dependency>
29+
<dependency>
30+
<groupId>com.alibaba.csp</groupId>
31+
<artifactId>sentinel-reactor-adapter</artifactId>
32+
</dependency>
33+
34+
<dependency>
35+
<groupId>org.springframework.cloud</groupId>
36+
<artifactId>spring-cloud-gateway-server</artifactId>
37+
<version>${spring.cloud.gateway.version}</version>
38+
<scope>provided</scope>
39+
</dependency>
40+
<dependency>
41+
<groupId>org.springframework</groupId>
42+
<artifactId>spring-webflux</artifactId>
43+
<version>${spring.version}</version>
44+
<scope>provided</scope>
45+
</dependency>
46+
47+
<dependency>
48+
<groupId>org.springframework.cloud</groupId>
49+
<artifactId>spring-cloud-starter-gateway</artifactId>
50+
<version>${spring.cloud.gateway.version}</version>
51+
<scope>test</scope>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.springframework.boot</groupId>
55+
<artifactId>spring-boot-starter-webflux</artifactId>
56+
<version>${spring.boot.version}</version>
57+
<scope>test</scope>
58+
</dependency>
59+
<dependency>
60+
<groupId>org.springframework.boot</groupId>
61+
<artifactId>spring-boot-starter-test</artifactId>
62+
<version>${spring.boot.version}</version>
63+
<scope>test</scope>
64+
</dependency>
65+
66+
<dependency>
67+
<groupId>junit</groupId>
68+
<artifactId>junit</artifactId>
69+
<scope>test</scope>
70+
</dependency>
71+
<dependency>
72+
<groupId>org.assertj</groupId>
73+
<artifactId>assertj-core</artifactId>
74+
<scope>test</scope>
75+
</dependency>
76+
<dependency>
77+
<groupId>org.mockito</groupId>
78+
<artifactId>mockito-core</artifactId>
79+
<scope>test</scope>
80+
</dependency>
81+
</dependencies>
82+
83+
<build>
84+
<plugins>
85+
<plugin>
86+
<groupId>org.apache.maven.plugins</groupId>
87+
<artifactId>maven-surefire-plugin</artifactId>
88+
<version>${maven.surefire.version}</version>
89+
<dependencies>
90+
<dependency>
91+
<groupId>org.apache.maven.surefire</groupId>
92+
<artifactId>surefire-junit47</artifactId>
93+
<version>3.2.5</version>
94+
</dependency>
95+
</dependencies>
96+
<configuration>
97+
<skipTests>${skip.spring.v6x.test}</skipTests>
98+
</configuration>
99+
</plugin>
100+
</plugins>
101+
</build>
102+
103+
104+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 1999-2019 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.alibaba.csp.sentinel.adapter.gateway.sc;
17+
18+
import com.alibaba.csp.sentinel.EntryType;
19+
import com.alibaba.csp.sentinel.ResourceTypeConstants;
20+
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
21+
import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser;
22+
import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser;
23+
import com.alibaba.csp.sentinel.adapter.gateway.sc.api.GatewayApiMatcherManager;
24+
import com.alibaba.csp.sentinel.adapter.gateway.sc.api.matcher.WebExchangeApiMatcher;
25+
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
26+
import com.alibaba.csp.sentinel.adapter.reactor.ContextConfig;
27+
import com.alibaba.csp.sentinel.adapter.reactor.EntryConfig;
28+
import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer;
29+
import com.alibaba.csp.sentinel.util.AssertUtil;
30+
import org.springframework.cloud.gateway.filter.GatewayFilter;
31+
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
32+
import org.springframework.cloud.gateway.filter.GlobalFilter;
33+
import org.springframework.cloud.gateway.route.Route;
34+
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
35+
import org.springframework.core.Ordered;
36+
import org.springframework.web.server.ServerWebExchange;
37+
import reactor.core.publisher.Mono;
38+
39+
import java.util.Optional;
40+
import java.util.Set;
41+
import java.util.stream.Collectors;
42+
43+
/**
44+
* @author Eric Zhao
45+
* @since 1.6.0
46+
*/
47+
public class SentinelGatewayFilter implements GatewayFilter, GlobalFilter, Ordered {
48+
49+
private final int order;
50+
51+
private final GatewayParamParser<ServerWebExchange> paramParser;
52+
53+
public SentinelGatewayFilter() {
54+
this(Ordered.HIGHEST_PRECEDENCE);
55+
}
56+
57+
public SentinelGatewayFilter(int order) {
58+
this(order, new ServerWebExchangeItemParser());
59+
}
60+
61+
public SentinelGatewayFilter(RequestItemParser<ServerWebExchange> serverWebExchangeItemParser) {
62+
this(Ordered.HIGHEST_PRECEDENCE, serverWebExchangeItemParser);
63+
}
64+
65+
public SentinelGatewayFilter(int order, RequestItemParser<ServerWebExchange> requestItemParser) {
66+
AssertUtil.notNull(requestItemParser, "requestItemParser cannot be null");
67+
this.order = order;
68+
this.paramParser = new GatewayParamParser<>(requestItemParser);
69+
}
70+
71+
@Override
72+
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
73+
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
74+
75+
Mono<Void> asyncResult = chain.filter(exchange);
76+
if (route != null) {
77+
String routeId = route.getId();
78+
Object[] params = paramParser.parseParameterFor(routeId, exchange,
79+
r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID);
80+
String origin = Optional.ofNullable(GatewayCallbackManager.getRequestOriginParser())
81+
.map(f -> f.apply(exchange))
82+
.orElse("");
83+
asyncResult = asyncResult.transform(
84+
new SentinelReactorTransformer<>(new EntryConfig(routeId, ResourceTypeConstants.COMMON_API_GATEWAY,
85+
EntryType.IN, 1, params, new ContextConfig(contextName(routeId), origin)))
86+
);
87+
}
88+
89+
Set<String> matchingApis = pickMatchingApiDefinitions(exchange);
90+
for (String apiName : matchingApis) {
91+
Object[] params = paramParser.parseParameterFor(apiName, exchange,
92+
r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME);
93+
asyncResult = asyncResult.transform(
94+
new SentinelReactorTransformer<>(new EntryConfig(apiName, ResourceTypeConstants.COMMON_API_GATEWAY,
95+
EntryType.IN, 1, params))
96+
);
97+
}
98+
99+
return asyncResult;
100+
}
101+
102+
private String contextName(String route) {
103+
return SentinelGatewayConstants.GATEWAY_CONTEXT_ROUTE_PREFIX + route;
104+
}
105+
106+
Set<String> pickMatchingApiDefinitions(ServerWebExchange exchange) {
107+
return GatewayApiMatcherManager.getApiMatcherMap().values()
108+
.stream()
109+
.filter(m -> m.test(exchange))
110+
.map(WebExchangeApiMatcher::getApiName)
111+
.collect(Collectors.toSet());
112+
}
113+
114+
@Override
115+
public int getOrder() {
116+
return order;
117+
}
118+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 1999-2019 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.alibaba.csp.sentinel.adapter.gateway.sc;
17+
18+
import java.net.InetSocketAddress;
19+
import java.util.Optional;
20+
21+
import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser;
22+
23+
import org.springframework.http.HttpCookie;
24+
import org.springframework.web.server.ServerWebExchange;
25+
26+
/**
27+
* @author Eric Zhao
28+
* @since 1.6.0
29+
*/
30+
public class ServerWebExchangeItemParser implements RequestItemParser<ServerWebExchange> {
31+
32+
@Override
33+
public String getPath(ServerWebExchange exchange) {
34+
return exchange.getRequest().getPath().value();
35+
}
36+
37+
@Override
38+
public String getRemoteAddress(ServerWebExchange exchange) {
39+
InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress();
40+
if (remoteAddress == null) {
41+
return null;
42+
}
43+
return remoteAddress.getAddress().getHostAddress();
44+
}
45+
46+
@Override
47+
public String getHeader(ServerWebExchange exchange, String key) {
48+
return exchange.getRequest().getHeaders().getFirst(key);
49+
}
50+
51+
@Override
52+
public String getUrlParam(ServerWebExchange exchange, String paramName) {
53+
return exchange.getRequest().getQueryParams().getFirst(paramName);
54+
}
55+
56+
@Override
57+
public String getCookieValue(ServerWebExchange exchange, String cookieName) {
58+
return Optional.ofNullable(exchange.getRequest().getCookies().getFirst(cookieName))
59+
.map(HttpCookie::getValue)
60+
.orElse(null);
61+
}
62+
}

0 commit comments

Comments
 (0)