-
Notifications
You must be signed in to change notification settings - Fork 8.2k
feat: support Spring RestClient #3602
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
uuuyuqi
wants to merge
7
commits into
alibaba:1.8
Choose a base branch
from
uuuyuqi:uuuyuqi/feat-restclient-adapter
base: 1.8
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
3b6e844
feat: support Spring RestClient
uuuyuqi 3aea5a6
add customized resourceExtractor and fallback test
uuuyuqi 8776b5c
fix: resolve markdown lint errors in README
uuuyuqi ab50edd
fix: skip tests on Java < 17 for Spring 6.x compatibility
uuuyuqi 9663e55
fix: remove extra trailing blank line in README
uuuyuqi 56c4054
fix: address PR review comments for restclient adapter
uuuyuqi 9ebc14e
refactor: remove SentinelClientHttpResponse class
uuuyuqi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
sentinel-adapter/sentinel-spring-restclient-adapter/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| # Sentinel Spring RestClient Adapter | ||
|
|
||
| ## Overview | ||
|
|
||
| Sentinel Spring RestClient Adapter provides Sentinel integration for Spring Framework 6.0+ `RestClient`. With this adapter, you can easily add flow control, circuit breaking, and degradation features to HTTP requests made via `RestClient`. | ||
|
|
||
| ## Features | ||
|
|
||
| - Flow control (QPS limiting) | ||
| - Circuit breaking (degradation) | ||
| - Custom resource name extraction | ||
| - Custom fallback responses | ||
| - HTTP 5xx error tracing | ||
|
|
||
| ## Requirements | ||
|
|
||
| - Spring Framework 6.0+ | ||
| - JDK 17+ | ||
| - Sentinel Core 1.8.0+ | ||
|
|
||
| ## Usage | ||
|
|
||
| ### 1. Add Dependency | ||
|
|
||
| ```xml | ||
| <dependency> | ||
| <groupId>com.alibaba.csp</groupId> | ||
| <artifactId>sentinel-spring-restclient-adapter</artifactId> | ||
| <version>${sentinel.version}</version> | ||
| </dependency> | ||
| ``` | ||
|
|
||
| ### 2. Basic Usage | ||
|
|
||
| ```java | ||
| import com.alibaba.csp.sentinel.adapter.spring.restclient.SentinelRestClientInterceptor; | ||
| import org.springframework.web.client.RestClient; | ||
|
|
||
| // Create RestClient with Sentinel interceptor | ||
| RestClient restClient = RestClient.builder() | ||
| .requestInterceptor(new SentinelRestClientInterceptor()) | ||
| .build(); | ||
|
|
||
| // Use RestClient to send requests (protected by Sentinel) | ||
| String result = restClient.get() | ||
| .uri("https://httpbin.org/get") | ||
| .retrieve() | ||
| .body(String.class); | ||
| ``` | ||
|
|
||
| ### 3. Custom Configuration | ||
|
|
||
| ```java | ||
| import com.alibaba.csp.sentinel.adapter.spring.restclient.SentinelRestClientConfig; | ||
| import com.alibaba.csp.sentinel.adapter.spring.restclient.SentinelRestClientInterceptor; | ||
| import com.alibaba.csp.sentinel.adapter.spring.restclient.extractor.RestClientResourceExtractor; | ||
| import com.alibaba.csp.sentinel.adapter.spring.restclient.fallback.RestClientFallback; | ||
|
|
||
| // Custom resource name extractor | ||
| RestClientResourceExtractor customExtractor = request -> { | ||
| // Example: normalize RESTful path parameters | ||
| String path = request.getURI().getPath(); | ||
| if (path.matches("/users/\\d+")) { | ||
| path = "/users/{id}"; | ||
| } | ||
| return request.getMethod() + ":" + request.getURI().getHost() + path; | ||
| }; | ||
|
|
||
| // Custom fallback: throw a custom exception when blocked | ||
| RestClientFallback customFallback = (request, body, execution, ex) -> { | ||
| throw new RuntimeException("Service temporarily unavailable, please retry later", ex); | ||
| }; | ||
|
|
||
| // Create configuration | ||
| SentinelRestClientConfig config = new SentinelRestClientConfig( | ||
| "my-restclient:", // Resource name prefix | ||
| customExtractor, | ||
| customFallback | ||
| ); | ||
|
|
||
| // Create interceptor with custom configuration | ||
| RestClient restClient = RestClient.builder() | ||
| .requestInterceptor(new SentinelRestClientInterceptor(config)) | ||
| .build(); | ||
| ``` | ||
|
|
||
| ### 4. Configure Sentinel Rules | ||
|
|
||
| ```java | ||
| import com.alibaba.csp.sentinel.slots.block.RuleConstant; | ||
| import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | ||
| import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; | ||
| import java.util.Collections; | ||
|
|
||
| // Configure flow control rule | ||
| FlowRule rule = new FlowRule("restclient:GET:https://httpbin.org/get"); | ||
| rule.setGrade(RuleConstant.FLOW_GRADE_QPS); | ||
| rule.setCount(10); // Max 10 requests per second | ||
| rule.setLimitApp("default"); | ||
|
|
||
| FlowRuleManager.loadRules(Collections.singletonList(rule)); | ||
| ``` | ||
|
|
||
| ## Core Components | ||
|
|
||
| ### SentinelRestClientInterceptor | ||
|
|
||
| The main interceptor implementation responsible for: | ||
|
|
||
| - Creating Sentinel resources for each HTTP request | ||
| - Catching BlockException and invoking fallback handler | ||
| - Tracing exceptions and 5xx errors | ||
|
|
||
| ### SentinelRestClientConfig | ||
|
|
||
| Configuration class containing: | ||
|
|
||
| - `resourcePrefix`: Resource name prefix (default: `restclient:`) | ||
| - `resourceExtractor`: Resource name extractor | ||
| - `fallback`: Fallback handler | ||
|
|
||
| ### RestClientResourceExtractor | ||
|
|
||
| Interface for resource name extraction, allowing customization of resource name generation logic. | ||
|
|
||
| ### RestClientFallback | ||
|
|
||
| Interface for fallback handling, invoked when requests are blocked by flow control or circuit breaking. | ||
|
|
||
| ## Resource Name Format | ||
|
|
||
| The default resource name format: `{prefix}{METHOD}:{URL}` | ||
|
|
||
| Examples: | ||
|
|
||
| - `restclient:GET:https://httpbin.org/get` | ||
| - `restclient:POST:http://localhost:8080/api/users` | ||
|
|
||
| ## Notes | ||
|
|
||
| This adapter only supports `RestClient` from Spring Framework 6.0+, not `RestTemplate`. | ||
|
|
||
| ## Integration with Spring Cloud Alibaba | ||
|
|
||
| This adapter provides basic Sentinel integration. For Spring Cloud Alibaba projects: | ||
|
|
||
| 1. Add auto-configuration support in `spring-cloud-starter-alibaba-sentinel` | ||
| 2. Use `@SentinelRestClient` annotation for simplified configuration | ||
|
|
||
| ## License | ||
|
|
||
| Apache License 2.0 | ||
87 changes: 87 additions & 0 deletions
87
sentinel-adapter/sentinel-spring-restclient-adapter/pom.xml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| <?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.alibaba.csp</groupId> | ||
| <artifactId>sentinel-adapter</artifactId> | ||
| <version>${revision}</version> | ||
| <relativePath>../pom.xml</relativePath> | ||
| </parent> | ||
| <modelVersion>4.0.0</modelVersion> | ||
|
|
||
| <name>${project.groupId}:${project.artifactId}</name> | ||
|
|
||
| <artifactId>sentinel-spring-restclient-adapter</artifactId> | ||
| <packaging>jar</packaging> | ||
|
|
||
| <properties> | ||
| <spring-web.version>6.1.0</spring-web.version> | ||
| <spring-boot.version>3.2.0</spring-boot.version> | ||
| <spring-test.version>6.1.0</spring-test.version> | ||
|
|
||
| <skip.spring.v6x.test>false</skip.spring.v6x.test> | ||
| </properties> | ||
uuuyuqi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>com.alibaba.csp</groupId> | ||
| <artifactId>sentinel-core</artifactId> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>org.springframework</groupId> | ||
| <artifactId>spring-web</artifactId> | ||
| <version>${spring-web.version}</version> | ||
| <scope>provided</scope> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>junit</groupId> | ||
| <artifactId>junit</artifactId> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.mockito</groupId> | ||
| <artifactId>mockito-core</artifactId> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>com.alibaba</groupId> | ||
| <artifactId>fastjson</artifactId> | ||
| <scope>test</scope> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-web</artifactId> | ||
| <version>${spring-boot.version}</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-test</artifactId> | ||
| <version>${spring-boot.version}</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.springframework</groupId> | ||
| <artifactId>spring-test</artifactId> | ||
| <version>${spring-test.version}</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| </dependencies> | ||
|
|
||
| <build> | ||
| <plugins> | ||
| <plugin> | ||
| <groupId>org.apache.maven.plugins</groupId> | ||
| <artifactId>maven-surefire-plugin</artifactId> | ||
| <version>${maven.surefire.version}</version> | ||
| <configuration> | ||
| <skipTests>${skip.spring.v6x.test}</skipTests> | ||
| </configuration> | ||
| </plugin> | ||
| </plugins> | ||
| </build> | ||
| </project> | ||
79 changes: 79 additions & 0 deletions
79
...ain/java/com/alibaba/csp/sentinel/adapter/spring/restclient/SentinelRestClientConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| /* | ||
| * Copyright 1999-2020 Alibaba Group Holding Ltd. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.alibaba.csp.sentinel.adapter.spring.restclient; | ||
|
|
||
| import com.alibaba.csp.sentinel.adapter.spring.restclient.extractor.DefaultRestClientResourceExtractor; | ||
| import com.alibaba.csp.sentinel.adapter.spring.restclient.extractor.RestClientResourceExtractor; | ||
| import com.alibaba.csp.sentinel.adapter.spring.restclient.fallback.DefaultRestClientFallback; | ||
| import com.alibaba.csp.sentinel.adapter.spring.restclient.fallback.RestClientFallback; | ||
| import com.alibaba.csp.sentinel.util.AssertUtil; | ||
|
|
||
| /** | ||
| * Configuration for Sentinel RestClient interceptor. | ||
| * | ||
| * @author QHT, uuuyuqi | ||
| */ | ||
| public class SentinelRestClientConfig { | ||
|
|
||
| public static final String DEFAULT_RESOURCE_PREFIX = "restclient:"; | ||
|
|
||
| private final String resourcePrefix; | ||
| private final RestClientResourceExtractor resourceExtractor; | ||
| private final RestClientFallback fallback; | ||
|
|
||
| public SentinelRestClientConfig() { | ||
| this(DEFAULT_RESOURCE_PREFIX); | ||
| } | ||
|
|
||
| public SentinelRestClientConfig(String resourcePrefix) { | ||
| this(resourcePrefix, new DefaultRestClientResourceExtractor(), new DefaultRestClientFallback()); | ||
| } | ||
|
|
||
| public SentinelRestClientConfig(RestClientResourceExtractor resourceExtractor, RestClientFallback fallback) { | ||
| this(DEFAULT_RESOURCE_PREFIX, resourceExtractor, fallback); | ||
| } | ||
|
|
||
| public SentinelRestClientConfig(String resourcePrefix, | ||
| RestClientResourceExtractor resourceExtractor, | ||
| RestClientFallback fallback) { | ||
| AssertUtil.notNull(resourceExtractor, "resourceExtractor cannot be null"); | ||
| AssertUtil.notNull(fallback, "fallback cannot be null"); | ||
| this.resourcePrefix = resourcePrefix; | ||
| this.resourceExtractor = resourceExtractor; | ||
| this.fallback = fallback; | ||
| } | ||
|
|
||
| public String getResourcePrefix() { | ||
| return resourcePrefix; | ||
| } | ||
|
|
||
| public RestClientResourceExtractor getResourceExtractor() { | ||
| return resourceExtractor; | ||
| } | ||
|
|
||
| public RestClientFallback getFallback() { | ||
| return fallback; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "SentinelRestClientConfig{" + | ||
| "resourcePrefix='" + resourcePrefix + '\'' + | ||
| ", resourceExtractor=" + resourceExtractor + | ||
| ", fallback=" + fallback + | ||
| '}'; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.