Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sentinel-adapter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<module>sentinel-spring-webmvc-v6x-adapter</module>
<module>sentinel-zuul2-adapter</module>
<module>sentinel-okhttp-adapter</module>
<module>sentinel-spring-restclient-adapter</module>
<module>sentinel-jax-rs-adapter</module>
<module>sentinel-quarkus-adapter</module>
<module>sentinel-motan-adapter</module>
Expand Down
152 changes: 152 additions & 0 deletions sentinel-adapter/sentinel-spring-restclient-adapter/README.md
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 response
RestClientFallback customFallback = (request, body, execution, ex) -> {
return new SentinelClientHttpResponse("Service temporarily unavailable, please retry later");
};

// 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 sentinel-adapter/sentinel-spring-restclient-adapter/pom.xml
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>

<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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;

/**
* Default HTTP response when request is blocked by Sentinel.
*
* @author QHT, uuuyuqi
*/
public class SentinelClientHttpResponse implements ClientHttpResponse {

private String blockResponse = "Request blocked by Sentinel";

public SentinelClientHttpResponse() {
}

public SentinelClientHttpResponse(String blockResponse) {
this.blockResponse = blockResponse;
}

@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}

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

@Override
public String getStatusText() throws IOException {
return blockResponse;
}

@Override
public void close() {
}

@Override
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;
}
}
Loading
Loading