Skip to content

feat: support Spring RestClient#3602

Open
uuuyuqi wants to merge 5 commits intoalibaba:1.8from
uuuyuqi:uuuyuqi/feat-restclient-adapter
Open

feat: support Spring RestClient#3602
uuuyuqi wants to merge 5 commits intoalibaba:1.8from
uuuyuqi:uuuyuqi/feat-restclient-adapter

Conversation

@uuuyuqi
Copy link
Collaborator

@uuuyuqi uuuyuqi commented Mar 10, 2026

Describe what this PR does / why we need it

Closes #3601

Does this pull request fix one issue?

Closes #3601

Describe how you did it

add Sentinel RestClient Interceptor

Describe how to verify it

Run com.alibaba.csp.sentinel.adapter.spring.restclient.SentinelRestClientInterceptorSimpleTest to confirm the flow control behavior of Sentinel.

Special notes for reviews

None

uuuyuqi added 2 commits March 10, 2026 11:32
Change-Id: I49c8b1cf7a0a6f357b68b58bd352efc5981b76b2
Co-developed-by: OpenCode <noreply@opencode.ai>
Change-Id: Ieacaf949222f5871f934d2f0aa3bd6d73d1a087b

@PostMapping("/users")
public String createUser(@RequestBody String user) {
return "Created: " + user;

Check warning

Code scanning / CodeQL

Cross-site scripting Medium test

Cross-site scripting vulnerability due to a
user-provided value
.
uuuyuqi added 3 commits March 10, 2026 15:11
Change-Id: I2df54be42732b1c1786b4b9189639913b4598fd9
Co-developed-by: OpenCode <noreply@opencode.ai>
Change-Id: Ia9dceb36c2ef3a9a86a61110446c6aec4de331a9
Co-developed-by: OpenCode <noreply@opencode.ai>
Change-Id: I10f1752fa921aa8980e640c4fe7726d3c1764f37
Co-developed-by: OpenCode <noreply@opencode.ai>
} catch (BlockException ex) {
return handleBlockException(request, body, execution, ex);
} catch (IOException ex) {
Tracer.traceEntry(ex, hostEntry);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pathEntry should also record exceptions.


if (response.getStatusCode().is5xxServerError()) {
RuntimeException ex = new RuntimeException("Server error: " + response.getStatusCode().value());
Tracer.trace(ex);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, only pathEntry records exceptions. hostEntry should also record the ex.

public ClientHttpResponse handle(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution, BlockException ex) {
return new SentinelClientHttpResponse("RestClient request blocked by Sentinel: "
+ ex.getClass().getSimpleName());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, the client framework should default to throwing exceptions.

public HttpHeaders getHeaders() {
Map<String, List<String>> headers = new HashMap<>();
headers.put(HttpHeaders.CONTENT_TYPE,
Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the body isn't JSON, wouldn't it be more appropriate to use text/plain?

@LearningGp LearningGp requested a review from Copilot March 12, 2026 08:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Sentinel adapter module to integrate with Spring Framework 6+ RestClient, providing resource extraction, flow-control/circuit-breaking interception, and fallback handling.

Changes:

  • Introduces SentinelRestClientInterceptor with configurable resource naming and fallback behavior.
  • Adds default implementations for resource extraction and blocked-request fallback response.
  • Adds a new Maven module with documentation and a set of unit/integration-style tests.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
sentinel-adapter/pom.xml Registers the new sentinel-spring-restclient-adapter module in the parent build.
sentinel-adapter/sentinel-spring-restclient-adapter/pom.xml Adds module dependencies (Spring Web/Boot/Test) and Surefire configuration.
sentinel-adapter/sentinel-spring-restclient-adapter/README.md Documents adapter usage, configuration, and resource naming.
sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/SentinelRestClientInterceptor.java Implements the RestClient request interceptor with dual-level resource entries and fallback handling.
sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/SentinelRestClientConfig.java Provides configuration (prefix, extractor, fallback) for the interceptor.
sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/SentinelClientHttpResponse.java Defines the default blocked-response ClientHttpResponse implementation.
sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/extractor/RestClientResourceExtractor.java Introduces extractor SPI for resource naming.
sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/extractor/DefaultRestClientResourceExtractor.java Implements default resource naming from method + URL components.
sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/fallback/RestClientFallback.java Introduces fallback SPI for blocked requests.
sentinel-adapter/sentinel-spring-restclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/restclient/fallback/DefaultRestClientFallback.java Provides default blocked-request fallback response.
sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/SentinelRestClientInterceptorSimpleTest.java Adds integration-style tests around flow control/degrade with an embedded Spring Boot app.
sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/ManualTest.java Adds a manual runnable test/demo for the adapter.
sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/SentinelRestClientConfigTest.java Adds config validation tests (null extractor/fallback).
sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/SentinelClientHttpResponseTest.java Adds tests for blocked-response behavior.
sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/extractor/DefaultRestClientResourceExtractorTest.java Adds tests for default resource name formatting.
sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/fallback/DefaultRestClientFallbackTest.java Adds tests for default fallback handling.
sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/app/TestApplication.java Provides Spring Boot test application used by integration tests.
sentinel-adapter/sentinel-spring-restclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/restclient/app/TestController.java Provides endpoints used by integration tests (hello/users/error/delay).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

*/
package com.alibaba.csp.sentinel.adapter.spring.restclient.fallback;

import com.alibaba.csp.sentinel.adapter.spring.restclient.SentinelClientHttpResponse;
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import: SentinelClientHttpResponse is not referenced in this interface. Please remove it to avoid checkstyle/compilation warnings in stricter builds.

Suggested change
import com.alibaba.csp.sentinel.adapter.spring.restclient.SentinelClientHttpResponse;

Copilot uses AI. Check for mistakes.
import java.io.IOException;
import java.net.URI;

import static org.junit.Assert.assertEquals;
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused static import: assertEquals is not used in this test. Removing it keeps the test clean and avoids warnings.

Suggested change
import static org.junit.Assert.assertEquals;

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +115
Tracer.trace(ex);
}

return response;
} catch (BlockException ex) {
return handleBlockException(request, body, execution, ex);
} catch (IOException ex) {
Tracer.traceEntry(ex, hostEntry);
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception tracing is currently attributed only to hostEntry for IOException, and 5xx tracing uses Tracer.trace(ex) without binding to the active entry. This can lead to inaccurate Sentinel exception metrics at the path-level (and possibly no attribution depending on context). Consider tracing against pathEntry when it exists (otherwise hostEntry), and similarly trace 5xx against the active entry (e.g., via Tracer.traceEntry(...)).

Suggested change
Tracer.trace(ex);
}
return response;
} catch (BlockException ex) {
return handleBlockException(request, body, execution, ex);
} catch (IOException ex) {
Tracer.traceEntry(ex, hostEntry);
Entry currentEntry = (pathEntry != null) ? pathEntry : hostEntry;
if (currentEntry != null) {
Tracer.traceEntry(ex, currentEntry);
} else {
Tracer.trace(ex);
}
}
return response;
} catch (BlockException ex) {
return handleBlockException(request, body, execution, ex);
} catch (IOException ex) {
Entry currentEntry = (pathEntry != null) ? pathEntry : hostEntry;
if (currentEntry != null) {
Tracer.traceEntry(ex, currentEntry);
} else {
Tracer.trace(ex);
}

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +79
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(blockResponse.getBytes());
}

@Override
public HttpHeaders getHeaders() {
Map<String, List<String>> headers = new HashMap<>();
headers.put(HttpHeaders.CONTENT_TYPE,
Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(headers);
return httpHeaders;
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response body is plain text but the Content-Type is set to application/json, and blockResponse.getBytes() uses the platform default charset. Please align the content type with the actual payload (e.g., text/plain; charset=UTF-8), and encode bytes with a fixed charset (e.g., UTF-8) to avoid inconsistent behavior across environments.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +54
return HttpStatus.OK;
}

@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning 200 OK for a request that was blocked by flow control/circuit breaking can mask failures and confuse client-side error handling. Consider using a more appropriate status code (commonly 429 TOO_MANY_REQUESTS for flow control, or 503 SERVICE_UNAVAILABLE for degrade) to make the blocked state explicit to API consumers.

Suggested change
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
return HttpStatus.TOO_MANY_REQUESTS;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.TOO_MANY_REQUESTS.value();

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +43
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These imports (CountDownLatch, TimeUnit, AtomicBoolean, AtomicReference) are not used in the test class as shown. Please remove unused imports to avoid warnings and keep the test file focused.

Suggested change
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +57
@SpringBootTest(classes = TestApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
properties = {
"server.port=8087"
})
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using DEFINED_PORT + hard-coded 8087 can make CI flaky due to port conflicts (parallel test runs, local dev environment, etc.). Prefer RANDOM_PORT with injected port (e.g., @LocalServerPort) so the test suite is more reliable.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +37
public static void main(String[] args) {
System.out.println("=== Sentinel RestClient Adapter Manual Test ===\n");

testBasicUsage();
testWithFlowControl();
testWithCustomExtractor();

System.out.println("\n=== All manual tests completed! ===");
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a runnable demo/manual test living under src/test/java. While it won’t be picked up by Surefire as a JUnit test, it still becomes part of the test artifact and may confuse automated test expectations. Consider moving it to documentation/examples (or clearly marking it under a dedicated .../manual/... package and documenting that it performs real network calls).

Copilot uses AI. Check for mistakes.
## Usage

### 1. Add Dependency

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Maven dependency snippet is missing the closing </dependency> tag indentation/structure is fine, but the opening tag block should be properly closed to be copy-pastable.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +24
<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>
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coding Spring/Spring Boot versions in this module POM can cause version skew against the repository’s parent dependency management (and across other modules). If the repo already manages Spring versions via a BOM/parent, prefer relying on that (or using a shared property from the parent) to keep dependency alignment consistent.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

support Spring RestClient

3 participants