Skip to content
Closed
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 @@ -20,6 +20,7 @@
<module>sentinel-apache-dubbo-adapter</module>
<module>sentinel-apache-dubbo3-adapter</module>
<module>sentinel-apache-httpclient-adapter</module>
<module>sentinel-apache-httpclient5-adapter</module>
<module>sentinel-sofa-rpc-adapter</module>
<module>sentinel-grpc-adapter</module>
<module>sentinel-zuul-adapter</module>
Expand Down
77 changes: 77 additions & 0 deletions sentinel-adapter/sentinel-apache-httpclient5-adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Sentinel Apache HttpClient 5.x Adapter

## Introduction

Sentinel provides integration for Apache HttpClient 5.x to enable flow control for outgoing HTTP requests.

## Usage

### Add dependency

```xml
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-httpclient5-adapter</artifactId>
<version>x.y.z</version>
</dependency>
```

### Build the HttpClient

```java
CloseableHttpClient httpclient = HttpClients.custom()
.addExecInterceptorBefore(ChainElement.MAIN_TRANSPORT.name(), "sentinel",
new SentinelApacheHttpClient5Handler())
.build();
```

Or with custom configuration:

```java
SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig();
config.setPrefix("httpclient:");
config.setExtractor(myExtractor);
config.setFallback(myFallback);

CloseableHttpClient httpclient = HttpClients.custom()
.addExecInterceptorBefore(ChainElement.MAIN_TRANSPORT.name(), "sentinel",
new SentinelApacheHttpClient5Handler(config))
.build();
```

### Configuration

| Name | Description | Type | Default Value |
|------|------------|------|---------------|
| prefix | Customize resource prefix | `String` | `httpclient:` |
| extractor | Customize resource extractor | `ApacheHttpClientResourceExtractor` | `DefaultApacheHttpClientResourceExtractor` |
| fallback | Handle request when it is blocked | `ApacheHttpClientFallback` | `DefaultApacheHttpClientFallback` |

### Resource Extractor

The default extractor generates resource names in the format `METHOD:url` (e.g. `GET:/api/users`),
with query parameters and fragments stripped. You can customize this by implementing `ApacheHttpClientResourceExtractor`:

```java
public class MyResourceExtractor implements ApacheHttpClientResourceExtractor {
@Override
public String extractor(ClassicHttpRequest request) {
// custom resource name extraction logic
return request.getMethod() + ":" + request.getRequestUri();
}
}
```

### Fallback

The default fallback throws `SentinelRpcException`. You can customize the behavior:

```java
public class MyFallback implements ApacheHttpClientFallback {
@Override
public ClassicHttpResponse handle(ClassicHttpRequest request, BlockException e) {
// return a custom response or throw exception
throw new SentinelRpcException(e);
}
}
```
69 changes: 69 additions & 0 deletions sentinel-adapter/sentinel-apache-httpclient5-adapter/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?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>
<artifactId>sentinel-adapter</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>2.0.0-alpha2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>sentinel-apache-httpclient5-adapter</artifactId>
<packaging>jar</packaging>

<properties>
<apache.httpclient5.version>5.1</apache.httpclient5.version>
<spring.boot.version>2.1.3.RELEASE</spring.boot.version>
<spring-test.version>5.1.5.RELEASE</spring-test.version>
</properties>

