Skip to content

Commit bcb9b8b

Browse files
committed
feat: add Apache HttpClient 5.x adapter for Sentinel
Add sentinel-apache-httpclient5-adapter module that integrates Sentinel flow control with Apache HttpClient 5.x outgoing HTTP requests. Features: - ExecChainHandler-based integration (SentinelApacheHttpClient5Handler) - Configurable resource name extraction (METHOD:URL format by default) - Query string and fragment stripping in resource names - Customizable fallback handling (throws SentinelRpcException by default) - Configurable resource name prefix (default: "httpclient:") Follows the same architecture pattern as the existing HttpClient 4.x adapter while adapting to HC5's ExecChainHandler API. Closes #3614 Change-Id: I966295da9419beac99451fc6c9c31b1d75a9e7e6 Co-developed-by: Cursor <noreply@cursor.com>
1 parent 38b4619 commit bcb9b8b

15 files changed

Lines changed: 898 additions & 0 deletions

File tree

sentinel-adapter/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<module>sentinel-apache-dubbo-adapter</module>
2222
<module>sentinel-apache-dubbo3-adapter</module>
2323
<module>sentinel-apache-httpclient-adapter</module>
24+
<module>sentinel-apache-httpclient5-adapter</module>
2425
<module>sentinel-sofa-rpc-adapter</module>
2526
<module>sentinel-grpc-adapter</module>
2627
<module>sentinel-zuul-adapter</module>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Sentinel Apache HttpClient 5.x Adapter
2+
3+
## Introduction
4+
5+
Sentinel provides integration for Apache HttpClient 5.x to enable flow control for outgoing HTTP requests.
6+
7+
## Usage
8+
9+
### Add dependency
10+
11+
```xml
12+
<dependency>
13+
<groupId>com.alibaba.csp</groupId>
14+
<artifactId>sentinel-apache-httpclient5-adapter</artifactId>
15+
<version>x.y.z</version>
16+
</dependency>
17+
```
18+
19+
### Build the HttpClient
20+
21+
```java
22+
CloseableHttpClient httpclient = HttpClients.custom()
23+
.addExecInterceptorBefore(ChainElement.MAIN_TRANSPORT.name(), "sentinel",
24+
new SentinelApacheHttpClient5Handler())
25+
.build();
26+
```
27+
28+
Or with custom configuration:
29+
30+
```java
31+
SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig();
32+
config.setPrefix("httpclient:");
33+
config.setExtractor(myExtractor);
34+
config.setFallback(myFallback);
35+
36+
CloseableHttpClient httpclient = HttpClients.custom()
37+
.addExecInterceptorBefore(ChainElement.MAIN_TRANSPORT.name(), "sentinel",
38+
new SentinelApacheHttpClient5Handler(config))
39+
.build();
40+
```
41+
42+
### Configuration
43+
44+
| Name | Description | Type | Default Value |
45+
|------|------------|------|---------------|
46+
| prefix | Customize resource prefix | `String` | `httpclient:` |
47+
| extractor | Customize resource extractor | `ApacheHttpClientResourceExtractor` | `DefaultApacheHttpClientResourceExtractor` |
48+
| fallback | Handle request when it is blocked | `ApacheHttpClientFallback` | `DefaultApacheHttpClientFallback` |
49+
50+
### Resource Extractor
51+
52+
The default extractor generates resource names in the format `METHOD:url` (e.g. `GET:http://example.com/api/users`),
53+
with query parameters and fragments stripped. You can customize this by implementing `ApacheHttpClientResourceExtractor`:
54+
55+
```java
56+
public class MyResourceExtractor implements ApacheHttpClientResourceExtractor {
57+
@Override
58+
public String extractor(ClassicHttpRequest request) {
59+
// custom resource name extraction logic
60+
return request.getMethod() + ":" + request.getRequestUri();
61+
}
62+
}
63+
```
64+
65+
### Fallback
66+
67+
The default fallback throws `SentinelRpcException`. You can customize the behavior:
68+
69+
```java
70+
public class MyFallback implements ApacheHttpClientFallback {
71+
@Override
72+
public ClassicHttpResponse handle(ClassicHttpRequest request, BlockException e) {
73+
// return a custom response or throw exception
74+
throw new SentinelRpcException(e);
75+
}
76+
}
77+
```
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
<groupId>com.alibaba.csp</groupId>
7+
<artifactId>sentinel-adapter</artifactId>
8+
<version>${revision}</version>
9+
<relativePath>../pom.xml</relativePath>
10+
</parent>
11+
<modelVersion>4.0.0</modelVersion>
12+
13+
<name>${project.groupId}:${project.artifactId}</name>
14+
15+
<artifactId>sentinel-apache-httpclient5-adapter</artifactId>
16+
<packaging>jar</packaging>
17+
18+
<properties>
19+
<apache.httpclient5.version>5.1</apache.httpclient5.version>
20+
<spring.boot.version>2.1.3.RELEASE</spring.boot.version>
21+
<spring-test.version>5.1.5.RELEASE</spring-test.version>
22+
</properties>
23+
24+
<dependencies>
25+
<dependency>
26+
<groupId>com.alibaba.csp</groupId>
27+
<artifactId>sentinel-core</artifactId>
28+
</dependency>
29+
<dependency>
30+
<groupId>org.apache.httpcomponents.client5</groupId>
31+
<artifactId>httpclient5</artifactId>
32+
<version>${apache.httpclient5.version}</version>
33+
<scope>provided</scope>
34+
</dependency>
35+
36+
<dependency>
37+
<groupId>junit</groupId>
38+
<artifactId>junit</artifactId>
39+
<scope>test</scope>
40+
</dependency>
41+
42+
<dependency>
43+
<groupId>org.mockito</groupId>
44+
<artifactId>mockito-core</artifactId>
45+
<scope>test</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>com.alibaba</groupId>
49+
<artifactId>fastjson</artifactId>
50+
<scope>test</scope>
51+
</dependency>
52+
53+
<dependency>
54+
<groupId>org.springframework.boot</groupId>
55+
<artifactId>spring-boot-starter-web</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-test</artifactId>
62+
<version>${spring.boot.version}</version>
63+
<scope>test</scope>
64+
</dependency>
65+
<dependency>
66+
<groupId>org.springframework</groupId>
67+
<artifactId>spring-test</artifactId>
68+
<version>${spring-test.version}</version>
69+
<scope>test</scope>
70+
</dependency>
71+
</dependencies>
72+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 1999-2020 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.apache.httpclient5;
17+
18+
import com.alibaba.csp.sentinel.Entry;
19+
import com.alibaba.csp.sentinel.EntryType;
20+
import com.alibaba.csp.sentinel.ResourceTypeConstants;
21+
import com.alibaba.csp.sentinel.SphU;
22+
import com.alibaba.csp.sentinel.Tracer;
23+
import com.alibaba.csp.sentinel.adapter.apache.httpclient5.config.SentinelApacheHttpClientConfig;
24+
import com.alibaba.csp.sentinel.slots.block.BlockException;
25+
import com.alibaba.csp.sentinel.util.StringUtil;
26+
import org.apache.hc.client5.http.classic.ExecChain;
27+
import org.apache.hc.client5.http.classic.ExecChainHandler;
28+
import org.apache.hc.core5.http.ClassicHttpRequest;
29+
import org.apache.hc.core5.http.ClassicHttpResponse;
30+
import org.apache.hc.core5.http.HttpException;
31+
32+
import java.io.IOException;
33+
34+
/**
35+
* Apache HttpClient 5.x adapter for Sentinel.
36+
*
37+
* <p>This handler implements {@link ExecChainHandler} to intercept outgoing HTTP requests
38+
* and protect them with Sentinel flow control.</p>
39+
*
40+
* <p>Usage example:</p>
41+
* <pre>{@code
42+
* CloseableHttpClient httpclient = HttpClients.custom()
43+
* .addExecInterceptorBefore(ChainElement.MAIN_TRANSPORT.name(), "sentinel",
44+
* new SentinelApacheHttpClient5Handler())
45+
* .build();
46+
* }</pre>
47+
*
48+
* @author qihuai.wyq
49+
*/
50+
public class SentinelApacheHttpClient5Handler implements ExecChainHandler {
51+
52+
private final SentinelApacheHttpClientConfig config;
53+
54+
public SentinelApacheHttpClient5Handler() {
55+
this.config = new SentinelApacheHttpClientConfig();
56+
}
57+
58+
public SentinelApacheHttpClient5Handler(SentinelApacheHttpClientConfig config) {
59+
this.config = config;
60+
}
61+
62+
@Override
63+
public ClassicHttpResponse execute(ClassicHttpRequest classicHttpRequest, ExecChain.Scope scope,
64+
ExecChain execChain) throws IOException, HttpException {
65+
String name = config.getExtractor().extractor(classicHttpRequest);
66+
if (StringUtil.isEmpty(name)) {
67+
return execChain.proceed(classicHttpRequest, scope);
68+
}
69+
70+
if (StringUtil.isNotEmpty(config.getPrefix())) {
71+
name = config.getPrefix() + name;
72+
}
73+
74+
Entry entry = null;
75+
try {
76+
entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
77+
return execChain.proceed(classicHttpRequest, scope);
78+
} catch (BlockException e) {
79+
return config.getFallback().handle(classicHttpRequest, e);
80+
} catch (IOException | HttpException | RuntimeException e) {
81+
Tracer.traceEntry(e, entry);
82+
throw e;
83+
} catch (Throwable t) {
84+
Tracer.traceEntry(t, entry);
85+
throw new RuntimeException(t);
86+
} finally {
87+
if (entry != null) {
88+
entry.exit();
89+
}
90+
}
91+
}
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 1999-2020 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.apache.httpclient5.config;
17+
18+
import com.alibaba.csp.sentinel.adapter.apache.httpclient5.extractor.ApacheHttpClientResourceExtractor;
19+
import com.alibaba.csp.sentinel.adapter.apache.httpclient5.extractor.DefaultApacheHttpClientResourceExtractor;
20+
import com.alibaba.csp.sentinel.adapter.apache.httpclient5.fallback.ApacheHttpClientFallback;
21+
import com.alibaba.csp.sentinel.adapter.apache.httpclient5.fallback.DefaultApacheHttpClientFallback;
22+
import com.alibaba.csp.sentinel.util.AssertUtil;
23+
24+
/**
25+
* @author qihuai.wyq
26+
*/
27+
public class SentinelApacheHttpClientConfig {
28+
29+
private String prefix = "httpclient:";
30+
private ApacheHttpClientResourceExtractor extractor = new DefaultApacheHttpClientResourceExtractor();
31+
private ApacheHttpClientFallback fallback = new DefaultApacheHttpClientFallback();
32+
33+
public String getPrefix() {
34+
return prefix;
35+
}
36+
37+
public void setPrefix(String prefix) {
38+
AssertUtil.notNull(prefix, "prefix cannot be null");
39+
this.prefix = prefix;
40+
}
41+
42+
public ApacheHttpClientResourceExtractor getExtractor() {
43+
return extractor;
44+
}
45+
46+
public void setExtractor(ApacheHttpClientResourceExtractor extractor) {
47+
AssertUtil.notNull(extractor, "extractor cannot be null");
48+
this.extractor = extractor;
49+
}
50+
51+
public ApacheHttpClientFallback getFallback() {
52+
return fallback;
53+
}
54+
55+
public void setFallback(ApacheHttpClientFallback fallback) {
56+
AssertUtil.notNull(fallback, "fallback cannot be null");
57+
this.fallback = fallback;
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 1999-2020 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.apache.httpclient5.extractor;
17+
18+
import org.apache.hc.core5.http.ClassicHttpRequest;
19+
20+
/**
21+
* Extracts Sentinel resource name from an Apache HttpClient 5.x request.
22+
*
23+
* @author qihuai.wyq
24+
*/
25+
public interface ApacheHttpClientResourceExtractor {
26+
27+
/**
28+
* Extract resource name from the given request.
29+
*
30+
* @param request the HTTP request
31+
* @return the resource name, or {@code null}/{@code ""} to skip Sentinel protection
32+
*/
33+
String extractor(ClassicHttpRequest request);
34+
}

0 commit comments

Comments
 (0)