Skip to content

Commit

Permalink
Adapter: Support Apache HttpClient (#1455)
Browse files Browse the repository at this point in the history
Introduce support through a customized client builder `SentinelApacheHttpClientBuilder`.
  • Loading branch information
zhaoyuguang authored Jul 9, 2020
1 parent 4f31b2c commit 38911c5
Show file tree
Hide file tree
Showing 19 changed files with 862 additions and 0 deletions.
1 change: 1 addition & 0 deletions sentinel-adapter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<module>sentinel-web-servlet</module>
<module>sentinel-dubbo-adapter</module>
<module>sentinel-apache-dubbo-adapter</module>
<module>sentinel-apache-httpclient-adapter</module>
<module>sentinel-sofa-rpc-adapter</module>
<module>sentinel-grpc-adapter</module>
<module>sentinel-zuul-adapter</module>
Expand Down
75 changes: 75 additions & 0 deletions sentinel-adapter/sentinel-apache-httpclient-adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Sentinel Apache Httpclient Adapter

## Introduction

Sentinel provides integration for OkHttp client to enable flow control for web requests.

Add the following dependency in `pom.xml` (if you are using Maven):

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

We can use the `SentinelApacheHttpClientBuilder` when `CloseableHttpClient` at initialization, for example:

```java
CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder().build();
```

If we want to add some additional configurations, we can refer to the following code

```java
HttpClientBuilder builder = new SentinelApacheHttpClientBuilder();
//builder Other Definitions
CloseableHttpClient httpclient = builder.build();
```

## Configuration

- `SentinelApacheHttpClientConfig` 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` |

### extractor (resource extractor)

We can define `ApacheHttpClientResourceExtractor` to customize resource extractor replace `DefaultApacheHttpClientResourceExtractor` at `SentinelApacheHttpClientBuilder` default config, for example: httpclient:GET:/httpclient/back/1 ==> httpclient:GET:/httpclient/back/{id}

```java
SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig();
config.setExtractor(new ApacheHttpClientResourceExtractor() {

@Override
public String extractor(HttpRequestWrapper request) {
String contains = "/httpclient/back/";
String uri = request.getRequestLine().getUri();
if (uri.startsWith(contains)) {
uri = uri.substring(0, uri.indexOf(contains) + contains.length()) + "{id}";
}
return request.getMethod() + ":" + uri;
}
});
CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder(config).build();
```

### fallback (Block handling)

We can define `ApacheHttpClientFallback` at `SentinelApacheHttpClientBuilder` default config, to handle request is blocked according to the actual scenario, for example:

```java
public class DefaultApacheHttpClientFallback implements ApacheHttpClientFallback {

@Override
public CloseableHttpResponse handle(HttpRequestWrapper request, BlockException e) {
// Just wrap and throw the exception.
throw new SentinelRpcException(e);
}
}
```
69 changes: 69 additions & 0 deletions sentinel-adapter/sentinel-apache-httpclient-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>1.8.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

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

<properties>
<apache.httpclient.version>4.5.6</apache.httpclient.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</groupId>
<artifactId>httpclient</artifactId>
<version>${apache.httpclient.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,76 @@
/*
* 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.httpclient;

import com.alibaba.csp.sentinel.*;
import com.alibaba.csp.sentinel.adapter.apache.httpclient.config.SentinelApacheHttpClientConfig;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.apache.http.HttpException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.execchain.ClientExecChain;

import java.io.IOException;

/**
* @author zhaoyuguang
*/
public class SentinelApacheHttpClientBuilder extends HttpClientBuilder {

private final SentinelApacheHttpClientConfig config;

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

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

@Override
protected ClientExecChain decorateMainExec(final ClientExecChain mainExec) {
return new ClientExecChain() {
@Override
public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request,
HttpClientContext clientContext, HttpExecutionAware execAware)
throws IOException, HttpException {
Entry entry = null;
try {
String name = config.getExtractor().extractor(request);
if (!StringUtil.isEmpty(config.getPrefix())) {
name = config.getPrefix() + name;
}
entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
return mainExec.execute(route, request, clientContext, execAware);
} catch (BlockException e) {
return config.getFallback().handle(request, e);
} catch (Throwable t) {
Tracer.traceEntry(t, entry);
throw 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.httpclient.config;

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

/**
* @author zhaoyuguang
*/
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,26 @@
/*
* 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.httpclient.extractor;

import org.apache.http.client.methods.HttpRequestWrapper;

/**
* @author zhaoyuguang
*/
public interface ApacheHttpClientResourceExtractor {

String extractor(HttpRequestWrapper request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.httpclient.extractor;

import org.apache.http.client.methods.HttpRequestWrapper;

/**
* @author zhaoyuguang
*/
public class DefaultApacheHttpClientResourceExtractor implements ApacheHttpClientResourceExtractor {

@Override
public String extractor(HttpRequestWrapper request) {
return request.getRequestLine().getUri();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.httpclient.fallback;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.protocol.HttpContext;

import java.io.IOException;

/**
* @author zhaoyuguang
*/
public interface ApacheHttpClientFallback {

CloseableHttpResponse handle(HttpRequestWrapper request, BlockException e);
}
Loading

0 comments on commit 38911c5

Please sign in to comment.