<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>${apache.httpclient5.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>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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.apache.httpclient5;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.ResourceTypeConstants;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.adapter.apache.httpclient5.config.SentinelApacheHttpClientConfig;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecChainHandler;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpException;

import java.io.IOException;

/**
* Apache HttpClient 5.x adapter for Sentinel.
*
* <p>This handler implements {@link ExecChainHandler} to intercept outgoing HTTP requests
* and protect them with Sentinel flow control.</p>
*
* <p>Usage example:</p>
* <pre>{@code
* CloseableHttpClient httpclient = HttpClients.custom()
* .addExecInterceptorBefore(ChainElement.MAIN_TRANSPORT.name(), "sentinel",
* new SentinelApacheHttpClient5Handler())
* .build();
* }</pre>
*
* @author qihuai.wyq
*/
public class SentinelApacheHttpClient5Handler implements ExecChainHandler {

private final SentinelApacheHttpClientConfig config;

public SentinelApacheHttpClient5Handler() {
this.config = new SentinelApacheHttpClientConfig();
}

public SentinelApacheHttpClient5Handler(SentinelApacheHttpClientConfig config) {
this.config = config;
}

@Override
public ClassicHttpResponse execute(ClassicHttpRequest classicHttpRequest, ExecChain.Scope scope,
ExecChain execChain) throws IOException, HttpException {
String name = config.getExtractor().extractor(classicHttpRequest);
if (StringUtil.isEmpty(name)) {
return execChain.proceed(classicHttpRequest, scope);
}

if (StringUtil.isNotEmpty(config.getPrefix())) {
name = config.getPrefix() + name;
}

Entry entry = null;
try {
entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
return execChain.proceed(classicHttpRequest, scope);
} catch (BlockException e) {
return config.getFallback().handle(classicHttpRequest, e);
} catch (IOException | HttpException | RuntimeException e) {
Tracer.traceEntry(e, entry);
throw e;
} catch (Throwable t) {
Tracer.traceEntry(t, entry);
throw new RuntimeException(t);
} finally {
if (entry != null) {
entry.exit();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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.apache.httpclient5.config;

import com.alibaba.csp.sentinel.adapter.apache.httpclient5.extractor.ApacheHttpClientResourceExtractor;
import com.alibaba.csp.sentinel.adapter.apache.httpclient5.extractor.DefaultApacheHttpClientResourceExtractor;
import com.alibaba.csp.sentinel.adapter.apache.httpclient5.fallback.ApacheHttpClientFallback;
import com.alibaba.csp.sentinel.adapter.apache.httpclient5.fallback.DefaultApacheHttpClientFallback;
import com.alibaba.csp.sentinel.util.AssertUtil;

/**
* @author qihuai.wyq
*/
public class SentinelApacheHttpClientConfig {

private String prefix = "httpclient:";
private ApacheHttpClientResourceExtractor extractor = new DefaultApacheHttpClientResourceExtractor();
private ApacheHttpClientFallback fallback = new DefaultApacheHttpClientFallback();

public String getPrefix() {
return prefix;
}

public void setPrefix(String prefix) {
AssertUtil.notNull(prefix, "prefix cannot be null");
this.prefix = prefix;
}

public ApacheHttpClientResourceExtractor getExtractor() {
return extractor;
}

public void setExtractor(ApacheHttpClientResourceExtractor extractor) {
AssertUtil.notNull(extractor, "extractor cannot be null");
this.extractor = extractor;
}

public ApacheHttpClientFallback getFallback() {
return fallback;
}

public void setFallback(ApacheHttpClientFallback fallback) {
AssertUtil.notNull(fallback, "fallback cannot be null");
this.fallback = fallback;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.apache.httpclient5.extractor;

import org.apache.hc.core5.http.ClassicHttpRequest;

/**
* Extracts Sentinel resource name from an Apache HttpClient 5.x request.
*
* @author qihuai.wyq
*/
public interface ApacheHttpClientResourceExtractor {

/**
* Extract resource name from the given request.
*
* @param request the HTTP request
* @return the resource name, or {@code null}/{@code ""} to skip Sentinel protection
*/
String extractor(ClassicHttpRequest request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.apache.httpclient5.extractor;

import com.alibaba.csp.sentinel.log.RecordLog;
import org.apache.hc.core5.http.ClassicHttpRequest;

/**
* Default implementation of {@link ApacheHttpClientResourceExtractor}.
*
* <p>Generates resource name in the format {@code METHOD:url}, with query string
* and fragment stripped. This is consistent with the OkHttp adapter's resource naming
* convention.</p>
*
* @author qihuai.wyq
*/
public class DefaultApacheHttpClientResourceExtractor implements ApacheHttpClientResourceExtractor {

@Override
public String extractor(ClassicHttpRequest request) {
try {
String httpMethod = request.getMethod();
String originalUrl = request.getUri().toString();
int firstIndexOfQuery = originalUrl.indexOf('?');
int firstIndexOfFragment = originalUrl.indexOf('#');
if (firstIndexOfFragment < 0 && firstIndexOfQuery < 0) {
return httpMethod + ":" + originalUrl;
}
if (firstIndexOfFragment > 0 && firstIndexOfQuery > 0) {
int pos = Math.min(firstIndexOfQuery, firstIndexOfFragment);
return httpMethod + ":" + originalUrl.substring(0, pos);
} else if (firstIndexOfQuery > 0) {
return httpMethod + ":" + originalUrl.substring(0, firstIndexOfQuery);
} else {
return httpMethod + ":" + originalUrl.substring(0, firstIndexOfFragment);
}
} catch (Exception ex) {
RecordLog.warn("Failed to extract resource name of HttpClient 5 request, request={}", request, ex);
return null;
}
}
}
Loading
Loading