Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Apache HttpClient integration #1455

Merged
merged 6 commits into from
Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -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
64 changes: 64 additions & 0 deletions sentinel-adapter/sentinel-apache-httpclient-adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 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();
```

## Configuration

- `SentinelApacheHttpClientConfig` configuration:

| name | description | type | default value |
|------|------------|------|-------|
| prefix | custom resource prefix | `String` | `httpclient:` |
| extractor | custom resource extractor | `ApacheHttpClientResourceExtractor` | `DefaultApacheHttpClientResourceExtractor` |
| fallback | handle request when it is blocked | `ApacheHttpClientFallback` | `DefaultApacheHttpClientFallback` |

### extractor (resource extractor)

We can define `ApacheHttpClientResourceExtractor` to custom resource extractor replace `DefaultApacheHttpClientResourceExtractor`, for example: httpclient:GET:/httpclient/back/1 ==> httpclient:GET:/httpclient/back/{id}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

custom should be customize

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


```java
SentinelApacheHttpClientConfig.setExtractor(new ApacheHttpClientResourceExtractor() {

@Override
public String extractor(String method, String uri, HttpRequestWrapper request) {
String regex = "/httpclient/back/";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a regular expression but a PREFIX. Better to fix the naming and replace contains with startsWith.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

if (uri.contains(regex)) {
uri = uri.substring(0, uri.indexOf(regex) + regex.length()) + "{id}";
}
return method + ":" + uri;
}
});
```

### fallback (Block handling)

We can define `ApacheHttpClientFallback` 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,67 @@
/*
* 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 {

@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 = SentinelApacheHttpClientConfig.getExtractor().extractor(request.getMethod(),
request.getRequestLine().getUri(), request);
if (!StringUtil.isEmpty(SentinelApacheHttpClientConfig.getPrefix())) {
name = SentinelApacheHttpClientConfig.getPrefix() + name;
}
entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
return mainExec.execute(route, request, clientContext, execAware);
} catch (BlockException e) {
return SentinelApacheHttpClientConfig.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 final class SentinelApacheHttpClientConfig {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design for additional configuration is quite straightforward. We can't have different configurations between different clients in this design.

Adapters should follow the ways what they are adapted to follow. HttpClient way is that we can do kinds of decoration, configurations of sockets / interpreters / connection manager / etc. , before we generate the client instance while the client will focus on executing requests. So we should follow this design instead of global shared configuration. It should be extended as new methods of SentinelApacheHttpClientBuilder.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converted to an internal property of the Builder


private static volatile String prefix = "httpclient:";
private static volatile ApacheHttpClientResourceExtractor extractor = new DefaultApacheHttpClientResourceExtractor();
private static volatile ApacheHttpClientFallback fallback = new DefaultApacheHttpClientFallback();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And surely we will not need the default ones if we implement it as per instance configuration.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converted to an internal property of the Builder


public static String getPrefix() {
return prefix;
}

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

public static ApacheHttpClientResourceExtractor getExtractor() {
return extractor;
}

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

public static ApacheHttpClientFallback getFallback() {
return fallback;
}

public static void setFallback(ApacheHttpClientFallback fallback) {
AssertUtil.notNull(fallback, "fallback cannot be null");
SentinelApacheHttpClientConfig.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(String method, String uri, HttpRequestWrapper request);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion both parameter #1 and #2 has been wrapped in parameter #3 they are unnecessary.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first & two parameters are removed

}
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(String method, String uri, HttpRequestWrapper request) {
return method + ":" + uri;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the default implementation i can't quite agree with including METHOD in resource name.
Please go for advices from other reviewers, too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

method has remove

}
}
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