Skip to content

Commit 1375ff9

Browse files
authored
feat: add Apache HttpClient 5.x adapter support (#3614)
* 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> * chore: bump version to 1.8.10-SNAPSHOT After the 1.8.9 release, several PRs have been merged but the development version was not updated. Bump to 1.8.10-SNAPSHOT for next development iteration. Change-Id: I2b7ba2b5b4e9e39d1f9749288d54b9926a512132 Co-developed-by: Cursor <noreply@cursor.com> * fix: fix markdown table format to pass document-lint Change-Id: I1fe4682e41185dc7c8ed30541989f2881ba87549 Co-developed-by: Cursor <noreply@cursor.com> * fix: update @author from qihuai.wyq to uuuyuqi Change-Id: Ie28f99434badfc00decc914ffd27f83c837d947d Co-developed-by: Cursor <noreply@cursor.com> * chore: remove accidentally committed .vscode/settings.json Change-Id: I60caec5b17e92a79827671e531a0818f61163b48 Co-developed-by: Cursor <noreply@cursor.com> * docs: add HC4 to HC5 migration notes for resource naming Document the default resource name format difference between HC4 and HC5 adapters so users understand that existing flow control rules will not match automatically after migration, and provide a snippet for preserving the legacy URI-only naming via a custom extractor. Change-Id: Iadacf3d3288acb4b23ce67441a88c0b8ad0f0b6e Co-developed-by: Claude <noreply@anthropic.com>
1 parent 38b4619 commit 1375ff9

16 files changed

Lines changed: 925 additions & 1 deletion

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
</issueManagement>
4141

4242
<properties>
43-
<revision>1.8.9</revision>
43+
<revision>1.8.10-SNAPSHOT</revision>
4444
<!-- Compile libs -->
4545
<fastjson.version>1.2.83_noneautotype</fastjson.version>
4646
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>

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: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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+
### Migration from Apache HttpClient 4.x Adapter
66+
67+
> **Note:** The default resource naming convention differs between the HC4 and HC5 adapters. If you are migrating from
68+
> `sentinel-apache-httpclient-adapter` to `sentinel-apache-httpclient5-adapter`, please review your existing flow control
69+
> rules carefully — they will **not** match automatically.
70+
71+
| Adapter | Default Resource Name Format | Example |
72+
| --- | --- | --- |
73+
| `sentinel-apache-httpclient-adapter` (HC4) | URI only (path, may include query) | `httpclient:/api/users` |
74+
| `sentinel-apache-httpclient5-adapter` (HC5) | `METHOD:full_url` (query/fragment stripped) | `httpclient:GET:http://example.com/api/users` |
75+
76+
Both adapters share the same default resource prefix (`httpclient:`), but the resource name suffix is incompatible.
77+
This means any flow control rules configured against HC4 resource names will no longer take effect after migration.
78+
79+
You have two options:
80+
81+
1. **Update your rules** to match the new HC5 resource name format (recommended — aligns with the OkHttp adapter and
82+
provides better granularity by including the HTTP method).
83+
2. **Preserve the old format** by supplying a custom `ApacheHttpClientResourceExtractor` that returns only the URI path,
84+
e.g.:
85+
86+
```java
87+
SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig();
88+
config.setExtractor(request -> request.getRequestUri());
89+
```
90+
91+
### Fallback
92+
93+
The default fallback throws `SentinelRpcException`. You can customize the behavior:
94+
95+
```java
96+
public class MyFallback implements ApacheHttpClientFallback {
97+
@Override
98+
public ClassicHttpResponse handle(ClassicHttpRequest request, BlockException e) {
99+
// return a custom response or throw exception
100+
throw new SentinelRpcException(e);
101+
}
102+
}
103+
```
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 uuuyuqi
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 uuuyuqi
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 uuuyuqi
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)