init
This commit is contained in:
79
sentinel/sentinel-adapter/pom.xml
Normal file
79
sentinel/sentinel-adapter/pom.xml
Normal file
@@ -0,0 +1,79 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parent</artifactId>
|
||||
<version>1.8.3</version>
|
||||
</parent>
|
||||
<artifactId>sentinel-adapter</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>sentinel-adapter</name>
|
||||
<description>The adapters of Sentinel</description>
|
||||
|
||||
<modules>
|
||||
<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>
|
||||
<module>sentinel-reactor-adapter</module>
|
||||
<module>sentinel-spring-webflux-adapter</module>
|
||||
<module>sentinel-api-gateway-adapter-common</module>
|
||||
<module>sentinel-spring-cloud-gateway-adapter</module>
|
||||
<module>sentinel-spring-webmvc-adapter</module>
|
||||
<module>sentinel-zuul2-adapter</module>
|
||||
<module>sentinel-okhttp-adapter</module>
|
||||
<module>sentinel-jax-rs-adapter</module>
|
||||
<module>sentinel-quarkus-adapter</module>
|
||||
<module>sentinel-motan-adapter</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-extension</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-web-servlet</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-reactor-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
||||
@@ -0,0 +1,72 @@
|
||||
# Sentinel Apache Dubbo Adapter (for 2.7.x+)
|
||||
|
||||
> Note: 中文文档请见[此处](https://github.com/alibaba/Sentinel/wiki/主流框架的适配#dubbo)。
|
||||
|
||||
Sentinel Dubbo Adapter provides service consumer filter and provider filter
|
||||
for [Apache Dubbo](https://dubbo.apache.org/en-us/) services.
|
||||
|
||||
**Note: This adapter only supports Apache Dubbo 2.7.x and above.** For legacy `com.alibaba:dubbo` 2.6.x,
|
||||
please use `sentinel-dubbo-adapter` module instead.
|
||||
|
||||
To use Sentinel Dubbo Adapter, you can simply add the following dependency to your `pom.xml`:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-apache-dubbo-adapter</artifactId>
|
||||
<version>x.y.z</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
The Sentinel filters are **enabled by default**. Once you add the dependency,
|
||||
the Dubbo services and methods will become protected resources in Sentinel,
|
||||
which can leverage Sentinel's flow control and guard ability when rules are configured.
|
||||
Demos can be found in [sentinel-demo-apache-dubbo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-apache-dubbo).
|
||||
|
||||
If you don't want the filters enabled, you can manually disable them. For example:
|
||||
|
||||
```xml
|
||||
<dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/>
|
||||
|
||||
<dubbo:provider filter="-sentinel.dubbo.provider.filter"/>
|
||||
```
|
||||
|
||||
For more details of Dubbo filter, see [here](http://dubbo.apache.org/en-us/docs/dev/impls/filter.html).
|
||||
|
||||
## Dubbo resources
|
||||
|
||||
The resource for Dubbo services has two granularities: service interface and service method.
|
||||
|
||||
- Service interface: resourceName format is `interfaceName`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService`
|
||||
- Service method: resourceName format is `interfaceName:methodSignature`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)`
|
||||
|
||||
## Flow control based on caller
|
||||
|
||||
In many circumstances, it's also significant to control traffic flow based on the **caller**.
|
||||
For example, assuming that there are two services A and B, both of them initiate remote call requests to the service provider.
|
||||
If we want to limit the calls from service B only, we can set the `limitApp` of flow rule as the identifier of service B (e.g. service name).
|
||||
|
||||
Sentinel Dubbo Adapter will automatically resolve the Dubbo consumer's *application name* as the caller's name (`origin`),
|
||||
and will bring the caller's name when doing resource protection.
|
||||
If `limitApp` of flow rules is not configured (`default`), flow control will take effects on all callers.
|
||||
If `limitApp` of a flow rule is configured with a caller, then the corresponding flow rule will only take effect on the specific caller.
|
||||
|
||||
> Note: Dubbo consumer does not provide its Dubbo application name when doing RPC,
|
||||
> so developers should manually put the application name into *attachment* at consumer side,
|
||||
> then extract it at provider side. Sentinel Dubbo Adapter has implemented a filter (`DubboAppContextFilter`)
|
||||
> where consumer can carry application name information to provider automatically.
|
||||
> If the consumer does not use Sentinel Dubbo Adapter but requires flow control based on caller,
|
||||
> developers can manually put the application name into attachment with the key `dubboApplication`.
|
||||
>
|
||||
> Since 1.8.0, the adapter provides support for customizing origin parsing logic. You may register your own `DubboOriginParser`
|
||||
> implementation to `DubboAdapterGlobalConfig`.
|
||||
|
||||
## Global fallback
|
||||
|
||||
Sentinel Dubbo Adapter supports global fallback configuration.
|
||||
The global fallback will handle exceptions and give replacement result when blocked by
|
||||
flow control, degrade or system load protection. You can implement your own `DubboFallback` interface
|
||||
and then register to `DubboAdapterGlobalConfig`.
|
||||
If no fallback is configured, Sentinel will wrap the `BlockException` as the fallback result.
|
||||
|
||||
Besides, we can also leverage [Dubbo mock mechanism](http://dubbo.apache.org/en-us/docs/user/demos/local-mock.html) to provide fallback implementation of degraded Dubbo services.
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>sentinel-apache-dubbo-adapter</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<java.source.version>1.8</java.source.version>
|
||||
<java.target.version>1.8</java.target.version>
|
||||
<apache.dubbo.version>2.7.13</apache.dubbo.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>${apache.dubbo.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>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
|
||||
import org.apache.dubbo.rpc.Filter;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
|
||||
/**
|
||||
* Base class of the {@link SentinelDubboProviderFilter} and {@link SentinelDubboConsumerFilter}.
|
||||
*
|
||||
* @author Zechao Zheng
|
||||
*/
|
||||
public abstract class BaseSentinelDubboFilter implements Filter {
|
||||
|
||||
|
||||
/**
|
||||
* Get method name of dubbo rpc
|
||||
*
|
||||
* @param invoker
|
||||
* @param invocation
|
||||
* @return
|
||||
*/
|
||||
abstract String getMethodName(Invoker invoker, Invocation invocation, String prefix);
|
||||
|
||||
/**
|
||||
* Get interface name of dubbo rpc
|
||||
*
|
||||
* @param invoker
|
||||
* @return
|
||||
*/
|
||||
abstract String getInterfaceName(Invoker invoker, String prefix);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.Filter;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
|
||||
|
||||
/**
|
||||
* Puts current consumer's application name in the attachment of each invocation.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@Activate(group = CONSUMER)
|
||||
public class DubboAppContextFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
String application = invoker.getUrl().getParameter(CommonConstants.APPLICATION_KEY);
|
||||
if (application != null) {
|
||||
RpcContext.getContext().setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, application);
|
||||
}
|
||||
return invoker.invoke(invocation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public final class DubboUtils {
|
||||
|
||||
public static final String SENTINEL_DUBBO_APPLICATION_KEY = "dubboApplication";
|
||||
|
||||
public static String getApplication(Invocation invocation, String defaultValue) {
|
||||
if (invocation == null || invocation.getAttachments() == null) {
|
||||
throw new IllegalArgumentException("Bad invocation instance");
|
||||
}
|
||||
return invocation.getAttachment(SENTINEL_DUBBO_APPLICATION_KEY, defaultValue);
|
||||
}
|
||||
|
||||
public static String getMethodResourceName(Invoker<?> invoker, Invocation invocation){
|
||||
return getMethodResourceName(invoker, invocation, false);
|
||||
}
|
||||
|
||||
public static String getMethodResourceName(Invoker<?> invoker, Invocation invocation, Boolean useGroupAndVersion) {
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
String interfaceResource = useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName();
|
||||
buf.append(interfaceResource)
|
||||
.append(":")
|
||||
.append(invocation.getMethodName())
|
||||
.append("(");
|
||||
boolean isFirst = true;
|
||||
for (Class<?> clazz : invocation.getParameterTypes()) {
|
||||
if (!isFirst) {
|
||||
buf.append(",");
|
||||
}
|
||||
buf.append(clazz.getName());
|
||||
isFirst = false;
|
||||
}
|
||||
buf.append(")");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String getMethodResourceName(Invoker<?> invoker, Invocation invocation, String prefix) {
|
||||
if (StringUtil.isNotBlank(prefix)) {
|
||||
return new StringBuilder(64)
|
||||
.append(prefix)
|
||||
.append(getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled()))
|
||||
.toString();
|
||||
} else {
|
||||
return getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String getInterfaceName(Invoker invoker) {
|
||||
return getInterfaceName(invoker, false);
|
||||
}
|
||||
|
||||
public static String getInterfaceName(Invoker<?> invoker, Boolean useGroupAndVersion) {
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
return useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName();
|
||||
}
|
||||
|
||||
public static String getInterfaceName(Invoker<?> invoker, String prefix) {
|
||||
if (StringUtil.isNotBlank(prefix)) {
|
||||
return new StringBuilder(64)
|
||||
.append(prefix)
|
||||
.append(getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled()))
|
||||
.toString();
|
||||
} else {
|
||||
return getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private DubboUtils() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.*;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.*;
|
||||
import org.apache.dubbo.rpc.support.RpcUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
|
||||
|
||||
/**
|
||||
* <p>Dubbo service consumer filter for Sentinel. Auto activated by default.</p>
|
||||
* <p>
|
||||
* If you want to disable the consumer filter, you can configure:
|
||||
* <pre>
|
||||
* <dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/>
|
||||
* </pre>
|
||||
*
|
||||
* @author Carpenter Lee
|
||||
* @author Eric Zhao
|
||||
* @author Lin Liang
|
||||
*/
|
||||
@Activate(group = CONSUMER)
|
||||
public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter {
|
||||
|
||||
public SentinelDubboConsumerFilter() {
|
||||
RecordLog.info("Sentinel Apache Dubbo consumer filter initialized");
|
||||
}
|
||||
|
||||
@Override
|
||||
String getMethodName(Invoker invoker, Invocation invocation, String prefix) {
|
||||
return DubboUtils.getMethodResourceName(invoker, invocation, prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getInterfaceName(Invoker invoker, String prefix) {
|
||||
return DubboUtils.getInterfaceName(invoker, prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation);
|
||||
if (InvokeMode.SYNC == invokeMode) {
|
||||
return syncInvoke(invoker, invocation);
|
||||
} else {
|
||||
return asyncInvoke(invoker, invocation);
|
||||
}
|
||||
}
|
||||
|
||||
private Result syncInvoke(Invoker<?> invoker, Invocation invocation) {
|
||||
Entry interfaceEntry = null;
|
||||
Entry methodEntry = null;
|
||||
String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey();
|
||||
String interfaceResourceName = getInterfaceName(invoker, prefix);
|
||||
String methodResourceName = getMethodName(invoker, invocation, prefix);
|
||||
try {
|
||||
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT);
|
||||
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT,
|
||||
invocation.getArguments());
|
||||
Result result = invoker.invoke(invocation);
|
||||
if (result.hasException()) {
|
||||
Tracer.traceEntry(result.getException(), interfaceEntry);
|
||||
Tracer.traceEntry(result.getException(), methodEntry);
|
||||
}
|
||||
return result;
|
||||
} catch (BlockException e) {
|
||||
return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e);
|
||||
} catch (RpcException e) {
|
||||
Tracer.traceEntry(e, interfaceEntry);
|
||||
Tracer.traceEntry(e, methodEntry);
|
||||
throw e;
|
||||
} finally {
|
||||
if (methodEntry != null) {
|
||||
methodEntry.exit(1, invocation.getArguments());
|
||||
}
|
||||
if (interfaceEntry != null) {
|
||||
interfaceEntry.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Result asyncInvoke(Invoker<?> invoker, Invocation invocation) {
|
||||
LinkedList<EntryHolder> queue = new LinkedList<>();
|
||||
String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey();
|
||||
String interfaceResourceName = getInterfaceName(invoker, prefix);
|
||||
String methodResourceName = getMethodName(invoker, invocation, prefix);
|
||||
try {
|
||||
queue.push(new EntryHolder(
|
||||
SphU.asyncEntry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT), null));
|
||||
queue.push(new EntryHolder(
|
||||
SphU.asyncEntry(methodResourceName, ResourceTypeConstants.COMMON_RPC,
|
||||
EntryType.OUT, 1, invocation.getArguments()), invocation.getArguments()));
|
||||
Result result = invoker.invoke(invocation);
|
||||
result.whenCompleteWithContext((r, throwable) -> {
|
||||
Throwable error = throwable;
|
||||
if (error == null) {
|
||||
error = Optional.ofNullable(r).map(Result::getException).orElse(null);
|
||||
}
|
||||
while (!queue.isEmpty()) {
|
||||
EntryHolder holder = queue.pop();
|
||||
Tracer.traceEntry(error, holder.entry);
|
||||
exitEntry(holder);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
} catch (BlockException e) {
|
||||
while (!queue.isEmpty()) {
|
||||
exitEntry(queue.pop());
|
||||
}
|
||||
return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e);
|
||||
}
|
||||
}
|
||||
|
||||
static class EntryHolder {
|
||||
|
||||
final private Entry entry;
|
||||
final private Object[] params;
|
||||
|
||||
public EntryHolder(Entry entry, Object[] params) {
|
||||
this.entry = entry;
|
||||
this.params = params;
|
||||
}
|
||||
}
|
||||
|
||||
private void exitEntry(EntryHolder holder) {
|
||||
if (holder.params != null) {
|
||||
holder.entry.exit(1, holder.params);
|
||||
} else {
|
||||
holder.entry.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.*;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;
|
||||
|
||||
/**
|
||||
* <p>Apache Dubbo service provider filter that enables integration with Sentinel. Auto activated by default.</p>
|
||||
* <p>Note: this only works for Apache Dubbo 2.7.x or above version.</p>
|
||||
* <p>
|
||||
* If you want to disable the provider filter, you can configure:
|
||||
* <pre>
|
||||
* <dubbo:provider filter="-sentinel.dubbo.provider.filter"/>
|
||||
* </pre>
|
||||
*
|
||||
* @author Carpenter Lee
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@Activate(group = PROVIDER)
|
||||
public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter {
|
||||
|
||||
public SentinelDubboProviderFilter() {
|
||||
RecordLog.info("Sentinel Apache Dubbo provider filter initialized");
|
||||
}
|
||||
|
||||
@Override
|
||||
String getMethodName(Invoker invoker, Invocation invocation, String prefix) {
|
||||
return DubboUtils.getMethodResourceName(invoker, invocation, prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getInterfaceName(Invoker invoker, String prefix) {
|
||||
return DubboUtils.getInterfaceName(invoker, prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
// Get origin caller.
|
||||
String origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation);
|
||||
if (null == origin) {
|
||||
origin = "";
|
||||
}
|
||||
Entry interfaceEntry = null;
|
||||
Entry methodEntry = null;
|
||||
String prefix = DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey();
|
||||
String interfaceResourceName = getInterfaceName(invoker, prefix);
|
||||
String methodResourceName = getMethodName(invoker, invocation, prefix);
|
||||
try {
|
||||
// Only need to create entrance context at provider side, as context will take effect
|
||||
// at entrance of invocation chain only (for inbound traffic).
|
||||
ContextUtil.enter(methodResourceName, origin);
|
||||
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN);
|
||||
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN,
|
||||
invocation.getArguments());
|
||||
Result result = invoker.invoke(invocation);
|
||||
if (result.hasException()) {
|
||||
Tracer.traceEntry(result.getException(), interfaceEntry);
|
||||
Tracer.traceEntry(result.getException(), methodEntry);
|
||||
}
|
||||
return result;
|
||||
} catch (BlockException e) {
|
||||
return DubboAdapterGlobalConfig.getProviderFallback().handle(invoker, invocation, e);
|
||||
} catch (RpcException e) {
|
||||
Tracer.traceEntry(e, interfaceEntry);
|
||||
Tracer.traceEntry(e, methodEntry);
|
||||
throw e;
|
||||
} finally {
|
||||
if (methodEntry != null) {
|
||||
methodEntry.exit(1, invocation.getArguments());
|
||||
}
|
||||
if (interfaceEntry != null) {
|
||||
interfaceEntry.exit();
|
||||
}
|
||||
ContextUtil.exit();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.config;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DefaultDubboFallback;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallback;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.origin.DefaultDubboOriginParser;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.origin.DubboOriginParser;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Responsible for dubbo service provider, consumer attribute configuration
|
||||
* </p>
|
||||
*
|
||||
* @author lianglin
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public final class DubboAdapterGlobalConfig {
|
||||
|
||||
private static final String TRUE_STR = "true";
|
||||
|
||||
public static final String DUBBO_RES_NAME_WITH_PREFIX_KEY = "csp.sentinel.dubbo.resource.use.prefix";
|
||||
public static final String DUBBO_PROVIDER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.provider.prefix";
|
||||
public static final String DUBBO_CONSUMER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.consumer.prefix";
|
||||
|
||||
private static final String DEFAULT_DUBBO_PROVIDER_PREFIX = "dubbo:provider:";
|
||||
private static final String DEFAULT_DUBBO_CONSUMER_PREFIX = "dubbo:consumer:";
|
||||
|
||||
public static final String DUBBO_INTERFACE_GROUP_VERSION_ENABLED = "csp.sentinel.dubbo.interface.group.version.enabled";
|
||||
|
||||
private static volatile DubboFallback consumerFallback = new DefaultDubboFallback();
|
||||
private static volatile DubboFallback providerFallback = new DefaultDubboFallback();
|
||||
private static volatile DubboOriginParser originParser = new DefaultDubboOriginParser();
|
||||
|
||||
public static boolean isUsePrefix() {
|
||||
return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_RES_NAME_WITH_PREFIX_KEY));
|
||||
}
|
||||
|
||||
public static String getDubboProviderResNamePrefixKey() {
|
||||
if (isUsePrefix()) {
|
||||
String config = SentinelConfig.getConfig(DUBBO_PROVIDER_RES_NAME_PREFIX_KEY);
|
||||
return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_PROVIDER_PREFIX;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getDubboConsumerResNamePrefixKey() {
|
||||
if (isUsePrefix()) {
|
||||
String config = SentinelConfig.getConfig(DUBBO_CONSUMER_RES_NAME_PREFIX_KEY);
|
||||
return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_CONSUMER_PREFIX;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Boolean getDubboInterfaceGroupAndVersionEnabled() {
|
||||
return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED));
|
||||
}
|
||||
|
||||
public static DubboFallback getConsumerFallback() {
|
||||
return consumerFallback;
|
||||
}
|
||||
|
||||
public static void setConsumerFallback(DubboFallback consumerFallback) {
|
||||
AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null");
|
||||
DubboAdapterGlobalConfig.consumerFallback = consumerFallback;
|
||||
}
|
||||
|
||||
public static DubboFallback getProviderFallback() {
|
||||
return providerFallback;
|
||||
}
|
||||
|
||||
public static void setProviderFallback(DubboFallback providerFallback) {
|
||||
AssertUtil.notNull(providerFallback, "providerFallback cannot be null");
|
||||
DubboAdapterGlobalConfig.providerFallback = providerFallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the origin parser of Dubbo adapter.
|
||||
*
|
||||
* @return the origin parser
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public static DubboOriginParser getOriginParser() {
|
||||
return originParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the origin parser of Dubbo adapter.
|
||||
*
|
||||
* @param originParser the origin parser
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public static void setOriginParser(DubboOriginParser originParser) {
|
||||
AssertUtil.notNull(originParser, "originParser cannot be null");
|
||||
DubboAdapterGlobalConfig.originParser = originParser;
|
||||
}
|
||||
|
||||
private DubboAdapterGlobalConfig() {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.fallback;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
|
||||
import org.apache.dubbo.rpc.AsyncRpcResult;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class DefaultDubboFallback implements DubboFallback {
|
||||
|
||||
@Override
|
||||
public Result handle(Invoker<?> invoker, Invocation invocation, BlockException ex) {
|
||||
// Just wrap the exception.
|
||||
return AsyncRpcResult.newDefaultAsyncResult(ex.toRuntimeException(), invocation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.fallback;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
|
||||
/**
|
||||
* Fallback handler for Dubbo services.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DubboFallback {
|
||||
|
||||
/**
|
||||
* Handle the block exception and provide fallback result.
|
||||
*
|
||||
* @param invoker Dubbo invoker
|
||||
* @param invocation Dubbo invocation
|
||||
* @param ex block exception
|
||||
* @return fallback result
|
||||
*/
|
||||
Result handle(Invoker<?> invoker, Invocation invocation, BlockException ex);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.fallback;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig;
|
||||
|
||||
/**
|
||||
* <p>Global fallback registry for Dubbo.</p>
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @deprecated use {@link DubboAdapterGlobalConfig} instead since 1.8.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class DubboFallbackRegistry {
|
||||
|
||||
public static DubboFallback getConsumerFallback() {
|
||||
return DubboAdapterGlobalConfig.getConsumerFallback();
|
||||
}
|
||||
|
||||
public static void setConsumerFallback(DubboFallback consumerFallback) {
|
||||
DubboAdapterGlobalConfig.setConsumerFallback(consumerFallback);
|
||||
}
|
||||
|
||||
public static DubboFallback getProviderFallback() {
|
||||
return DubboAdapterGlobalConfig.getProviderFallback();
|
||||
}
|
||||
|
||||
public static void setProviderFallback(DubboFallback providerFallback) {
|
||||
DubboAdapterGlobalConfig.setProviderFallback(providerFallback);
|
||||
}
|
||||
|
||||
private DubboFallbackRegistry() {}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
* https://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.dubbo.origin;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
|
||||
/**
|
||||
* Default Dubbo origin parser.
|
||||
*
|
||||
* @author jingzian
|
||||
*/
|
||||
public class DefaultDubboOriginParser implements DubboOriginParser {
|
||||
|
||||
@Override
|
||||
public String parse(Invoker<?> invoker, Invocation invocation) {
|
||||
return DubboUtils.getApplication(invocation, "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.dubbo.origin;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
|
||||
/**
|
||||
* Customized origin parser for Dubbo provider filter.{@link Context#getOrigin()}
|
||||
*
|
||||
* @author jingzian
|
||||
*/
|
||||
public interface DubboOriginParser {
|
||||
|
||||
/**
|
||||
* Parses the origin (caller) from Dubbo invocation.
|
||||
*
|
||||
* @param invoker Dubbo invoker
|
||||
* @param invocation Dubbo invocation
|
||||
* @return the parsed origin
|
||||
*/
|
||||
String parse(Invoker<?> invoker, Invocation invocation);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
sentinel.dubbo.provider.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter
|
||||
sentinel.dubbo.consumer.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboConsumerFilter
|
||||
dubbo.application.context.name.filter=com.alibaba.csp.sentinel.adapter.dubbo.DubboAppContextFilter
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DefaultDubboFallback;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Base test class, provide common methods for subClass
|
||||
* The package is same as CtSph, to call CtSph.resetChainMap() method for test
|
||||
* <p>
|
||||
* Note: Only for test. DO NOT USE IN PRODUCTION!
|
||||
*
|
||||
* @author cdfive
|
||||
* @author lianglin
|
||||
*/
|
||||
public class BaseTest {
|
||||
|
||||
|
||||
/**
|
||||
* Clean up resources for context, clusterNodeMap, processorSlotChainMap
|
||||
*/
|
||||
public void cleanUpAll() {
|
||||
try {
|
||||
clearDubboContext();
|
||||
cleanUpCstContext();
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanUpCstContext() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
ClusterBuilderSlot.getClusterNodeMap().clear();
|
||||
CtSph.resetChainMap();
|
||||
Method method = ContextUtil.class.getDeclaredMethod("resetContextMap");
|
||||
method.setAccessible(true);
|
||||
method.invoke(null, null);
|
||||
ContextUtil.exit();
|
||||
FlowRuleManager.loadRules(new ArrayList<>());
|
||||
DegradeRuleManager.loadRules(new ArrayList<>());
|
||||
}
|
||||
|
||||
private void clearDubboContext() {
|
||||
SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false");
|
||||
DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback());
|
||||
RpcContext.removeContext();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author lianglin
|
||||
*/
|
||||
public class DubboTestUtil {
|
||||
|
||||
|
||||
public static Class<?> DEFAULT_TEST_SERVICE = DemoService.class;
|
||||
public static Method DEFAULT_TEST_METHOD_ONE = DEFAULT_TEST_SERVICE.getMethods()[0];
|
||||
public static Method DEFAULT_TEST_METHOD_TWO = DEFAULT_TEST_SERVICE.getMethods()[1];
|
||||
|
||||
public static Invoker getMockInvoker(URL url, Class<?> cls) {
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
when(invoker.getUrl()).thenReturn(url);
|
||||
when(invoker.getInterface()).thenReturn(cls);
|
||||
return invoker;
|
||||
}
|
||||
|
||||
public static Invoker getDefaultMockInvoker() {
|
||||
return getMockInvoker(getDefaultTestURL(), DEFAULT_TEST_SERVICE);
|
||||
}
|
||||
|
||||
public static Invocation getMockInvocation(Method method) {
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
when(invocation.getMethodName()).thenReturn(method.getName());
|
||||
when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
|
||||
return invocation;
|
||||
}
|
||||
|
||||
public static Invocation getDefaultMockInvocationOne() {
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
when(invocation.getMethodName()).thenReturn(DEFAULT_TEST_METHOD_ONE.getName());
|
||||
when(invocation.getParameterTypes()).thenReturn(DEFAULT_TEST_METHOD_ONE.getParameterTypes());
|
||||
return invocation;
|
||||
}
|
||||
|
||||
public static Invocation getDefaultMockInvocationTwo() {
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
when(invocation.getMethodName()).thenReturn(DEFAULT_TEST_METHOD_TWO.getName());
|
||||
when(invocation.getParameterTypes()).thenReturn(DEFAULT_TEST_METHOD_TWO.getParameterTypes());
|
||||
return invocation;
|
||||
}
|
||||
|
||||
public static URL getDefaultTestURL() {
|
||||
URL url = URL.valueOf("dubbo://127.0.0.1:2181")
|
||||
.addParameter(CommonConstants.VERSION_KEY, "1.0.0")
|
||||
.addParameter(CommonConstants.GROUP_KEY, "grp1")
|
||||
.addParameter(CommonConstants.INTERFACE_KEY, DEFAULT_TEST_SERVICE.getName());
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.BaseTest;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author cdfive
|
||||
*/
|
||||
public class DubboAppContextFilterTest extends BaseTest {
|
||||
|
||||
private DubboAppContextFilter filter = new DubboAppContextFilter();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cleanUpAll();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
cleanUpAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokeApplicationKey() {
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
URL url = URL.valueOf("test://test:111/test?application=serviceA");
|
||||
when(invoker.getUrl()).thenReturn(url);
|
||||
|
||||
filter.invoke(invoker, invocation);
|
||||
verify(invoker).invoke(invocation);
|
||||
|
||||
String application = RpcContext.getContext().getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY);
|
||||
assertEquals("serviceA", application);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokeNullApplicationKey() {
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
URL url = URL.valueOf("test://test:111/test?application=");
|
||||
when(invoker.getUrl()).thenReturn(url);
|
||||
|
||||
filter.invoke(invoker, invocation);
|
||||
verify(invoker).invoke(invocation);
|
||||
|
||||
String application = RpcContext.getContext().getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY);
|
||||
assertEquals(application, "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author cdfive
|
||||
*/
|
||||
public class DubboUtilsTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "true");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "");
|
||||
SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false");
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "");
|
||||
SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetApplication() {
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
when(invocation.getAttachments()).thenReturn(new HashMap<>());
|
||||
when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, ""))
|
||||
.thenReturn("consumerA");
|
||||
|
||||
String application = DubboUtils.getApplication(invocation, "");
|
||||
verify(invocation).getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "");
|
||||
|
||||
assertEquals("consumerA", application);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetApplicationNoAttachments() {
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
when(invocation.getAttachments()).thenReturn(null);
|
||||
when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, ""))
|
||||
.thenReturn("consumerA");
|
||||
|
||||
DubboUtils.getApplication(invocation, "");
|
||||
|
||||
fail("No attachments in invocation, IllegalArgumentException should be thrown!");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetResourceName() throws NoSuchMethodException {
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
when(invoker.getInterface()).thenReturn(DemoService.class);
|
||||
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class);
|
||||
when(invocation.getMethodName()).thenReturn(method.getName());
|
||||
when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
|
||||
|
||||
String resourceName = DubboUtils.getMethodResourceName(invoker, invocation);
|
||||
|
||||
assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetResourceNameWithGroupAndVersion() throws NoSuchMethodException {
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
URL url = URL.valueOf("dubbo://127.0.0.1:2181")
|
||||
.addParameter(CommonConstants.VERSION_KEY, "1.0.0")
|
||||
.addParameter(CommonConstants.GROUP_KEY, "grp1")
|
||||
.addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName());
|
||||
when(invoker.getUrl()).thenReturn(url);
|
||||
when(invoker.getInterface()).thenReturn(DemoService.class);
|
||||
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class);
|
||||
when(invocation.getMethodName()).thenReturn(method.getName());
|
||||
when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
|
||||
|
||||
String resourceNameUseGroupAndVersion = DubboUtils.getMethodResourceName(invoker, invocation, true);
|
||||
|
||||
assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:1.0.0:grp1:sayHello(java.lang.String,int)", resourceNameUseGroupAndVersion);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetResourceNameWithPrefix() throws NoSuchMethodException {
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
when(invoker.getInterface()).thenReturn(DemoService.class);
|
||||
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class);
|
||||
when(invocation.getMethodName()).thenReturn(method.getName());
|
||||
when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
|
||||
|
||||
//test with default prefix
|
||||
String resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey());
|
||||
assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
|
||||
resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey());
|
||||
assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
|
||||
|
||||
|
||||
//test with custom prefix
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:");
|
||||
resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey());
|
||||
assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
|
||||
resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey());
|
||||
assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInterfaceName() {
|
||||
|
||||
URL url = URL.valueOf("dubbo://127.0.0.1:2181")
|
||||
.addParameter(CommonConstants.VERSION_KEY, "1.0.0")
|
||||
.addParameter(CommonConstants.GROUP_KEY, "grp1")
|
||||
.addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName());
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
when(invoker.getUrl()).thenReturn(url);
|
||||
when(invoker.getInterface()).thenReturn(DemoService.class);
|
||||
|
||||
SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false");
|
||||
assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", DubboUtils.getInterfaceName(invoker));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInterfaceNameWithGroupAndVersion() throws NoSuchMethodException {
|
||||
|
||||
URL url = URL.valueOf("dubbo://127.0.0.1:2181")
|
||||
.addParameter(CommonConstants.VERSION_KEY, "1.0.0")
|
||||
.addParameter(CommonConstants.GROUP_KEY, "grp1")
|
||||
.addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName());
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
when(invoker.getUrl()).thenReturn(url);
|
||||
when(invoker.getInterface()).thenReturn(DemoService.class);
|
||||
|
||||
SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "true");
|
||||
assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:1.0.0:grp1", DubboUtils.getInterfaceName(invoker, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInterfaceNameWithPrefix() throws NoSuchMethodException {
|
||||
URL url = URL.valueOf("dubbo://127.0.0.1:2181")
|
||||
.addParameter(CommonConstants.VERSION_KEY, "1.0.0")
|
||||
.addParameter(CommonConstants.GROUP_KEY, "grp1")
|
||||
.addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName());
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
when(invoker.getUrl()).thenReturn(url);
|
||||
when(invoker.getInterface()).thenReturn(DemoService.class);
|
||||
|
||||
|
||||
//test with default prefix
|
||||
String resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey());
|
||||
assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName);
|
||||
resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey());
|
||||
assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName);
|
||||
|
||||
|
||||
//test with custom prefix
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:");
|
||||
resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey());
|
||||
assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName);
|
||||
resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey());
|
||||
assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.BaseTest;
|
||||
import com.alibaba.csp.sentinel.DubboTestUtil;
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallback;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry;
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.node.Node;
|
||||
import com.alibaba.csp.sentinel.node.StatisticNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
||||
|
||||
import org.apache.dubbo.rpc.*;
|
||||
import org.apache.dubbo.rpc.support.RpcUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO;
|
||||
import static org.apache.dubbo.rpc.Constants.ASYNC_KEY;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author cdfive
|
||||
* @author lianglin
|
||||
*/
|
||||
public class SentinelDubboConsumerFilterTest extends BaseTest {
|
||||
|
||||
private final SentinelDubboConsumerFilter consumerFilter = new SentinelDubboConsumerFilter();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cleanUpAll();
|
||||
initFallback();
|
||||
}
|
||||
|
||||
@After
|
||||
public void destroy() {
|
||||
cleanUpAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterfaceLevelFollowControlAsync() throws InterruptedException {
|
||||
|
||||
Invoker invoker = DubboTestUtil.getDefaultMockInvoker();
|
||||
Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne();
|
||||
|
||||
when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString());
|
||||
initFlowRule(DubboUtils.getInterfaceName(invoker));
|
||||
|
||||
Result result1 = invokeDubboRpc(false, invoker, invocation);
|
||||
assertEquals("normal", result1.getValue());
|
||||
|
||||
// should fallback because the qps > 1
|
||||
Result result2 = invokeDubboRpc(false, invoker, invocation);
|
||||
assertEquals("fallback", result2.getValue());
|
||||
|
||||
// sleeping 1000 ms to reset qps
|
||||
Thread.sleep(1000);
|
||||
Result result3 = invokeDubboRpc(false, invoker, invocation);
|
||||
assertEquals("normal", result3.getValue());
|
||||
|
||||
verifyInvocationStructureForCallFinish(invoker, invocation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDegradeAsync() throws InterruptedException {
|
||||
|
||||
Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne();
|
||||
Invoker invoker = DubboTestUtil.getDefaultMockInvoker();
|
||||
|
||||
when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString());
|
||||
initDegradeRule(DubboUtils.getInterfaceName(invoker));
|
||||
|
||||
Result result = invokeDubboRpc(false, invoker, invocation);
|
||||
verifyInvocationStructureForCallFinish(invoker, invocation);
|
||||
assertEquals("normal", result.getValue());
|
||||
|
||||
// inc the clusterNode's exception to trigger the fallback
|
||||
for (int i = 0; i < 5; i++) {
|
||||
invokeDubboRpc(true, invoker, invocation);
|
||||
verifyInvocationStructureForCallFinish(invoker, invocation);
|
||||
}
|
||||
|
||||
Result result2 = invokeDubboRpc(false, invoker, invocation);
|
||||
assertEquals("fallback", result2.getValue());
|
||||
|
||||
// sleeping 1000 ms to reset exception
|
||||
Thread.sleep(1000);
|
||||
Result result3 = invokeDubboRpc(false, invoker, invocation);
|
||||
assertEquals("normal", result3.getValue());
|
||||
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNull(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDegradeSync() throws InterruptedException {
|
||||
|
||||
Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne();
|
||||
Invoker invoker = DubboTestUtil.getDefaultMockInvoker();
|
||||
initDegradeRule(DubboUtils.getInterfaceName(invoker));
|
||||
|
||||
Result result = invokeDubboRpc(false, invoker, invocation);
|
||||
verifyInvocationStructureForCallFinish(invoker, invocation);
|
||||
assertEquals("normal", result.getValue());
|
||||
|
||||
// inc the clusterNode's exception to trigger the fallback
|
||||
for (int i = 0; i < 5; i++) {
|
||||
invokeDubboRpc(true, invoker, invocation);
|
||||
verifyInvocationStructureForCallFinish(invoker, invocation);
|
||||
}
|
||||
|
||||
Result result2 = invokeDubboRpc(false, invoker, invocation);
|
||||
assertEquals("fallback", result2.getValue());
|
||||
|
||||
// sleeping 1000 ms to reset exception
|
||||
Thread.sleep(1000);
|
||||
Result result3 = invokeDubboRpc(false, invoker, invocation);
|
||||
assertEquals("normal", result3.getValue());
|
||||
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNull(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodFlowControlAsync() {
|
||||
|
||||
Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne();
|
||||
Invoker invoker = DubboTestUtil.getDefaultMockInvoker();
|
||||
|
||||
when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString());
|
||||
initFlowRule(consumerFilter.getMethodName(invoker, invocation, null));
|
||||
invokeDubboRpc(false, invoker, invocation);
|
||||
invokeDubboRpc(false, invoker, invocation);
|
||||
|
||||
Invocation invocation2 = DubboTestUtil.getDefaultMockInvocationTwo();
|
||||
Result result2 = invokeDubboRpc(false, invoker, invocation2);
|
||||
verifyInvocationStructureForCallFinish(invoker, invocation2);
|
||||
assertEquals("normal", result2.getValue());
|
||||
|
||||
// the method of invocation should be blocked
|
||||
Result fallback = invokeDubboRpc(false, invoker, invocation);
|
||||
assertEquals("fallback", fallback.getValue());
|
||||
verifyInvocationStructureForCallFinish(invoker, invocation);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokeAsync() {
|
||||
|
||||
Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne();
|
||||
Invoker invoker = DubboTestUtil.getDefaultMockInvoker();
|
||||
|
||||
when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString());
|
||||
final Result result = mock(Result.class);
|
||||
when(result.hasException()).thenReturn(false);
|
||||
when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> {
|
||||
verifyInvocationStructureForAsyncCall(invoker, invocation);
|
||||
return result;
|
||||
});
|
||||
consumerFilter.invoke(invoker, invocation);
|
||||
verify(invoker).invoke(invocation);
|
||||
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNotNull(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokeSync() {
|
||||
|
||||
Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne();
|
||||
Invoker invoker = DubboTestUtil.getDefaultMockInvoker();
|
||||
|
||||
final Result result = mock(Result.class);
|
||||
when(result.hasException()).thenReturn(false);
|
||||
when(result.getException()).thenReturn(new Exception());
|
||||
when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> {
|
||||
verifyInvocationStructure(invoker, invocation);
|
||||
return result;
|
||||
});
|
||||
|
||||
consumerFilter.invoke(invoker, invocation);
|
||||
verify(invoker).invoke(invocation);
|
||||
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNull(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply verify invocation structure in memory:
|
||||
* EntranceNode(defaultContextName)
|
||||
* --InterfaceNode(interfaceName)
|
||||
* ----MethodNode(resourceName)
|
||||
*/
|
||||
private void verifyInvocationStructure(Invoker invoker, Invocation invocation) {
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNotNull(context);
|
||||
// As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context
|
||||
// In actual project, a consumer is usually also a provider, the context will be created by
|
||||
//SentinelDubboProviderFilter
|
||||
// If consumer is on the top of Dubbo RPC invocation chain, use default context
|
||||
String resourceName = consumerFilter.getMethodName(invoker, invocation, null);
|
||||
assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName());
|
||||
assertEquals("", context.getOrigin());
|
||||
|
||||
DefaultNode entranceNode = context.getEntranceNode();
|
||||
ResourceWrapper entranceResource = entranceNode.getId();
|
||||
|
||||
assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName());
|
||||
assertSame(EntryType.IN, entranceResource.getEntryType());
|
||||
|
||||
// As SphU.entry(interfaceName, EntryType.OUT);
|
||||
Set<Node> childList = entranceNode.getChildList();
|
||||
assertEquals(1, childList.size());
|
||||
DefaultNode interfaceNode = getNode(DubboUtils.getInterfaceName(invoker), entranceNode);
|
||||
ResourceWrapper interfaceResource = interfaceNode.getId();
|
||||
|
||||
assertEquals(DubboUtils.getInterfaceName(invoker), interfaceResource.getName());
|
||||
assertSame(EntryType.OUT, interfaceResource.getEntryType());
|
||||
|
||||
// As SphU.entry(resourceName, EntryType.OUT);
|
||||
childList = interfaceNode.getChildList();
|
||||
assertEquals(1, childList.size());
|
||||
DefaultNode methodNode = getNode(resourceName, entranceNode);
|
||||
ResourceWrapper methodResource = methodNode.getId();
|
||||
assertEquals(resourceName, methodResource.getName());
|
||||
assertSame(EntryType.OUT, methodResource.getEntryType());
|
||||
|
||||
// Verify curEntry
|
||||
Entry curEntry = context.getCurEntry();
|
||||
assertSame(methodNode, curEntry.getCurNode());
|
||||
assertSame(interfaceNode, curEntry.getLastNode());
|
||||
assertNull(curEntry.getOriginNode());// As context origin is not "", no originNode should be created in curEntry
|
||||
|
||||
// Verify clusterNode
|
||||
ClusterNode methodClusterNode = methodNode.getClusterNode();
|
||||
ClusterNode interfaceClusterNode = interfaceNode.getClusterNode();
|
||||
assertNotSame(methodClusterNode,
|
||||
interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode
|
||||
|
||||
// As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode
|
||||
Map<String, StatisticNode> methodOriginCountMap = methodClusterNode.getOriginCountMap();
|
||||
assertEquals(0, methodOriginCountMap.size());
|
||||
|
||||
Map<String, StatisticNode> interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap();
|
||||
assertEquals(0, interfaceOriginCountMap.size());
|
||||
}
|
||||
|
||||
private void verifyInvocationStructureForAsyncCall(Invoker invoker, Invocation invocation) {
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNotNull(context);
|
||||
|
||||
// As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context
|
||||
// In actual project, a consumer is usually also a provider, the context will be created by
|
||||
//SentinelDubboProviderFilter
|
||||
// If consumer is on the top of Dubbo RPC invocation chain, use default context
|
||||
String resourceName = consumerFilter.getMethodName(invoker, invocation, null);
|
||||
assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName());
|
||||
assertEquals("", context.getOrigin());
|
||||
|
||||
DefaultNode entranceNode = context.getEntranceNode();
|
||||
ResourceWrapper entranceResource = entranceNode.getId();
|
||||
assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName());
|
||||
assertSame(EntryType.IN, entranceResource.getEntryType());
|
||||
|
||||
// As SphU.entry(interfaceName, EntryType.OUT);
|
||||
Set<Node> childList = entranceNode.getChildList();
|
||||
assertEquals(2, childList.size());
|
||||
DefaultNode interfaceNode = getNode(DubboUtils.getInterfaceName(invoker), entranceNode);
|
||||
ResourceWrapper interfaceResource = interfaceNode.getId();
|
||||
assertEquals(DubboUtils.getInterfaceName(invoker), interfaceResource.getName());
|
||||
assertSame(EntryType.OUT, interfaceResource.getEntryType());
|
||||
|
||||
// As SphU.entry(resourceName, EntryType.OUT);
|
||||
childList = interfaceNode.getChildList();
|
||||
assertEquals(0, childList.size());
|
||||
DefaultNode methodNode = getNode(resourceName, entranceNode);
|
||||
ResourceWrapper methodResource = methodNode.getId();
|
||||
assertEquals(resourceName, methodResource.getName());
|
||||
assertSame(EntryType.OUT, methodResource.getEntryType());
|
||||
|
||||
// Verify curEntry
|
||||
// nothing will bind to local context when use the AsyncEntry
|
||||
Entry curEntry = context.getCurEntry();
|
||||
assertNull(curEntry);
|
||||
|
||||
// Verify clusterNode
|
||||
ClusterNode methodClusterNode = methodNode.getClusterNode();
|
||||
ClusterNode interfaceClusterNode = interfaceNode.getClusterNode();
|
||||
assertNotSame(methodClusterNode,
|
||||
interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode
|
||||
|
||||
// As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode
|
||||
Map<String, StatisticNode> methodOriginCountMap = methodClusterNode.getOriginCountMap();
|
||||
assertEquals(0, methodOriginCountMap.size());
|
||||
|
||||
Map<String, StatisticNode> interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap();
|
||||
assertEquals(0, interfaceOriginCountMap.size());
|
||||
}
|
||||
|
||||
private void verifyInvocationStructureForCallFinish(Invoker invoker, Invocation invocation) {
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNull(context);
|
||||
String methodResourceName = consumerFilter.getMethodName(invoker, invocation, null);
|
||||
Entry[] entries = (Entry[]) RpcContext.getContext().get(methodResourceName);
|
||||
assertNull(entries);
|
||||
}
|
||||
|
||||
private DefaultNode getNode(String resourceName, DefaultNode root) {
|
||||
|
||||
Queue<DefaultNode> queue = new LinkedList<>();
|
||||
queue.offer(root);
|
||||
while (!queue.isEmpty()) {
|
||||
DefaultNode temp = queue.poll();
|
||||
if (temp.getId().getName().equals(resourceName)) {
|
||||
return temp;
|
||||
}
|
||||
for (Node node : temp.getChildList()) {
|
||||
queue.offer((DefaultNode) node);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void initFlowRule(String resource) {
|
||||
FlowRule flowRule = new FlowRule(resource);
|
||||
flowRule.setCount(1);
|
||||
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
|
||||
List<FlowRule> flowRules = new ArrayList<>();
|
||||
flowRules.add(flowRule);
|
||||
FlowRuleManager.loadRules(flowRules);
|
||||
}
|
||||
|
||||
private void initDegradeRule(String resource) {
|
||||
DegradeRule degradeRule = new DegradeRule(resource)
|
||||
.setCount(0.5)
|
||||
.setGrade(DEGRADE_GRADE_EXCEPTION_RATIO);
|
||||
List<DegradeRule> degradeRules = new ArrayList<>();
|
||||
degradeRules.add(degradeRule);
|
||||
degradeRule.setTimeWindow(1);
|
||||
DegradeRuleManager.loadRules(degradeRules);
|
||||
}
|
||||
|
||||
private void initFallback() {
|
||||
DubboAdapterGlobalConfig.setConsumerFallback((invoker, invocation, ex) -> {
|
||||
// boolean async = RpcUtils.isAsync(invoker.getUrl(), invocation);
|
||||
return AsyncRpcResult.newDefaultAsyncResult("fallback", invocation);
|
||||
});
|
||||
}
|
||||
|
||||
private Result invokeDubboRpc(boolean exception, Invoker invoker, Invocation invocation) {
|
||||
Result result = null;
|
||||
InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation);
|
||||
if (InvokeMode.SYNC == invokeMode) {
|
||||
result = exception ? new AppResponse(new Exception("error")) : new AppResponse("normal");
|
||||
} else {
|
||||
result = exception ? AsyncRpcResult.newDefaultAsyncResult(new Exception("error"), invocation)
|
||||
: AsyncRpcResult.newDefaultAsyncResult("normal", invocation);
|
||||
}
|
||||
when(invoker.invoke(invocation)).thenReturn(result);
|
||||
return consumerFilter.invoke(invoker, invocation);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.BaseTest;
|
||||
import com.alibaba.csp.sentinel.DubboTestUtil;
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.node.Node;
|
||||
import com.alibaba.csp.sentinel.node.StatisticNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author cdfive
|
||||
* @author lianglin
|
||||
*/
|
||||
public class SentinelDubboProviderFilterTest extends BaseTest {
|
||||
|
||||
|
||||
private SentinelDubboProviderFilter filter = new SentinelDubboProviderFilter();
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cleanUpAll();
|
||||
}
|
||||
|
||||
@After
|
||||
public void destroy() {
|
||||
cleanUpAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvoke() {
|
||||
|
||||
final String originApplication = "consumerA";
|
||||
|
||||
URL url = DubboTestUtil.getDefaultTestURL();
|
||||
url = url.addParameter(CommonConstants.SIDE_KEY, CommonConstants.PROVIDER_SIDE);
|
||||
Invoker invoker = DubboTestUtil.getMockInvoker(url, DemoService.class);
|
||||
|
||||
Invocation invocation = DubboTestUtil.getMockInvocation(DemoService.class.getMethods()[0]);
|
||||
when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, ""))
|
||||
.thenReturn(originApplication);
|
||||
|
||||
final Result result = mock(Result.class);
|
||||
when(result.hasException()).thenReturn(false);
|
||||
when(result.getException()).thenReturn(new Exception());
|
||||
|
||||
when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> {
|
||||
verifyInvocationStructure(originApplication, invoker, invocation);
|
||||
return result;
|
||||
});
|
||||
|
||||
filter.invoke(invoker, invocation);
|
||||
verify(invoker).invoke(invocation);
|
||||
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNull(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply verify invocation structure in memory:
|
||||
* EntranceNode(methodResourceName)
|
||||
* --InterfaceNode(interfaceName)
|
||||
* ----MethodNode(methodResourceName)
|
||||
*/
|
||||
private void verifyInvocationStructure(String originApplication, Invoker invoker, Invocation invocation) {
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNotNull(context);
|
||||
|
||||
// As ContextUtil.enter(resourceName, application) in SentinelDubboProviderFilter
|
||||
String methodResourceName = filter.getMethodName(invoker, invocation, null);
|
||||
assertEquals(methodResourceName, context.getName());
|
||||
assertEquals(originApplication, context.getOrigin());
|
||||
|
||||
DefaultNode entranceNode = context.getEntranceNode();
|
||||
ResourceWrapper entranceResource = entranceNode.getId();
|
||||
assertEquals(methodResourceName, entranceResource.getName());
|
||||
assertSame(EntryType.IN, entranceResource.getEntryType());
|
||||
|
||||
// As SphU.entry(interfaceName, EntryType.IN);
|
||||
Set<Node> childList = entranceNode.getChildList();
|
||||
assertEquals(1, childList.size());
|
||||
DefaultNode interfaceNode = (DefaultNode) childList.iterator().next();
|
||||
ResourceWrapper interfaceResource = interfaceNode.getId();
|
||||
|
||||
assertEquals(filter.getInterfaceName(invoker, null), interfaceResource.getName());
|
||||
assertSame(EntryType.IN, interfaceResource.getEntryType());
|
||||
|
||||
// As SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments());
|
||||
childList = interfaceNode.getChildList();
|
||||
assertEquals(1, childList.size());
|
||||
DefaultNode methodNode = (DefaultNode) childList.iterator().next();
|
||||
ResourceWrapper methodResource = methodNode.getId();
|
||||
assertEquals(methodResourceName, methodResource.getName());
|
||||
assertSame(EntryType.IN, methodResource.getEntryType());
|
||||
|
||||
// Verify curEntry
|
||||
Entry curEntry = context.getCurEntry();
|
||||
assertSame(methodNode, curEntry.getCurNode());
|
||||
assertSame(interfaceNode, curEntry.getLastNode());
|
||||
assertNotNull(curEntry.getOriginNode());// As context origin is not "", originNode should be created
|
||||
|
||||
// Verify clusterNode
|
||||
ClusterNode methodClusterNode = methodNode.getClusterNode();
|
||||
ClusterNode interfaceClusterNode = interfaceNode.getClusterNode();
|
||||
assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode
|
||||
|
||||
// As context origin is not "", the StatisticNode should be created in originCountMap of ClusterNode
|
||||
Map<String, StatisticNode> methodOriginCountMap = methodClusterNode.getOriginCountMap();
|
||||
assertEquals(1, methodOriginCountMap.size());
|
||||
assertTrue(methodOriginCountMap.containsKey(originApplication));
|
||||
|
||||
Map<String, StatisticNode> interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap();
|
||||
assertEquals(1, interfaceOriginCountMap.size());
|
||||
assertTrue(interfaceOriginCountMap.containsKey(originApplication));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.fallback;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
|
||||
|
||||
import org.apache.dubbo.rpc.AsyncRpcResult;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class DubboFallbackRegistryTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultFallback() {
|
||||
// Test for default fallback.
|
||||
BlockException ex = new FlowException("xxx");
|
||||
Result result = new DefaultDubboFallback().handle(null, null, ex);
|
||||
Assert.assertTrue("The result should carry exception", result.hasException());
|
||||
Assert.assertTrue(BlockException.isBlockException(result.getException()));
|
||||
Assert.assertTrue(result.getException().getMessage().contains(ex.getClass().getSimpleName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomFallback() {
|
||||
BlockException ex = new FlowException("xxx");
|
||||
DubboAdapterGlobalConfig.setConsumerFallback(
|
||||
(invoker, invocation, e) -> AsyncRpcResult
|
||||
.newDefaultAsyncResult("Error: " + e.getClass().getName(), invocation));
|
||||
Result result = DubboAdapterGlobalConfig.getConsumerFallback()
|
||||
.handle(null, null, ex);
|
||||
Assert.assertFalse("The invocation should not fail", result.hasException());
|
||||
Assert.assertEquals("Error: " + ex.getClass().getName(), result.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.dubbo.origin;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig;
|
||||
import com.alibaba.dubbo.rpc.RpcInvocation;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author tiecheng
|
||||
*/
|
||||
public class DubboOriginRegistryTest {
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
DubboAdapterGlobalConfig.setOriginParser(new DefaultDubboOriginParser());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testDefaultOriginParserFail() {
|
||||
DubboAdapterGlobalConfig.getOriginParser().parse(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultOriginParserSuccess() {
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
String dubboName = "sentinel";
|
||||
invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, dubboName);
|
||||
String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation);
|
||||
Assert.assertEquals(dubboName, origin);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomOriginParser() {
|
||||
DubboAdapterGlobalConfig.setOriginParser(new DubboOriginParser() {
|
||||
@Override
|
||||
public String parse(Invoker<?> invoker, Invocation invocation) {
|
||||
return invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "default") + "_" + invocation
|
||||
.getMethodName();
|
||||
}
|
||||
});
|
||||
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation);
|
||||
Assert.assertEquals("default_null", origin);
|
||||
|
||||
String dubboName = "sentinel";
|
||||
invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, dubboName);
|
||||
origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation);
|
||||
Assert.assertEquals(dubboName + "_null", origin);
|
||||
|
||||
invocation.setMethodName("hello");
|
||||
origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation);
|
||||
Assert.assertEquals(dubboName + "_hello", origin);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.provider;
|
||||
|
||||
/**
|
||||
* @author leyou
|
||||
*/
|
||||
public interface DemoService {
|
||||
String sayHello(String name, int n);
|
||||
String sayHi(String name,int n);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.provider.impl;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
|
||||
|
||||
/**
|
||||
* @author leyou
|
||||
*/
|
||||
public class DemoServiceImpl implements DemoService {
|
||||
public String sayHello(String name, int n) {
|
||||
return "Hello " + name + ", " + n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sayHi(String name, int n) {
|
||||
return "Hi " + name + ", " + n;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://code.alibabatech.com/schema/dubbo
|
||||
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<dubbo:application name="demo-consumer"/>
|
||||
<dubbo:registry address="multicast://224.5.6.7:1234"/>
|
||||
<dubbo:protocol name="dubbo" port="20880"/>
|
||||
<dubbo:service interface="com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService" ref="demoServiceImp" />
|
||||
<bean id="demoServiceImp" class="com.alibaba.csp.sentinel.adapter.dubbo.provider.impl.DemoServiceImpl"/>
|
||||
|
||||
<dubbo:reference id="demoService" interface="com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService"/>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://code.alibabatech.com/schema/dubbo
|
||||
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<dubbo:application name="demo-provider"/>
|
||||
<dubbo:registry address="multicast://224.5.6.7:1234"/>
|
||||
<dubbo:protocol name="dubbo" port="20880"/>
|
||||
<dubbo:service interface="com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService" ref="demoServiceImp" />
|
||||
<bean id="demoServiceImp" class="com.alibaba.csp.sentinel.adapter.dubbo.provider.impl.DemoServiceImpl"/>
|
||||
|
||||
<dubbo:reference id="demoService" interface="com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService"/>
|
||||
|
||||
</beans>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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.3</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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 com.alibaba.csp.sentinel.slots.block.SentinelRpcException;
|
||||
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 class DefaultApacheHttpClientFallback implements ApacheHttpClientFallback {
|
||||
|
||||
@Override
|
||||
public CloseableHttpResponse handle(HttpRequestWrapper request, BlockException e) {
|
||||
// Just wrap and throw the exception.
|
||||
throw new SentinelRpcException(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.Constants;
|
||||
import com.alibaba.csp.sentinel.adapter.apache.httpclient.app.TestApplication;
|
||||
import com.alibaba.csp.sentinel.adapter.apache.httpclient.config.SentinelApacheHttpClientConfig;
|
||||
import com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor.ApacheHttpClientResourceExtractor;
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpRequestWrapper;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.protocol.BasicHttpContext;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* @author zhaoyuguang
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = TestApplication.class,
|
||||
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
|
||||
properties = {
|
||||
"server.port=8084"
|
||||
})
|
||||
public class SentinelApacheHttpClientTest {
|
||||
|
||||
@Value("${server.port}")
|
||||
private Integer port;
|
||||
|
||||
@Test
|
||||
public void testSentinelOkHttpInterceptor0() throws Exception {
|
||||
|
||||
CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder().build();
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + port + "/httpclient/back");
|
||||
System.out.println(getRemoteString(httpclient, httpGet));
|
||||
ClusterNode cn = ClusterBuilderSlot.getClusterNode("httpclient:/httpclient/back");
|
||||
assertNotNull(cn);
|
||||
Constants.ROOT.removeChildList();
|
||||
ClusterBuilderSlot.getClusterNodeMap().clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSentinelOkHttpInterceptor1() throws Exception {
|
||||
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();
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + port + "/httpclient/back/1");
|
||||
System.out.println(getRemoteString(httpclient, httpGet));
|
||||
ClusterNode cn = ClusterBuilderSlot.getClusterNode("httpclient:GET:/httpclient/back/{id}");
|
||||
assertNotNull(cn);
|
||||
Constants.ROOT.removeChildList();
|
||||
ClusterBuilderSlot.getClusterNodeMap().clear();
|
||||
}
|
||||
|
||||
private String getRemoteString(CloseableHttpClient httpclient, HttpGet httpGet) throws IOException {
|
||||
String result;
|
||||
HttpContext context = new BasicHttpContext();
|
||||
CloseableHttpResponse response;
|
||||
response = httpclient.execute(httpGet, context);
|
||||
try {
|
||||
HttpEntity entity = response.getEntity();
|
||||
result = EntityUtils.toString(entity, "utf-8");
|
||||
EntityUtils.consume(entity);
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
httpclient.close();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.app;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author zhaoyuguang
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class TestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TestApplication.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.app.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author zhaoyuguang
|
||||
*/
|
||||
@RestController
|
||||
public class TestController {
|
||||
|
||||
@RequestMapping("/httpclient/back")
|
||||
public String back() {
|
||||
return "Welcome Back!";
|
||||
}
|
||||
|
||||
@RequestMapping("/httpclient/back/{id}")
|
||||
public String back(@PathVariable String id) {
|
||||
return "Welcome Back! " + id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author zhaoyuguang
|
||||
*/
|
||||
public class SentinelApacheHttpClientConfigTest {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testConfigSetPrefix() {
|
||||
SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig();
|
||||
config.setPrefix(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testConfigSetCleaner() {
|
||||
SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig();
|
||||
config.setExtractor(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testConfigSetFallback() {
|
||||
SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig();
|
||||
config.setFallback(null);
|
||||
}
|
||||
}
|
||||
@@ -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.httpclient.fallback;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.block.SentinelRpcException;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author zhaoyuguang
|
||||
*/
|
||||
public class ApacheHttpClientFallbackTest {
|
||||
|
||||
@Test(expected = SentinelRpcException.class)
|
||||
public void testDefaultOkHttpFallback() {
|
||||
BlockException e = new FlowException("xxx");
|
||||
ApacheHttpClientFallback fallback = new DefaultApacheHttpClientFallback();
|
||||
fallback.handle(null, e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# Sentinel API Gateway Adapter Common
|
||||
|
||||
The `sentinel-api-gateway-adapter-common` module provides common abstraction for
|
||||
API gateway flow control:
|
||||
|
||||
- `GatewayFlowRule`: flow control rule specific for route or API defined in API gateway.
|
||||
This can be automatically converted to `FlowRule` or `ParamFlowRule`.
|
||||
- `ApiDefinition`: gateway API definition with a group of predicates
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport-common</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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.gateway.common;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public final class SentinelGatewayConstants {
|
||||
|
||||
public static final int APP_TYPE_GATEWAY = 1;
|
||||
|
||||
public static final int RESOURCE_MODE_ROUTE_ID = 0;
|
||||
public static final int RESOURCE_MODE_CUSTOM_API_NAME = 1;
|
||||
|
||||
public static final int PARAM_PARSE_STRATEGY_CLIENT_IP = 0;
|
||||
public static final int PARAM_PARSE_STRATEGY_HOST = 1;
|
||||
public static final int PARAM_PARSE_STRATEGY_HEADER = 2;
|
||||
public static final int PARAM_PARSE_STRATEGY_URL_PARAM = 3;
|
||||
public static final int PARAM_PARSE_STRATEGY_COOKIE = 4;
|
||||
|
||||
public static final int URL_MATCH_STRATEGY_EXACT = 0;
|
||||
public static final int URL_MATCH_STRATEGY_PREFIX = 1;
|
||||
public static final int URL_MATCH_STRATEGY_REGEX = 2;
|
||||
|
||||
public static final int PARAM_MATCH_STRATEGY_EXACT = 0;
|
||||
public static final int PARAM_MATCH_STRATEGY_PREFIX = 1;
|
||||
public static final int PARAM_MATCH_STRATEGY_REGEX = 2;
|
||||
public static final int PARAM_MATCH_STRATEGY_CONTAINS = 3;
|
||||
|
||||
public static final String GATEWAY_CONTEXT_DEFAULT = "sentinel_gateway_context_default";
|
||||
public static final String GATEWAY_CONTEXT_PREFIX = "sentinel_gateway_context$$";
|
||||
public static final String GATEWAY_CONTEXT_ROUTE_PREFIX = "sentinel_gateway_context$$route$$";
|
||||
|
||||
public static final String GATEWAY_NOT_MATCH_PARAM = "$NM";
|
||||
public static final String GATEWAY_DEFAULT_PARAM = "$D";
|
||||
|
||||
private SentinelGatewayConstants() {}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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.gateway.common.api;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A group of HTTP API patterns.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public class ApiDefinition {
|
||||
|
||||
private String apiName;
|
||||
private Set<ApiPredicateItem> predicateItems;
|
||||
|
||||
public ApiDefinition() {}
|
||||
|
||||
public ApiDefinition(String apiName) {
|
||||
this.apiName = apiName;
|
||||
}
|
||||
|
||||
public String getApiName() {
|
||||
return apiName;
|
||||
}
|
||||
|
||||
public ApiDefinition setApiName(String apiName) {
|
||||
this.apiName = apiName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set<ApiPredicateItem> getPredicateItems() {
|
||||
return predicateItems;
|
||||
}
|
||||
|
||||
public ApiDefinition setPredicateItems(Set<ApiPredicateItem> predicateItems) {
|
||||
this.predicateItems = predicateItems;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) { return true; }
|
||||
if (o == null || getClass() != o.getClass()) { return false; }
|
||||
|
||||
ApiDefinition that = (ApiDefinition)o;
|
||||
|
||||
if (!Objects.equals(apiName, that.apiName)) { return false; }
|
||||
return Objects.equals(predicateItems, that.predicateItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = apiName != null ? apiName.hashCode() : 0;
|
||||
result = 31 * result + (predicateItems != null ? predicateItems.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApiDefinition{" +
|
||||
"apiName='" + apiName + '\'' +
|
||||
", predicateItems=" + predicateItems +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.api;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public interface ApiDefinitionChangeObserver {
|
||||
|
||||
/**
|
||||
* Notify the observer about the new gateway API definitions.
|
||||
*
|
||||
* @param apiDefinitions new set of gateway API definition
|
||||
*/
|
||||
void onChange(Set<ApiDefinition> apiDefinitions);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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.gateway.common.api;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public class ApiPathPredicateItem implements ApiPredicateItem {
|
||||
|
||||
private String pattern;
|
||||
private int matchStrategy = SentinelGatewayConstants.URL_MATCH_STRATEGY_EXACT;
|
||||
|
||||
public ApiPathPredicateItem setPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ApiPathPredicateItem setMatchStrategy(int matchStrategy) {
|
||||
this.matchStrategy = matchStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public int getMatchStrategy() {
|
||||
return matchStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) { return true; }
|
||||
if (o == null || getClass() != o.getClass()) { return false; }
|
||||
|
||||
ApiPathPredicateItem that = (ApiPathPredicateItem)o;
|
||||
|
||||
if (matchStrategy != that.matchStrategy) { return false; }
|
||||
return Objects.equals(pattern, that.pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = pattern != null ? pattern.hashCode() : 0;
|
||||
result = 31 * result + matchStrategy;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApiPathPredicateItem{" +
|
||||
"pattern='" + pattern + '\'' +
|
||||
", matchStrategy=" + matchStrategy +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.api;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public class ApiPredicateGroupItem implements ApiPredicateItem {
|
||||
|
||||
private final Set<ApiPredicateItem> items = new HashSet<>();
|
||||
|
||||
public ApiPredicateGroupItem addItem(ApiPredicateItem item) {
|
||||
AssertUtil.notNull(item, "item cannot be null");
|
||||
items.add(item);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set<ApiPredicateItem> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
/*@Override
|
||||
public ApiPredicateItem and(ApiPredicateItem item) {
|
||||
AssertUtil.notNull(item, "item cannot be null");
|
||||
return this.addItem(item);
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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.gateway.common.api;
|
||||
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public interface ApiPredicateItem {
|
||||
|
||||
/**
|
||||
* Combine two {@link ApiPredicateItem}.
|
||||
*
|
||||
* @param item another predicate item
|
||||
* @return combined predicate group item
|
||||
*/
|
||||
/*default ApiPredicateItem and(ApiPredicateItem item) {
|
||||
AssertUtil.notNull(item, "item cannot be null");
|
||||
return new ApiPredicateGroupItem()
|
||||
.addItem(this).addItem(item);
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
|
||||
import com.alibaba.csp.sentinel.property.PropertyListener;
|
||||
import com.alibaba.csp.sentinel.property.SentinelProperty;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.csp.sentinel.spi.SpiLoader;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Manager for gateway API definitions.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public final class GatewayApiDefinitionManager {
|
||||
|
||||
private static final Map<String, ApiDefinition> API_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
private static final ApiDefinitionPropertyListener LISTENER = new ApiDefinitionPropertyListener();
|
||||
private static SentinelProperty<Set<ApiDefinition>> currentProperty = new DynamicSentinelProperty<>();
|
||||
|
||||
/**
|
||||
* The map keeps all found ApiDefinitionChangeObserver (class name as key).
|
||||
*/
|
||||
private static final Map<String, ApiDefinitionChangeObserver> API_CHANGE_OBSERVERS = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
try {
|
||||
currentProperty.addListener(LISTENER);
|
||||
initializeApiChangeObserverSpi();
|
||||
} catch (Throwable ex) {
|
||||
RecordLog.warn("[GatewayApiDefinitionManager] Failed to initialize", ex);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void initializeApiChangeObserverSpi() {
|
||||
List<ApiDefinitionChangeObserver> listeners = SpiLoader.of(ApiDefinitionChangeObserver.class).loadInstanceList();
|
||||
for (ApiDefinitionChangeObserver e : listeners) {
|
||||
API_CHANGE_OBSERVERS.put(e.getClass().getCanonicalName(), e);
|
||||
RecordLog.info("[GatewayApiDefinitionManager] ApiDefinitionChangeObserver added: {}"
|
||||
, e.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
public static void register2Property(SentinelProperty<Set<ApiDefinition>> property) {
|
||||
AssertUtil.notNull(property, "property cannot be null");
|
||||
synchronized (LISTENER) {
|
||||
RecordLog.info("[GatewayApiDefinitionManager] Registering new property to gateway API definition manager");
|
||||
currentProperty.removeListener(LISTENER);
|
||||
property.addListener(LISTENER);
|
||||
currentProperty = property;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load given gateway API definitions and apply to downstream observers.
|
||||
*
|
||||
* @param apiDefinitions set of gateway API definitions
|
||||
* @return true if updated, or else false
|
||||
*/
|
||||
public static boolean loadApiDefinitions(Set<ApiDefinition> apiDefinitions) {
|
||||
return currentProperty.updateValue(apiDefinitions);
|
||||
}
|
||||
|
||||
public static ApiDefinition getApiDefinition(final String apiName) {
|
||||
if (apiName == null) {
|
||||
return null;
|
||||
}
|
||||
return API_MAP.get(apiName);
|
||||
}
|
||||
|
||||
public static Set<ApiDefinition> getApiDefinitions() {
|
||||
return new HashSet<>(API_MAP.values());
|
||||
}
|
||||
|
||||
private static final class ApiDefinitionPropertyListener implements PropertyListener<Set<ApiDefinition>> {
|
||||
|
||||
@Override
|
||||
public void configUpdate(Set<ApiDefinition> set) {
|
||||
applyApiUpdateInternal(set);
|
||||
RecordLog.info("[GatewayApiDefinitionManager] Api definition updated: {}", API_MAP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configLoad(Set<ApiDefinition> set) {
|
||||
applyApiUpdateInternal(set);
|
||||
RecordLog.info("[GatewayApiDefinitionManager] Api definition loaded: {}", API_MAP);
|
||||
}
|
||||
|
||||
private static synchronized void applyApiUpdateInternal(Set<ApiDefinition> set) {
|
||||
if (set == null || set.isEmpty()) {
|
||||
API_MAP.clear();
|
||||
notifyDownstreamListeners(new HashSet<ApiDefinition>());
|
||||
return;
|
||||
}
|
||||
Map<String, ApiDefinition> map = new HashMap<>(set.size());
|
||||
Set<ApiDefinition> validSet = new HashSet<>();
|
||||
for (ApiDefinition definition : set) {
|
||||
if (isValidApi(definition)) {
|
||||
map.put(definition.getApiName(), definition);
|
||||
validSet.add(definition);
|
||||
}
|
||||
}
|
||||
|
||||
API_MAP.clear();
|
||||
API_MAP.putAll(map);
|
||||
|
||||
// propagate to downstream.
|
||||
notifyDownstreamListeners(validSet);
|
||||
}
|
||||
}
|
||||
|
||||
private static void notifyDownstreamListeners(/*@Valid*/ final Set<ApiDefinition> definitions) {
|
||||
try {
|
||||
for (Map.Entry<?, ApiDefinitionChangeObserver> entry : API_CHANGE_OBSERVERS.entrySet()) {
|
||||
entry.getValue().onChange(definitions);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[GatewayApiDefinitionManager] WARN: failed to notify downstream api listeners", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isValidApi(ApiDefinition apiDefinition) {
|
||||
return apiDefinition != null && StringUtil.isNotBlank(apiDefinition.getApiName())
|
||||
&& apiDefinition.getPredicateItems() != null;
|
||||
}
|
||||
|
||||
static void addApiChangeListener(ApiDefinitionChangeObserver listener) {
|
||||
AssertUtil.notNull(listener, "listener cannot be null");
|
||||
API_CHANGE_OBSERVERS.put(listener.getClass().getCanonicalName(), listener);
|
||||
}
|
||||
|
||||
static void removeApiChangeListener(Class<?> clazz) {
|
||||
AssertUtil.notNull(clazz, "class cannot be null");
|
||||
API_CHANGE_OBSERVERS.remove(clazz.getCanonicalName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.api.matcher;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.csp.sentinel.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public abstract class AbstractApiMatcher<T> implements Predicate<T> {
|
||||
|
||||
protected final String apiName;
|
||||
protected final ApiDefinition apiDefinition;
|
||||
/**
|
||||
* We use {@link com.alibaba.csp.sentinel.util.function.Predicate} here as the min JDK version is 1.7.
|
||||
*/
|
||||
protected final Set<Predicate<T>> matchers = new HashSet<>();
|
||||
|
||||
public AbstractApiMatcher(ApiDefinition apiDefinition) {
|
||||
AssertUtil.notNull(apiDefinition, "apiDefinition cannot be null");
|
||||
AssertUtil.assertNotBlank(apiDefinition.getApiName(), "apiName cannot be empty");
|
||||
this.apiName = apiDefinition.getApiName();
|
||||
this.apiDefinition = apiDefinition;
|
||||
|
||||
try {
|
||||
initializeMatchers();
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[GatewayApiMatcher] Failed to initialize internal matchers", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the matchers.
|
||||
*/
|
||||
protected abstract void initializeMatchers();
|
||||
|
||||
@Override
|
||||
public boolean test(T t) {
|
||||
for (Predicate<T> matcher : matchers) {
|
||||
if (matcher.test(t)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getApiName() {
|
||||
return apiName;
|
||||
}
|
||||
|
||||
public ApiDefinition getApiDefinition() {
|
||||
return apiDefinition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.command;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
@CommandMapping(name = "gateway/getApiDefinitions", desc = "Fetch all customized gateway API groups")
|
||||
public class GetGatewayApiDefinitionGroupCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(GatewayApiDefinitionManager.getApiDefinitions()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.command;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
@CommandMapping(name = "gateway/getRules", desc = "Fetch all gateway rules")
|
||||
public class GetGatewayRuleCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(GatewayRuleManager.getRules()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.command;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.datasource.WritableDataSource;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
@CommandMapping(name = "gateway/updateApiDefinitions", desc = "")
|
||||
public class UpdateGatewayApiDefinitionGroupCommandHandler implements CommandHandler<String> {
|
||||
|
||||
private static WritableDataSource<Set<ApiDefinition>> apiDefinitionWds = null;
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String data = request.getParam("data");
|
||||
if (StringUtil.isBlank(data)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("Bad data"));
|
||||
}
|
||||
try {
|
||||
data = URLDecoder.decode(data, "utf-8");
|
||||
} catch (Exception e) {
|
||||
RecordLog.info("Decode gateway API definition data error", e);
|
||||
return CommandResponse.ofFailure(e, "decode gateway API definition data error");
|
||||
}
|
||||
|
||||
RecordLog.info("[API Server] Receiving data change (type: gateway API definition): {}", data);
|
||||
|
||||
String result = SUCCESS_MSG;
|
||||
|
||||
Set<ApiDefinition> apiDefinitions = parseJson(data);
|
||||
GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions);
|
||||
if (!writeToDataSource(apiDefinitionWds, apiDefinitions)) {
|
||||
result = WRITE_DS_FAILURE_MSG;
|
||||
}
|
||||
return CommandResponse.ofSuccess(result);
|
||||
}
|
||||
|
||||
private static final String SUCCESS_MSG = "success";
|
||||
private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)";
|
||||
|
||||
/**
|
||||
* Parse json data to set of {@link ApiDefinition}.
|
||||
*
|
||||
* Since the predicateItems of {@link ApiDefinition} is set of interface,
|
||||
* here we parse predicateItems to {@link ApiPathPredicateItem} temporarily.
|
||||
*/
|
||||
private Set<ApiDefinition> parseJson(String data) {
|
||||
Set<ApiDefinition> apiDefinitions = new HashSet<>();
|
||||
JSONArray array = JSON.parseArray(data);
|
||||
for (Object obj : array) {
|
||||
JSONObject o = (JSONObject)obj;
|
||||
ApiDefinition apiDefinition = new ApiDefinition((o.getString("apiName")));
|
||||
Set<ApiPredicateItem> predicateItems = new HashSet<>();
|
||||
JSONArray itemArray = o.getJSONArray("predicateItems");
|
||||
if (itemArray != null) {
|
||||
predicateItems.addAll(itemArray.toJavaList(ApiPathPredicateItem.class));
|
||||
}
|
||||
apiDefinition.setPredicateItems(predicateItems);
|
||||
apiDefinitions.add(apiDefinition);
|
||||
}
|
||||
|
||||
return apiDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write target value to given data source.
|
||||
*
|
||||
* @param dataSource writable data source
|
||||
* @param value target value to save
|
||||
* @param <T> value type
|
||||
* @return true if write successful or data source is empty; false if error occurs
|
||||
*/
|
||||
private <T> boolean writeToDataSource(WritableDataSource<T> dataSource, T value) {
|
||||
if (dataSource != null) {
|
||||
try {
|
||||
dataSource.write(value);
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("Write data source failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized static WritableDataSource<Set<ApiDefinition>> getWritableDataSource() {
|
||||
return apiDefinitionWds;
|
||||
}
|
||||
|
||||
public synchronized static void setWritableDataSource(WritableDataSource<Set<ApiDefinition>> apiDefinitionWds) {
|
||||
UpdateGatewayApiDefinitionGroupCommandHandler.apiDefinitionWds = apiDefinitionWds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.command;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.datasource.WritableDataSource;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
@CommandMapping(name = "gateway/updateRules", desc = "Update gateway rules")
|
||||
public class UpdateGatewayRuleCommandHandler implements CommandHandler<String> {
|
||||
private static WritableDataSource<Set<GatewayFlowRule>> gatewayFlowWds = null;
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String data = request.getParam("data");
|
||||
if (StringUtil.isBlank(data)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("Bad data"));
|
||||
}
|
||||
try {
|
||||
data = URLDecoder.decode(data, "utf-8");
|
||||
} catch (Exception e) {
|
||||
RecordLog.info("Decode gateway rule data error", e);
|
||||
return CommandResponse.ofFailure(e, "decode gateway rule data error");
|
||||
}
|
||||
|
||||
RecordLog.info("[API Server] Receiving rule change (type: gateway rule): {}", data);
|
||||
|
||||
String result = SUCCESS_MSG;
|
||||
Set<GatewayFlowRule> flowRules = JSON.parseObject(data, new TypeReference<Set<GatewayFlowRule>>() {
|
||||
});
|
||||
GatewayRuleManager.loadRules(flowRules);
|
||||
if (!writeToDataSource(gatewayFlowWds, flowRules)) {
|
||||
result = WRITE_DS_FAILURE_MSG;
|
||||
}
|
||||
return CommandResponse.ofSuccess(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write target value to given data source.
|
||||
*
|
||||
* @param dataSource writable data source
|
||||
* @param value target value to save
|
||||
* @param <T> value type
|
||||
* @return true if write successful or data source is empty; false if error occurs
|
||||
*/
|
||||
private <T> boolean writeToDataSource(WritableDataSource<T> dataSource, T value) {
|
||||
if (dataSource != null) {
|
||||
try {
|
||||
dataSource.write(value);
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("Write data source failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized static WritableDataSource<Set<GatewayFlowRule>> getWritableDataSource() {
|
||||
return gatewayFlowWds;
|
||||
}
|
||||
|
||||
public synchronized static void setWritableDataSource(WritableDataSource<Set<GatewayFlowRule>> gatewayFlowWds) {
|
||||
UpdateGatewayRuleCommandHandler.gatewayFlowWds = gatewayFlowWds;
|
||||
}
|
||||
|
||||
private static final String SUCCESS_MSG = "success";
|
||||
private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)";
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.param;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public class GatewayParamParser<T> {
|
||||
|
||||
private final RequestItemParser<T> requestItemParser;
|
||||
|
||||
public GatewayParamParser(RequestItemParser<T> requestItemParser) {
|
||||
AssertUtil.notNull(requestItemParser, "requestItemParser cannot be null");
|
||||
this.requestItemParser = requestItemParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse parameters for given resource from the request entity on condition of the rule predicate.
|
||||
*
|
||||
* @param resource valid resource name
|
||||
* @param request valid request
|
||||
* @param rulePredicate rule predicate indicating the rules to refer
|
||||
* @return the parameter array
|
||||
*/
|
||||
public Object[] parseParameterFor(String resource, T request, Predicate<GatewayFlowRule> rulePredicate) {
|
||||
if (StringUtil.isEmpty(resource) || request == null || rulePredicate == null) {
|
||||
return new Object[0];
|
||||
}
|
||||
Set<GatewayFlowRule> gatewayRules = new HashSet<>();
|
||||
Set<Boolean> predSet = new HashSet<>();
|
||||
boolean hasNonParamRule = false;
|
||||
for (GatewayFlowRule rule : GatewayRuleManager.getRulesForResource(resource)) {
|
||||
if (rule.getParamItem() != null) {
|
||||
gatewayRules.add(rule);
|
||||
predSet.add(rulePredicate.test(rule));
|
||||
} else {
|
||||
hasNonParamRule = true;
|
||||
}
|
||||
}
|
||||
if (!hasNonParamRule && gatewayRules.isEmpty()) {
|
||||
return new Object[0];
|
||||
}
|
||||
if (predSet.size() > 1 || predSet.contains(false)) {
|
||||
return new Object[0];
|
||||
}
|
||||
int size = hasNonParamRule ? gatewayRules.size() + 1 : gatewayRules.size();
|
||||
Object[] arr = new Object[size];
|
||||
for (GatewayFlowRule rule : gatewayRules) {
|
||||
GatewayParamFlowItem paramItem = rule.getParamItem();
|
||||
int idx = paramItem.getIndex();
|
||||
String param = parseInternal(paramItem, request);
|
||||
arr[idx] = param;
|
||||
}
|
||||
if (hasNonParamRule) {
|
||||
arr[size - 1] = SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private String parseInternal(GatewayParamFlowItem item, T request) {
|
||||
switch (item.getParseStrategy()) {
|
||||
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP:
|
||||
return parseClientIp(item, request);
|
||||
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST:
|
||||
return parseHost(item, request);
|
||||
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER:
|
||||
return parseHeader(item, request);
|
||||
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM:
|
||||
return parseUrlParameter(item, request);
|
||||
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE:
|
||||
return parseCookie(item, request);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String parseClientIp(/*@Valid*/ GatewayParamFlowItem item, T request) {
|
||||
String clientIp = requestItemParser.getRemoteAddress(request);
|
||||
String pattern = item.getPattern();
|
||||
if (StringUtil.isEmpty(pattern)) {
|
||||
return clientIp;
|
||||
}
|
||||
return parseWithMatchStrategyInternal(item.getMatchStrategy(), clientIp, pattern);
|
||||
}
|
||||
|
||||
private String parseHeader(/*@Valid*/ GatewayParamFlowItem item, T request) {
|
||||
String headerKey = item.getFieldName();
|
||||
String pattern = item.getPattern();
|
||||
// TODO: what if the header has multiple values?
|
||||
String headerValue = requestItemParser.getHeader(request, headerKey);
|
||||
if (StringUtil.isEmpty(pattern)) {
|
||||
return headerValue;
|
||||
}
|
||||
// Match value according to regex pattern or exact mode.
|
||||
return parseWithMatchStrategyInternal(item.getMatchStrategy(), headerValue, pattern);
|
||||
}
|
||||
|
||||
private String parseHost(/*@Valid*/ GatewayParamFlowItem item, T request) {
|
||||
String pattern = item.getPattern();
|
||||
String host = requestItemParser.getHeader(request, "Host");
|
||||
if (StringUtil.isEmpty(pattern)) {
|
||||
return host;
|
||||
}
|
||||
// Match value according to regex pattern or exact mode.
|
||||
return parseWithMatchStrategyInternal(item.getMatchStrategy(), host, pattern);
|
||||
}
|
||||
|
||||
private String parseUrlParameter(/*@Valid*/ GatewayParamFlowItem item, T request) {
|
||||
String paramName = item.getFieldName();
|
||||
String pattern = item.getPattern();
|
||||
String param = requestItemParser.getUrlParam(request, paramName);
|
||||
if (StringUtil.isEmpty(pattern)) {
|
||||
return param;
|
||||
}
|
||||
// Match value according to regex pattern or exact mode.
|
||||
return parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern);
|
||||
}
|
||||
|
||||
private String parseCookie(/*@Valid*/ GatewayParamFlowItem item, T request) {
|
||||
String cookieName = item.getFieldName();
|
||||
String pattern = item.getPattern();
|
||||
String param = requestItemParser.getCookieValue(request, cookieName);
|
||||
if (StringUtil.isEmpty(pattern)) {
|
||||
return param;
|
||||
}
|
||||
// Match value according to regex pattern or exact mode.
|
||||
return parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern);
|
||||
}
|
||||
|
||||
private String parseWithMatchStrategyInternal(int matchStrategy, String value, String pattern) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
switch (matchStrategy) {
|
||||
case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT:
|
||||
return value.equals(pattern) ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
|
||||
case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS:
|
||||
return value.contains(pattern) ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
|
||||
case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX:
|
||||
Pattern regex = GatewayRegexCache.getRegexPattern(pattern);
|
||||
if (regex == null) {
|
||||
return value;
|
||||
}
|
||||
return regex.matcher(value).matches() ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.param;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.2
|
||||
*/
|
||||
public final class GatewayRegexCache {
|
||||
|
||||
private static final Map<String, Pattern> REGEX_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public static Pattern getRegexPattern(String pattern) {
|
||||
if (pattern == null) {
|
||||
return null;
|
||||
}
|
||||
return REGEX_CACHE.get(pattern);
|
||||
}
|
||||
|
||||
public static boolean addRegexPattern(String pattern) {
|
||||
if (pattern == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Pattern regex = Pattern.compile(pattern);
|
||||
REGEX_CACHE.put(pattern, regex);
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[GatewayRegexCache] Failed to compile the regex: " + pattern, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
REGEX_CACHE.clear();
|
||||
}
|
||||
|
||||
private GatewayRegexCache() {}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.param;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public interface RequestItemParser<T> {
|
||||
|
||||
/**
|
||||
* Get API path from the request.
|
||||
*
|
||||
* @param request valid request
|
||||
* @return API path
|
||||
*/
|
||||
String getPath(T request);
|
||||
|
||||
/**
|
||||
* Get remote address from the request.
|
||||
*
|
||||
* @param request valid request
|
||||
* @return remote address
|
||||
*/
|
||||
String getRemoteAddress(T request);
|
||||
|
||||
/**
|
||||
* Get the header associated with the header key.
|
||||
*
|
||||
* @param request valid request
|
||||
* @param key valid header key
|
||||
* @return the header
|
||||
*/
|
||||
String getHeader(T request, String key);
|
||||
|
||||
/**
|
||||
* Get the parameter value associated with the parameter name.
|
||||
*
|
||||
* @param request valid request
|
||||
* @param paramName valid parameter name
|
||||
* @return the parameter value
|
||||
*/
|
||||
String getUrlParam(T request, String paramName);
|
||||
|
||||
/**
|
||||
* Get the cookie value associated with the cookie name.
|
||||
*
|
||||
* @param request valid request
|
||||
* @param cookieName valid cookie name
|
||||
* @return the cookie value
|
||||
* @since 1.7.0
|
||||
*/
|
||||
String getCookieValue(T request, String cookieName);
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.rule;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public class GatewayFlowRule {
|
||||
|
||||
private String resource;
|
||||
private int resourceMode = SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID;
|
||||
|
||||
private int grade = RuleConstant.FLOW_GRADE_QPS;
|
||||
private double count;
|
||||
private long intervalSec = 1;
|
||||
|
||||
private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;
|
||||
private int burst;
|
||||
/**
|
||||
* For throttle (rate limiting with queueing).
|
||||
*/
|
||||
private int maxQueueingTimeoutMs = 500;
|
||||
|
||||
/**
|
||||
* For parameter flow control. If not set, the gateway rule will be
|
||||
* converted to normal flow rule.
|
||||
*/
|
||||
private GatewayParamFlowItem paramItem;
|
||||
|
||||
public GatewayFlowRule() {}
|
||||
|
||||
public GatewayFlowRule(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public GatewayFlowRule setResource(String resource) {
|
||||
this.resource = resource;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getResourceMode() {
|
||||
return resourceMode;
|
||||
}
|
||||
|
||||
public GatewayFlowRule setResourceMode(int resourceMode) {
|
||||
this.resourceMode = resourceMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getGrade() {
|
||||
return grade;
|
||||
}
|
||||
|
||||
public GatewayFlowRule setGrade(int grade) {
|
||||
this.grade = grade;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getControlBehavior() {
|
||||
return controlBehavior;
|
||||
}
|
||||
|
||||
public GatewayFlowRule setControlBehavior(int controlBehavior) {
|
||||
this.controlBehavior = controlBehavior;
|
||||
return this;
|
||||
}
|
||||
|
||||
public double getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public GatewayFlowRule setCount(double count) {
|
||||
this.count = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getIntervalSec() {
|
||||
return intervalSec;
|
||||
}
|
||||
|
||||
public GatewayFlowRule setIntervalSec(long intervalSec) {
|
||||
this.intervalSec = intervalSec;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getBurst() {
|
||||
return burst;
|
||||
}
|
||||
|
||||
public GatewayFlowRule setBurst(int burst) {
|
||||
this.burst = burst;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GatewayParamFlowItem getParamItem() {
|
||||
return paramItem;
|
||||
}
|
||||
|
||||
public GatewayFlowRule setParamItem(GatewayParamFlowItem paramItem) {
|
||||
this.paramItem = paramItem;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getMaxQueueingTimeoutMs() {
|
||||
return maxQueueingTimeoutMs;
|
||||
}
|
||||
|
||||
public GatewayFlowRule setMaxQueueingTimeoutMs(int maxQueueingTimeoutMs) {
|
||||
this.maxQueueingTimeoutMs = maxQueueingTimeoutMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) { return true; }
|
||||
if (o == null || getClass() != o.getClass()) { return false; }
|
||||
|
||||
GatewayFlowRule rule = (GatewayFlowRule)o;
|
||||
|
||||
if (resourceMode != rule.resourceMode) { return false; }
|
||||
if (grade != rule.grade) { return false; }
|
||||
if (Double.compare(rule.count, count) != 0) { return false; }
|
||||
if (intervalSec != rule.intervalSec) { return false; }
|
||||
if (controlBehavior != rule.controlBehavior) { return false; }
|
||||
if (burst != rule.burst) { return false; }
|
||||
if (maxQueueingTimeoutMs != rule.maxQueueingTimeoutMs) { return false; }
|
||||
if (!Objects.equals(resource, rule.resource)) { return false; }
|
||||
return Objects.equals(paramItem, rule.paramItem);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result;
|
||||
long temp;
|
||||
result = resource != null ? resource.hashCode() : 0;
|
||||
result = 31 * result + resourceMode;
|
||||
result = 31 * result + grade;
|
||||
temp = Double.doubleToLongBits(count);
|
||||
result = 31 * result + (int)(temp ^ (temp >>> 32));
|
||||
result = 31 * result + (int)(intervalSec ^ (intervalSec >>> 32));
|
||||
result = 31 * result + controlBehavior;
|
||||
result = 31 * result + burst;
|
||||
result = 31 * result + maxQueueingTimeoutMs;
|
||||
result = 31 * result + (paramItem != null ? paramItem.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GatewayFlowRule{" +
|
||||
"resource='" + resource + '\'' +
|
||||
", resourceMode=" + resourceMode +
|
||||
", grade=" + grade +
|
||||
", count=" + count +
|
||||
", intervalSec=" + intervalSec +
|
||||
", controlBehavior=" + controlBehavior +
|
||||
", burst=" + burst +
|
||||
", maxQueueingTimeoutMs=" + maxQueueingTimeoutMs +
|
||||
", paramItem=" + paramItem +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.rule;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public class GatewayParamFlowItem {
|
||||
|
||||
/**
|
||||
* Should be set when applying to parameter flow rules.
|
||||
*/
|
||||
private Integer index;
|
||||
|
||||
/**
|
||||
* Strategy for parsing item (e.g. client IP, arbitrary headers and URL parameters).
|
||||
*/
|
||||
private int parseStrategy;
|
||||
/**
|
||||
* Field to get (only required for arbitrary headers or URL parameters mode).
|
||||
*/
|
||||
private String fieldName;
|
||||
/**
|
||||
* Matching pattern. If not set, all values will be kept in LRU map.
|
||||
*/
|
||||
private String pattern;
|
||||
/**
|
||||
* Matching strategy for item value.
|
||||
*/
|
||||
private int matchStrategy = SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT;
|
||||
|
||||
public Integer getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
GatewayParamFlowItem setIndex(Integer index) {
|
||||
this.index = index;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getParseStrategy() {
|
||||
return parseStrategy;
|
||||
}
|
||||
|
||||
public GatewayParamFlowItem setParseStrategy(int parseStrategy) {
|
||||
this.parseStrategy = parseStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public GatewayParamFlowItem setFieldName(String fieldName) {
|
||||
this.fieldName = fieldName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public GatewayParamFlowItem setPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getMatchStrategy() {
|
||||
return matchStrategy;
|
||||
}
|
||||
|
||||
public GatewayParamFlowItem setMatchStrategy(int matchStrategy) {
|
||||
this.matchStrategy = matchStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GatewayParamFlowItem{" +
|
||||
"index=" + index +
|
||||
", parseStrategy=" + parseStrategy +
|
||||
", fieldName='" + fieldName + '\'' +
|
||||
", pattern='" + pattern + '\'' +
|
||||
", matchStrategy=" + matchStrategy +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.rule;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
final class GatewayRuleConverter {
|
||||
|
||||
static FlowRule toFlowRule(/*@Valid*/ GatewayFlowRule rule) {
|
||||
return new FlowRule(rule.getResource())
|
||||
.setControlBehavior(rule.getControlBehavior())
|
||||
.setCount(rule.getCount())
|
||||
.setGrade(rule.getGrade())
|
||||
.setMaxQueueingTimeMs(rule.getMaxQueueingTimeoutMs());
|
||||
}
|
||||
|
||||
static ParamFlowItem generateNonMatchPassParamItem() {
|
||||
return new ParamFlowItem().setClassType(String.class.getName())
|
||||
.setCount(1000_0000)
|
||||
.setObject(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
|
||||
}
|
||||
|
||||
static ParamFlowItem generateNonMatchBlockParamItem() {
|
||||
return new ParamFlowItem().setClassType(String.class.getName())
|
||||
.setCount(0)
|
||||
.setObject(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
|
||||
}
|
||||
|
||||
static ParamFlowRule applyNonParamToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, int idx) {
|
||||
return new ParamFlowRule(gatewayRule.getResource())
|
||||
.setCount(gatewayRule.getCount())
|
||||
.setGrade(gatewayRule.getGrade())
|
||||
.setDurationInSec(gatewayRule.getIntervalSec())
|
||||
.setBurstCount(gatewayRule.getBurst())
|
||||
.setControlBehavior(gatewayRule.getControlBehavior())
|
||||
.setMaxQueueingTimeMs(gatewayRule.getMaxQueueingTimeoutMs())
|
||||
.setParamIdx(idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a gateway rule to parameter flow rule, then apply the generated
|
||||
* parameter index to {@link GatewayParamFlowItem} of the rule.
|
||||
*
|
||||
* @param gatewayRule a valid gateway rule that should contain valid parameter items
|
||||
* @param idx generated parameter index (callers should guarantee it's unique and incremental)
|
||||
* @return converted parameter flow rule
|
||||
*/
|
||||
static ParamFlowRule applyToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, int idx) {
|
||||
ParamFlowRule paramRule = new ParamFlowRule(gatewayRule.getResource())
|
||||
.setCount(gatewayRule.getCount())
|
||||
.setGrade(gatewayRule.getGrade())
|
||||
.setDurationInSec(gatewayRule.getIntervalSec())
|
||||
.setBurstCount(gatewayRule.getBurst())
|
||||
.setControlBehavior(gatewayRule.getControlBehavior())
|
||||
.setMaxQueueingTimeMs(gatewayRule.getMaxQueueingTimeoutMs())
|
||||
.setParamIdx(idx);
|
||||
GatewayParamFlowItem gatewayItem = gatewayRule.getParamItem();
|
||||
// Apply the current idx to gateway rule item.
|
||||
gatewayItem.setIndex(idx);
|
||||
// Apply for pattern-based parameters.
|
||||
String valuePattern = gatewayItem.getPattern();
|
||||
if (valuePattern != null) {
|
||||
paramRule.getParamFlowItemList().add(generateNonMatchPassParamItem());
|
||||
}
|
||||
return paramRule;
|
||||
}
|
||||
|
||||
private GatewayRuleConverter() {}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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.gateway.common.rule;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayRegexCache;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
|
||||
import com.alibaba.csp.sentinel.property.PropertyListener;
|
||||
import com.alibaba.csp.sentinel.property.SentinelProperty;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleUtil;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetric;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public final class GatewayRuleManager {
|
||||
|
||||
/**
|
||||
* Gateway flow rule map: (resource, [rules...])
|
||||
*/
|
||||
private static final Map<String, Set<GatewayFlowRule>> GATEWAY_RULE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Map<String, List<ParamFlowRule>> CONVERTED_PARAM_RULE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
private static final GatewayRulePropertyListener LISTENER = new GatewayRulePropertyListener();
|
||||
private static final Set<Integer> FIELD_REQUIRED_SET = new HashSet<>(
|
||||
Arrays.asList(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM,
|
||||
SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER,
|
||||
SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE)
|
||||
);
|
||||
private static SentinelProperty<Set<GatewayFlowRule>> currentProperty = new DynamicSentinelProperty<>();
|
||||
|
||||
static {
|
||||
currentProperty.addListener(LISTENER);
|
||||
}
|
||||
|
||||
private GatewayRuleManager() {
|
||||
}
|
||||
|
||||
public static void register2Property(SentinelProperty<Set<GatewayFlowRule>> property) {
|
||||
AssertUtil.notNull(property, "property cannot be null");
|
||||
synchronized (LISTENER) {
|
||||
RecordLog.info("[GatewayRuleManager] Registering new property to gateway flow rule manager");
|
||||
currentProperty.removeListener(LISTENER);
|
||||
property.addListener(LISTENER);
|
||||
currentProperty = property;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all provided gateway rules into memory, while
|
||||
* previous rules will be replaced.
|
||||
*
|
||||
* @param rules rule set
|
||||
* @return true if updated, otherwise false
|
||||
*/
|
||||
public static boolean loadRules(Set<GatewayFlowRule> rules) {
|
||||
return currentProperty.updateValue(rules);
|
||||
}
|
||||
|
||||
public static Set<GatewayFlowRule> getRules() {
|
||||
Set<GatewayFlowRule> rules = new HashSet<>();
|
||||
for (Set<GatewayFlowRule> ruleSet : GATEWAY_RULE_MAP.values()) {
|
||||
rules.addAll(ruleSet);
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
public static Set<GatewayFlowRule> getRulesForResource(String resourceName) {
|
||||
if (StringUtil.isBlank(resourceName)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
Set<GatewayFlowRule> set = GATEWAY_RULE_MAP.get(resourceName);
|
||||
if (set == null) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return new HashSet<>(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Get all converted parameter rules.</p>
|
||||
* <p>Note: caller SHOULD NOT modify the list and rules.</p>
|
||||
*
|
||||
* @param resourceName valid resource name
|
||||
* @return converted parameter rules
|
||||
*/
|
||||
public static List<ParamFlowRule> getConvertedParamRules(String resourceName) {
|
||||
if (StringUtil.isBlank(resourceName)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return CONVERTED_PARAM_RULE_MAP.get(resourceName);
|
||||
}
|
||||
|
||||
public static boolean isValidRule(GatewayFlowRule rule) {
|
||||
if (rule == null || StringUtil.isBlank(rule.getResource()) || rule.getResourceMode() < 0
|
||||
|| rule.getGrade() < 0 || rule.getCount() < 0 || rule.getBurst() < 0 || rule.getControlBehavior() < 0) {
|
||||
return false;
|
||||
}
|
||||
if (rule.getGrade() == RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
|
||||
&& rule.getMaxQueueingTimeoutMs() < 0) {
|
||||
return false;
|
||||
}
|
||||
if (rule.getIntervalSec() <= 0) {
|
||||
return false;
|
||||
}
|
||||
GatewayParamFlowItem item = rule.getParamItem();
|
||||
if (item != null) {
|
||||
return isValidParamItem(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean isValidParamItem(/*@NonNull*/ GatewayParamFlowItem item) {
|
||||
if (item.getParseStrategy() < 0) {
|
||||
return false;
|
||||
}
|
||||
// Check required field name for item types.
|
||||
if (FIELD_REQUIRED_SET.contains(item.getParseStrategy()) && StringUtil.isBlank(item.getFieldName())) {
|
||||
return false;
|
||||
}
|
||||
return StringUtil.isEmpty(item.getPattern()) || item.getMatchStrategy() >= 0;
|
||||
}
|
||||
|
||||
private static final class GatewayRulePropertyListener implements PropertyListener<Set<GatewayFlowRule>> {
|
||||
|
||||
@Override
|
||||
public void configUpdate(Set<GatewayFlowRule> conf) {
|
||||
applyGatewayRuleInternal(conf);
|
||||
RecordLog.info("[GatewayRuleManager] Gateway flow rules received: {}", GATEWAY_RULE_MAP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configLoad(Set<GatewayFlowRule> conf) {
|
||||
applyGatewayRuleInternal(conf);
|
||||
RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: {}", GATEWAY_RULE_MAP);
|
||||
}
|
||||
|
||||
private int getIdxInternal(Map<String, Integer> idxMap, String resourceName) {
|
||||
// Prepare index map.
|
||||
if (!idxMap.containsKey(resourceName)) {
|
||||
idxMap.put(resourceName, 0);
|
||||
}
|
||||
return idxMap.get(resourceName);
|
||||
}
|
||||
|
||||
private void cacheRegexPattern(/*@NonNull*/ GatewayParamFlowItem item) {
|
||||
String pattern = item.getPattern();
|
||||
if (StringUtil.isNotEmpty(pattern) &&
|
||||
item.getMatchStrategy() == SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX) {
|
||||
if (GatewayRegexCache.getRegexPattern(pattern) == null) {
|
||||
GatewayRegexCache.addRegexPattern(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void applyGatewayRuleInternal(Set<GatewayFlowRule> conf) {
|
||||
if (conf == null || conf.isEmpty()) {
|
||||
applyToConvertedParamMap(new HashSet<ParamFlowRule>());
|
||||
GATEWAY_RULE_MAP.clear();
|
||||
return;
|
||||
}
|
||||
Map<String, Set<GatewayFlowRule>> gatewayRuleMap = new ConcurrentHashMap<>();
|
||||
Map<String, Integer> idxMap = new HashMap<>();
|
||||
Set<ParamFlowRule> paramFlowRules = new HashSet<>();
|
||||
Map<String, List<GatewayFlowRule>> noParamMap = new HashMap<>();
|
||||
|
||||
for (GatewayFlowRule rule : conf) {
|
||||
if (!isValidRule(rule)) {
|
||||
RecordLog.warn("[GatewayRuleManager] Ignoring invalid rule when loading new rules: " + rule);
|
||||
continue;
|
||||
}
|
||||
String resourceName = rule.getResource();
|
||||
if (rule.getParamItem() == null) {
|
||||
// Cache the rules with no parameter config, then skip.
|
||||
List<GatewayFlowRule> noParamList = noParamMap.get(resourceName);
|
||||
if (noParamList == null) {
|
||||
noParamList = new ArrayList<>();
|
||||
noParamMap.put(resourceName, noParamList);
|
||||
}
|
||||
noParamList.add(rule);
|
||||
} else {
|
||||
int idx = getIdxInternal(idxMap, resourceName);
|
||||
// Convert to parameter flow rule.
|
||||
if (paramFlowRules.add(GatewayRuleConverter.applyToParamRule(rule, idx))) {
|
||||
idxMap.put(rule.getResource(), idx + 1);
|
||||
}
|
||||
cacheRegexPattern(rule.getParamItem());
|
||||
}
|
||||
// Apply to the gateway rule map.
|
||||
Set<GatewayFlowRule> ruleSet = gatewayRuleMap.get(resourceName);
|
||||
if (ruleSet == null) {
|
||||
ruleSet = new HashSet<>();
|
||||
gatewayRuleMap.put(resourceName, ruleSet);
|
||||
}
|
||||
ruleSet.add(rule);
|
||||
}
|
||||
// Handle non-param mode rules.
|
||||
for (Map.Entry<String, List<GatewayFlowRule>> e : noParamMap.entrySet()) {
|
||||
List<GatewayFlowRule> rules = e.getValue();
|
||||
if (rules == null || rules.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (GatewayFlowRule rule : rules) {
|
||||
int idx = getIdxInternal(idxMap, e.getKey());
|
||||
// Always use the same index (the last position).
|
||||
paramFlowRules.add(GatewayRuleConverter.applyNonParamToParamRule(rule, idx));
|
||||
}
|
||||
}
|
||||
|
||||
applyToConvertedParamMap(paramFlowRules);
|
||||
|
||||
GATEWAY_RULE_MAP.clear();
|
||||
GATEWAY_RULE_MAP.putAll(gatewayRuleMap);
|
||||
}
|
||||
|
||||
private void applyToConvertedParamMap(Set<ParamFlowRule> paramFlowRules) {
|
||||
Map<String, List<ParamFlowRule>> newRuleMap = ParamFlowRuleUtil.buildParamRuleMap(
|
||||
new ArrayList<>(paramFlowRules));
|
||||
if (newRuleMap == null || newRuleMap.isEmpty()) {
|
||||
// No parameter flow rules, so clear all the metrics.
|
||||
for (String resource : CONVERTED_PARAM_RULE_MAP.keySet()) {
|
||||
ParameterMetricStorage.clearParamMetricForResource(resource);
|
||||
}
|
||||
RecordLog.info("[GatewayRuleManager] No gateway rules, clearing parameter metrics of previous rules");
|
||||
CONVERTED_PARAM_RULE_MAP.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear unused parameter metrics.
|
||||
for (Map.Entry<String, List<ParamFlowRule>> entry : CONVERTED_PARAM_RULE_MAP.entrySet()) {
|
||||
String resource = entry.getKey();
|
||||
if (!newRuleMap.containsKey(resource)) {
|
||||
ParameterMetricStorage.clearParamMetricForResource(resource);
|
||||
continue;
|
||||
}
|
||||
List<ParamFlowRule> newRuleList = newRuleMap.get(resource);
|
||||
List<ParamFlowRule> oldRuleList = new ArrayList<>(entry.getValue());
|
||||
oldRuleList.removeAll(newRuleList);
|
||||
for (ParamFlowRule rule : oldRuleList) {
|
||||
ParameterMetric metric = ParameterMetricStorage.getParamMetricForResource(resource);
|
||||
if (null != metric) {
|
||||
metric.clearForRule(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply to converted rule map.
|
||||
CONVERTED_PARAM_RULE_MAP.clear();
|
||||
CONVERTED_PARAM_RULE_MAP.putAll(newRuleMap);
|
||||
|
||||
RecordLog.info("[GatewayRuleManager] Converted internal param rules: {}", CONVERTED_PARAM_RULE_MAP);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.slot;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage;
|
||||
import com.alibaba.csp.sentinel.spi.Spi;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.1
|
||||
*/
|
||||
@Spi(order = -4000)
|
||||
public class GatewayFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
|
||||
|
||||
@Override
|
||||
public void entry(Context context, ResourceWrapper resource, DefaultNode node, int count,
|
||||
boolean prioritized, Object... args) throws Throwable {
|
||||
checkGatewayParamFlow(resource, count, args);
|
||||
|
||||
fireEntry(context, resource, node, count, prioritized, args);
|
||||
}
|
||||
|
||||
private void checkGatewayParamFlow(ResourceWrapper resourceWrapper, int count, Object... args)
|
||||
throws BlockException {
|
||||
if (args == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ParamFlowRule> rules = GatewayRuleManager.getConvertedParamRules(resourceWrapper.getName());
|
||||
if (rules == null || rules.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ParamFlowRule rule : rules) {
|
||||
// Initialize the parameter metrics.
|
||||
ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule);
|
||||
|
||||
if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) {
|
||||
String triggeredParam = "";
|
||||
if (args.length > rule.getParamIdx()) {
|
||||
Object value = args[rule.getParamIdx()];
|
||||
triggeredParam = String.valueOf(value);
|
||||
}
|
||||
throw new ParamFlowException(resourceWrapper.getName(), triggeredParam, rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
|
||||
fireExit(context, resourceWrapper, count, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.slot;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.6.1
|
||||
*
|
||||
* @deprecated since 1.7.2, we can use @Spi(order = -4000) to adjust the order of {@link GatewayFlowSlot},
|
||||
* this class is reserved for compatibility with older versions.
|
||||
*
|
||||
* @see GatewayFlowSlot
|
||||
* @see DefaultSlotChainBuilder
|
||||
*/
|
||||
@Deprecated
|
||||
public class GatewaySlotChainBuilder extends DefaultSlotChainBuilder {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
com.alibaba.csp.sentinel.adapter.gateway.common.command.UpdateGatewayApiDefinitionGroupCommandHandler
|
||||
com.alibaba.csp.sentinel.adapter.gateway.common.command.UpdateGatewayRuleCommandHandler
|
||||
com.alibaba.csp.sentinel.adapter.gateway.common.command.GetGatewayApiDefinitionGroupCommandHandler
|
||||
com.alibaba.csp.sentinel.adapter.gateway.common.command.GetGatewayRuleCommandHandler
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewayFlowSlot
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.alibaba.csp.sentinel.adapter.gateway.common.api;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class GatewayApiDefinitionManagerTest {
|
||||
|
||||
@Test
|
||||
public void testIsValidApi() {
|
||||
ApiDefinition bad1 = new ApiDefinition();
|
||||
ApiDefinition bad2 = new ApiDefinition("foo");
|
||||
ApiDefinition good1 = new ApiDefinition("foo")
|
||||
.setPredicateItems(Collections.<ApiPredicateItem>singleton(new ApiPathPredicateItem()
|
||||
.setPattern("/abc")
|
||||
));
|
||||
|
||||
assertFalse(GatewayApiDefinitionManager.isValidApi(bad1));
|
||||
assertFalse(GatewayApiDefinitionManager.isValidApi(bad2));
|
||||
assertTrue(GatewayApiDefinitionManager.isValidApi(good1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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.gateway.common.param;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.util.function.Predicate;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class GatewayParamParserTest {
|
||||
|
||||
private final Predicate<GatewayFlowRule> routeIdPredicate = new Predicate<GatewayFlowRule>() {
|
||||
@Override
|
||||
public boolean test(GatewayFlowRule e) {
|
||||
return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID;
|
||||
}
|
||||
};
|
||||
private final Predicate<GatewayFlowRule> apiNamePredicate = new Predicate<GatewayFlowRule>() {
|
||||
@Override
|
||||
public boolean test(GatewayFlowRule e) {
|
||||
return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME;
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testParseParametersNoParamItem() {
|
||||
RequestItemParser<Object> itemParser = mock(RequestItemParser.class);
|
||||
GatewayParamParser<Object> parser = new GatewayParamParser<>(itemParser);
|
||||
// Create a fake request.
|
||||
Object request = new Object();
|
||||
// Prepare gateway rules.
|
||||
Set<GatewayFlowRule> rules = new HashSet<>();
|
||||
String routeId1 = "my_test_route_A";
|
||||
rules.add(new GatewayFlowRule(routeId1)
|
||||
.setCount(5)
|
||||
.setIntervalSec(1)
|
||||
);
|
||||
rules.add(new GatewayFlowRule(routeId1)
|
||||
.setCount(10)
|
||||
.setControlBehavior(2)
|
||||
.setMaxQueueingTimeoutMs(1000)
|
||||
);
|
||||
GatewayRuleManager.loadRules(rules);
|
||||
|
||||
Object[] params = parser.parseParameterFor(routeId1, request, routeIdPredicate);
|
||||
assertThat(params.length).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseParametersWithItems() {
|
||||
RequestItemParser<Object> itemParser = mock(RequestItemParser.class);
|
||||
GatewayParamParser<Object> paramParser = new GatewayParamParser<>(itemParser);
|
||||
// Create a fake request.
|
||||
Object request = new Object();
|
||||
|
||||
// Prepare gateway rules.
|
||||
Set<GatewayFlowRule> rules = new HashSet<>();
|
||||
final String routeId1 = "my_test_route_A";
|
||||
final String api1 = "my_test_route_B";
|
||||
final String headerName = "X-Sentinel-Flag";
|
||||
final String paramName = "p";
|
||||
final String cookieName = "myCookie";
|
||||
GatewayFlowRule routeRuleNoParam = new GatewayFlowRule(routeId1)
|
||||
.setCount(10)
|
||||
.setIntervalSec(10);
|
||||
GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId1)
|
||||
.setCount(2)
|
||||
.setIntervalSec(2)
|
||||
.setBurst(2)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
|
||||
);
|
||||
GatewayFlowRule routeRule2 = new GatewayFlowRule(routeId1)
|
||||
.setCount(10)
|
||||
.setIntervalSec(1)
|
||||
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
|
||||
.setMaxQueueingTimeoutMs(600)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
|
||||
.setFieldName(headerName)
|
||||
);
|
||||
GatewayFlowRule routeRule3 = new GatewayFlowRule(routeId1)
|
||||
.setCount(20)
|
||||
.setIntervalSec(1)
|
||||
.setBurst(5)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
|
||||
.setFieldName(paramName)
|
||||
);
|
||||
GatewayFlowRule routeRule4 = new GatewayFlowRule(routeId1)
|
||||
.setCount(120)
|
||||
.setIntervalSec(10)
|
||||
.setBurst(30)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST)
|
||||
);
|
||||
GatewayFlowRule routeRule5 = new GatewayFlowRule(routeId1)
|
||||
.setCount(50)
|
||||
.setIntervalSec(30)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE)
|
||||
.setFieldName(cookieName)
|
||||
);
|
||||
GatewayFlowRule apiRule1 = new GatewayFlowRule(api1)
|
||||
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
|
||||
.setCount(5)
|
||||
.setIntervalSec(1)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
|
||||
.setFieldName(paramName)
|
||||
);
|
||||
rules.add(routeRule1);
|
||||
rules.add(routeRule2);
|
||||
rules.add(routeRule3);
|
||||
rules.add(routeRule4);
|
||||
rules.add(routeRule5);
|
||||
rules.add(routeRuleNoParam);
|
||||
rules.add(apiRule1);
|
||||
GatewayRuleManager.loadRules(rules);
|
||||
|
||||
final String expectedHost = "hello.test.sentinel";
|
||||
final String expectedAddress = "66.77.88.99";
|
||||
final String expectedHeaderValue1 = "Sentinel";
|
||||
final String expectedUrlParamValue1 = "17";
|
||||
final String expectedCookieValue1 = "Sentinel-Foo";
|
||||
|
||||
mockClientHostAddress(itemParser, expectedAddress);
|
||||
Map<String, String> expectedHeaders = new HashMap<String, String>() {{
|
||||
put(headerName, expectedHeaderValue1); put("Host", expectedHost);
|
||||
}};
|
||||
mockHeaders(itemParser, expectedHeaders);
|
||||
mockSingleUrlParam(itemParser, paramName, expectedUrlParamValue1);
|
||||
mockSingleCookie(itemParser, cookieName, expectedCookieValue1);
|
||||
|
||||
Object[] params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate);
|
||||
// Param length should be 6 (5 with parameters, 1 normal flow with generated constant)
|
||||
assertThat(params.length).isEqualTo(6);
|
||||
assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo(expectedAddress);
|
||||
assertThat(params[routeRule2.getParamItem().getIndex()]).isEqualTo(expectedHeaderValue1);
|
||||
assertThat(params[routeRule3.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue1);
|
||||
assertThat(params[routeRule4.getParamItem().getIndex()]).isEqualTo(expectedHost);
|
||||
assertThat(params[routeRule5.getParamItem().getIndex()]).isEqualTo(expectedCookieValue1);
|
||||
assertThat(params[params.length - 1]).isEqualTo(SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM);
|
||||
|
||||
assertThat(paramParser.parseParameterFor(api1, request, routeIdPredicate).length).isZero();
|
||||
|
||||
String expectedUrlParamValue2 = "fs";
|
||||
mockSingleUrlParam(itemParser, paramName, expectedUrlParamValue2);
|
||||
params = paramParser.parseParameterFor(api1, request, apiNamePredicate);
|
||||
assertThat(params.length).isEqualTo(1);
|
||||
assertThat(params[apiRule1.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseParametersWithEmptyItemPattern() {
|
||||
RequestItemParser<Object> itemParser = mock(RequestItemParser.class);
|
||||
GatewayParamParser<Object> paramParser = new GatewayParamParser<>(itemParser);
|
||||
// Create a fake request.
|
||||
Object request = new Object();
|
||||
// Prepare gateway rules.
|
||||
Set<GatewayFlowRule> rules = new HashSet<>();
|
||||
final String routeId = "my_test_route_DS(*H";
|
||||
final String headerName = "X-Sentinel-Flag";
|
||||
GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId)
|
||||
.setCount(10)
|
||||
.setIntervalSec(2)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
|
||||
.setFieldName(headerName)
|
||||
.setPattern("")
|
||||
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT)
|
||||
);
|
||||
rules.add(routeRule1);
|
||||
GatewayRuleManager.loadRules(rules);
|
||||
|
||||
mockSingleHeader(itemParser, headerName, "Sent1nel");
|
||||
Object[] params = paramParser.parseParameterFor(routeId, request, routeIdPredicate);
|
||||
assertThat(params.length).isEqualTo(1);
|
||||
// Empty pattern should not take effect.
|
||||
assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo("Sent1nel");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseParametersWithItemPatternMatching() {
|
||||
RequestItemParser<Object> itemParser = mock(RequestItemParser.class);
|
||||
GatewayParamParser<Object> paramParser = new GatewayParamParser<>(itemParser);
|
||||
// Create a fake request.
|
||||
Object request = new Object();
|
||||
|
||||
// Prepare gateway rules.
|
||||
Set<GatewayFlowRule> rules = new HashSet<>();
|
||||
final String routeId1 = "my_test_route_F&@";
|
||||
final String api1 = "my_test_route_E5K";
|
||||
final String headerName = "X-Sentinel-Flag";
|
||||
final String paramName = "p";
|
||||
|
||||
String nameEquals = "Wow";
|
||||
String nameContains = "warn";
|
||||
String valueRegex = "\\d+";
|
||||
GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId1)
|
||||
.setCount(10)
|
||||
.setIntervalSec(1)
|
||||
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
|
||||
.setMaxQueueingTimeoutMs(600)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
|
||||
.setFieldName(headerName)
|
||||
.setPattern(nameEquals)
|
||||
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT)
|
||||
);
|
||||
GatewayFlowRule routeRule2 = new GatewayFlowRule(routeId1)
|
||||
.setCount(20)
|
||||
.setIntervalSec(1)
|
||||
.setBurst(5)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
|
||||
.setFieldName(paramName)
|
||||
.setPattern(nameContains)
|
||||
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS)
|
||||
);
|
||||
GatewayFlowRule apiRule1 = new GatewayFlowRule(api1)
|
||||
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
|
||||
.setCount(5)
|
||||
.setIntervalSec(1)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
|
||||
.setFieldName(paramName)
|
||||
.setPattern(valueRegex)
|
||||
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX)
|
||||
);
|
||||
rules.add(routeRule1);
|
||||
rules.add(routeRule2);
|
||||
rules.add(apiRule1);
|
||||
GatewayRuleManager.loadRules(rules);
|
||||
|
||||
mockSingleHeader(itemParser, headerName, nameEquals);
|
||||
mockSingleUrlParam(itemParser, paramName, nameContains);
|
||||
Object[] params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate);
|
||||
assertThat(params.length).isEqualTo(2);
|
||||
assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo(nameEquals);
|
||||
assertThat(params[routeRule2.getParamItem().getIndex()]).isEqualTo(nameContains);
|
||||
|
||||
mockSingleHeader(itemParser, headerName, nameEquals + "_foo");
|
||||
mockSingleUrlParam(itemParser, paramName, nameContains + "_foo");
|
||||
params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate);
|
||||
assertThat(params.length).isEqualTo(2);
|
||||
assertThat(params[routeRule1.getParamItem().getIndex()])
|
||||
.isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
|
||||
assertThat(params[routeRule2.getParamItem().getIndex()])
|
||||
.isEqualTo(nameContains + "_foo");
|
||||
|
||||
mockSingleHeader(itemParser, headerName, "foo");
|
||||
mockSingleUrlParam(itemParser, paramName, "foo");
|
||||
params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate);
|
||||
assertThat(params.length).isEqualTo(2);
|
||||
assertThat(params[routeRule1.getParamItem().getIndex()])
|
||||
.isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
|
||||
assertThat(params[routeRule2.getParamItem().getIndex()])
|
||||
.isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
|
||||
|
||||
mockSingleUrlParam(itemParser, paramName, "23");
|
||||
params = paramParser.parseParameterFor(api1, request, apiNamePredicate);
|
||||
assertThat(params.length).isEqualTo(1);
|
||||
assertThat(params[apiRule1.getParamItem().getIndex()]).isEqualTo("23");
|
||||
|
||||
mockSingleUrlParam(itemParser, paramName, "some233");
|
||||
params = paramParser.parseParameterFor(api1, request, apiNamePredicate);
|
||||
assertThat(params.length).isEqualTo(1);
|
||||
assertThat(params[apiRule1.getParamItem().getIndex()])
|
||||
.isEqualTo(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
|
||||
}
|
||||
|
||||
private void mockClientHostAddress(/*@Mock*/ RequestItemParser parser, String address) {
|
||||
when(parser.getRemoteAddress(any())).thenReturn(address);
|
||||
}
|
||||
|
||||
private void mockHeaders(/*@Mock*/ RequestItemParser parser, Map<String, String> headerMap) {
|
||||
for (Map.Entry<String, String> e : headerMap.entrySet()) {
|
||||
when(parser.getHeader(any(), eq(e.getKey()))).thenReturn(e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void mockUrlParams(/*@Mock*/ RequestItemParser parser, Map<String, String> paramMap) {
|
||||
for (Map.Entry<String, String> e : paramMap.entrySet()) {
|
||||
when(parser.getUrlParam(any(), eq(e.getKey()))).thenReturn(e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void mockSingleUrlParam(/*@Mock*/ RequestItemParser parser, String key, String value) {
|
||||
when(parser.getUrlParam(any(), eq(key))).thenReturn(value);
|
||||
}
|
||||
|
||||
private void mockSingleHeader(/*@Mock*/ RequestItemParser parser, String key, String value) {
|
||||
when(parser.getHeader(any(), eq(key))).thenReturn(value);
|
||||
}
|
||||
|
||||
private void mockSingleCookie(/*@Mock*/ RequestItemParser parser, String key, String value) {
|
||||
when(parser.getCookieValue(any(), eq(key))).thenReturn(value);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<ApiDefinition>());
|
||||
GatewayRuleManager.loadRules(new HashSet<GatewayFlowRule>());
|
||||
GatewayRegexCache.clear();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<ApiDefinition>());
|
||||
GatewayRuleManager.loadRules(new HashSet<GatewayFlowRule>());
|
||||
GatewayRegexCache.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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.gateway.common.param;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class GatewayRegexCacheTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
GatewayRegexCache.clear();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
GatewayRegexCache.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddAndGetRegexPattern() {
|
||||
// Test for invalid pattern.
|
||||
assertThat(GatewayRegexCache.addRegexPattern("\\")).isFalse();
|
||||
assertThat(GatewayRegexCache.addRegexPattern(null)).isFalse();
|
||||
// Test for good pattern.
|
||||
String goodPattern = "\\d+";
|
||||
assertThat(GatewayRegexCache.addRegexPattern(goodPattern)).isTrue();
|
||||
assertThat(GatewayRegexCache.getRegexPattern(goodPattern)).isNotNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.rule;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class GatewayRuleConverterTest {
|
||||
|
||||
@Test
|
||||
public void testConvertToFlowRule() {
|
||||
GatewayFlowRule rule = new GatewayFlowRule("routeId1")
|
||||
.setCount(10)
|
||||
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
|
||||
.setMaxQueueingTimeoutMs(1000);
|
||||
FlowRule flowRule = GatewayRuleConverter.toFlowRule(rule);
|
||||
assertEquals(rule.getResource(), flowRule.getResource());
|
||||
assertEquals(rule.getCount(), flowRule.getCount(), 0.01);
|
||||
assertEquals(rule.getControlBehavior(), flowRule.getControlBehavior());
|
||||
assertEquals(rule.getMaxQueueingTimeoutMs(), flowRule.getMaxQueueingTimeMs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertAndApplyToParamRule() {
|
||||
GatewayFlowRule routeRule1 = new GatewayFlowRule("routeId1")
|
||||
.setCount(2)
|
||||
.setIntervalSec(2)
|
||||
.setBurst(2)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
|
||||
);
|
||||
int idx = 1;
|
||||
ParamFlowRule paramRule = GatewayRuleConverter.applyToParamRule(routeRule1, idx);
|
||||
assertEquals(routeRule1.getResource(), paramRule.getResource());
|
||||
assertEquals(routeRule1.getCount(), paramRule.getCount(), 0.01);
|
||||
assertEquals(routeRule1.getControlBehavior(), paramRule.getControlBehavior());
|
||||
assertEquals(routeRule1.getIntervalSec(), paramRule.getDurationInSec());
|
||||
assertEquals(routeRule1.getBurst(), paramRule.getBurstCount());
|
||||
assertEquals(idx, (int)paramRule.getParamIdx());
|
||||
assertEquals(idx, (int)routeRule1.getParamItem().getIndex());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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
|
||||
*
|
||||
* https://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.gateway.common.rule;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class GatewayRuleManagerTest {
|
||||
|
||||
@Test
|
||||
public void testLoadAndGetGatewayRules() {
|
||||
Set<GatewayFlowRule> rules = new HashSet<>();
|
||||
String ahasRoute = "ahas_route";
|
||||
GatewayFlowRule rule1 = new GatewayFlowRule(ahasRoute)
|
||||
.setCount(500)
|
||||
.setIntervalSec(1);
|
||||
GatewayFlowRule rule2 = new GatewayFlowRule(ahasRoute)
|
||||
.setCount(20)
|
||||
.setIntervalSec(2)
|
||||
.setBurst(5)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
|
||||
);
|
||||
GatewayFlowRule rule3 = new GatewayFlowRule("complex_route_ZZZ")
|
||||
.setCount(10)
|
||||
.setIntervalSec(1)
|
||||
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
|
||||
.setMaxQueueingTimeoutMs(600)
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
|
||||
.setFieldName("X-Sentinel-Flag")
|
||||
);
|
||||
rules.add(rule1);
|
||||
rules.add(rule2);
|
||||
rules.add(rule3);
|
||||
GatewayRuleManager.loadRules(rules);
|
||||
|
||||
List<ParamFlowRule> convertedRules = GatewayRuleManager.getConvertedParamRules(ahasRoute);
|
||||
assertNotNull(convertedRules);
|
||||
assertEquals(0, (int)rule2.getParamItem().getIndex());
|
||||
assertEquals(0, (int)rule3.getParamItem().getIndex());
|
||||
assertTrue(GatewayRuleManager.getRulesForResource(ahasRoute).contains(rule1));
|
||||
assertTrue(GatewayRuleManager.getRulesForResource(ahasRoute).contains(rule2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValidRule() {
|
||||
GatewayFlowRule bad1 = new GatewayFlowRule();
|
||||
GatewayFlowRule bad2 = new GatewayFlowRule("abc")
|
||||
.setIntervalSec(0);
|
||||
GatewayFlowRule bad3 = new GatewayFlowRule("abc")
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM));
|
||||
GatewayFlowRule bad4 = new GatewayFlowRule("abc")
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
|
||||
.setFieldName("p")
|
||||
.setPattern("def")
|
||||
.setMatchStrategy(-1)
|
||||
);
|
||||
GatewayFlowRule good1 = new GatewayFlowRule("abc");
|
||||
GatewayFlowRule good2 = new GatewayFlowRule("abc")
|
||||
.setParamItem(new GatewayParamFlowItem().setParseStrategy(0));
|
||||
GatewayFlowRule good3 = new GatewayFlowRule("abc")
|
||||
.setParamItem(new GatewayParamFlowItem()
|
||||
.setMatchStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
|
||||
.setFieldName("Origin")
|
||||
.setPattern("def"));
|
||||
assertFalse(GatewayRuleManager.isValidRule(bad1));
|
||||
assertFalse(GatewayRuleManager.isValidRule(bad2));
|
||||
assertFalse(GatewayRuleManager.isValidRule(bad3));
|
||||
assertFalse(GatewayRuleManager.isValidRule(bad4));
|
||||
|
||||
assertTrue(GatewayRuleManager.isValidRule(good1));
|
||||
assertTrue(GatewayRuleManager.isValidRule(good2));
|
||||
assertTrue(GatewayRuleManager.isValidRule(good3));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
GatewayRuleManager.loadRules(new HashSet<GatewayFlowRule>());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
GatewayRuleManager.loadRules(new HashSet<GatewayFlowRule>());
|
||||
}
|
||||
}
|
||||
72
sentinel/sentinel-adapter/sentinel-dubbo-adapter/README.md
Normal file
72
sentinel/sentinel-adapter/sentinel-dubbo-adapter/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Sentinel Dubbo Adapter
|
||||
|
||||
> Note: 中文文档请见[此处](https://github.com/alibaba/Sentinel/wiki/主流框架的适配#dubbo)。
|
||||
|
||||
Sentinel Dubbo Adapter provides service consumer filter and provider filter
|
||||
for [Dubbo](https://dubbo.apache.org/en-us/) services.
|
||||
|
||||
**Note: This adapter only supports legacy Dubbo 2.6.x version and below.**
|
||||
For new Apache Dubbo 2.7.x or above version, please use `sentinel-apache-dubbo-adapter` module instead.
|
||||
|
||||
To use Sentinel Dubbo Adapter, you can simply add the following dependency to your `pom.xml`:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-dubbo-adapter</artifactId>
|
||||
<version>x.y.z</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
The Sentinel filters are **enabled by default**. Once you add the dependency,
|
||||
the Dubbo services and methods will become protected resources in Sentinel,
|
||||
which can leverage Sentinel's flow control and guard ability when rules are configured.
|
||||
Demos can be found in [sentinel-demo-dubbo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-dubbo).
|
||||
|
||||
If you don't want the filters enabled, you can manually disable them. For example:
|
||||
|
||||
```xml
|
||||
<dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/>
|
||||
|
||||
<dubbo:provider filter="-sentinel.dubbo.provider.filter"/>
|
||||
```
|
||||
|
||||
For more details of Dubbo filter, see [here](http://dubbo.apache.org/en-us/docs/dev/impls/filter.html).
|
||||
|
||||
## Dubbo resources
|
||||
|
||||
The resource for Dubbo services has two granularities: service interface and service method.
|
||||
|
||||
- Service interface:resourceName format is `interfaceName`,e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService`
|
||||
- Service method:resourceName format is `interfaceName:methodSignature`,e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)`
|
||||
|
||||
## Flow control based on caller
|
||||
|
||||
In many circumstances, it's also significant to control traffic flow based on the **caller**.
|
||||
For example, assuming that there are two services A and B, both of them initiate remote call requests to the service provider.
|
||||
If we want to limit the calls from service B only, we can set the `limitApp` of flow rule as the identifier of service B (e.g. service name).
|
||||
|
||||
Sentinel Dubbo Adapter will automatically resolve the Dubbo consumer's *application name* as the caller's name (`origin`),
|
||||
and will bring the caller's name when doing resource protection.
|
||||
If `limitApp` of flow rules is not configured (`default`), flow control will take effects on all callers.
|
||||
If `limitApp` of a flow rule is configured with a caller, then the corresponding flow rule will only take effect on the specific caller.
|
||||
|
||||
> Note: Dubbo consumer does not provide its Dubbo application name when doing RPC,
|
||||
> so developers should manually put the application name into *attachment* at consumer side,
|
||||
> then extract it at provider side. Sentinel Dubbo Adapter has implemented a filter (`DubboAppContextFilter`)
|
||||
> where consumer can carry application name information to provider automatically.
|
||||
> If the consumer does not use Sentinel Dubbo Adapter but requires flow control based on caller,
|
||||
> developers can manually put the application name into attachment with the key `dubboApplication`.
|
||||
>
|
||||
> Since 1.8.0, the adapter provides support for customizing origin parsing logic. You may register your own `DubboOriginParser`
|
||||
> implementation to `DubboAdapterGlobalConfig`.
|
||||
|
||||
## Global fallback
|
||||
|
||||
Sentinel Dubbo Adapter supports global fallback configuration.
|
||||
The global fallback will handle exceptions and give replacement result when blocked by
|
||||
flow control, degrade or system load protection. You can implement your own `DubboFallback` interface
|
||||
and then register to `DubboAdapterGlobalConfig`.
|
||||
If no fallback is configured, Sentinel will wrap the `BlockException` as the fallback result.
|
||||
|
||||
Besides, we can also leverage [Dubbo mock mechanism](http://dubbo.apache.org/en-us/docs/user/demos/local-mock.html) to provide fallback implementation of degraded Dubbo services.
|
||||
48
sentinel/sentinel-adapter/sentinel-dubbo-adapter/pom.xml
Normal file
48
sentinel/sentinel-adapter/sentinel-dubbo-adapter/pom.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-adapter</artifactId>
|
||||
<version>1.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>sentinel-dubbo-adapter</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<dubbo.version>2.6.6</dubbo.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>${dubbo.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>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.dubbo.rpc.Filter;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
|
||||
/**
|
||||
* @author leyou
|
||||
*/
|
||||
abstract class AbstractDubboFilter implements Filter {
|
||||
|
||||
protected String getMethodResourceName(Invoker<?> invoker, Invocation invocation) {
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
buf.append(invoker.getInterface().getName())
|
||||
.append(":")
|
||||
.append(invocation.getMethodName())
|
||||
.append("(");
|
||||
boolean isFirst = true;
|
||||
for (Class<?> clazz : invocation.getParameterTypes()) {
|
||||
if (!isFirst) {
|
||||
buf.append(",");
|
||||
}
|
||||
buf.append(clazz.getName());
|
||||
isFirst = false;
|
||||
}
|
||||
buf.append(")");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
protected String getMethodResourceName(Invoker<?> invoker, Invocation invocation, String prefix) {
|
||||
if (StringUtil.isBlank(prefix)) {
|
||||
return getMethodResourceName(invoker, invocation);
|
||||
}
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
return buf.append(prefix)
|
||||
.append(getMethodResourceName(invoker, invocation))
|
||||
.toString();
|
||||
}
|
||||
|
||||
protected String getInterfaceName(Invoker<?> invoker) {
|
||||
return invoker.getInterface().getName();
|
||||
}
|
||||
|
||||
protected String getInterfaceName(Invoker<?> invoker, String prefix) {
|
||||
if (StringUtil.isBlank(prefix)) {
|
||||
return getInterfaceName(invoker);
|
||||
}
|
||||
return prefix + getInterfaceName(invoker);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DefaultDubboFallback;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallback;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.origin.DefaultDubboOriginParser;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.origin.DubboOriginParser;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/**
|
||||
* <p>Global config and callback registry of Dubbo legacy adapter.</p>
|
||||
*
|
||||
* @author lianglin
|
||||
* @author Eric Zhao
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public final class DubboAdapterGlobalConfig {
|
||||
|
||||
private static final String TRUE_STR = "true";
|
||||
|
||||
public static final String DUBBO_RES_NAME_WITH_PREFIX_KEY = "csp.sentinel.dubbo.resource.use.prefix";
|
||||
public static final String DUBBO_PROVIDER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.provider.prefix";
|
||||
public static final String DUBBO_CONSUMER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.consumer.prefix";
|
||||
|
||||
private static final String DEFAULT_DUBBO_PROVIDER_PREFIX = "dubbo:provider:";
|
||||
private static final String DEFAULT_DUBBO_CONSUMER_PREFIX = "dubbo:consumer:";
|
||||
|
||||
private static volatile DubboFallback consumerFallback = new DefaultDubboFallback();
|
||||
private static volatile DubboFallback providerFallback = new DefaultDubboFallback();
|
||||
private static volatile DubboOriginParser originParser = new DefaultDubboOriginParser();
|
||||
|
||||
public static boolean isUsePrefix() {
|
||||
return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_RES_NAME_WITH_PREFIX_KEY));
|
||||
}
|
||||
|
||||
public static String getDubboProviderPrefix() {
|
||||
if (isUsePrefix()) {
|
||||
String config = SentinelConfig.getConfig(DUBBO_PROVIDER_RES_NAME_PREFIX_KEY);
|
||||
return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_PROVIDER_PREFIX;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getDubboConsumerPrefix() {
|
||||
if (isUsePrefix()) {
|
||||
String config = SentinelConfig.getConfig(DUBBO_CONSUMER_RES_NAME_PREFIX_KEY);
|
||||
return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_CONSUMER_PREFIX;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static DubboFallback getConsumerFallback() {
|
||||
return consumerFallback;
|
||||
}
|
||||
|
||||
public static void setConsumerFallback(DubboFallback consumerFallback) {
|
||||
AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null");
|
||||
DubboAdapterGlobalConfig.consumerFallback = consumerFallback;
|
||||
}
|
||||
|
||||
public static DubboFallback getProviderFallback() {
|
||||
return providerFallback;
|
||||
}
|
||||
|
||||
public static void setProviderFallback(DubboFallback providerFallback) {
|
||||
AssertUtil.notNull(providerFallback, "providerFallback cannot be null");
|
||||
DubboAdapterGlobalConfig.providerFallback = providerFallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the origin parser of Dubbo adapter.
|
||||
*
|
||||
* @return the origin parser
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public static DubboOriginParser getOriginParser() {
|
||||
return originParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the origin parser of Dubbo adapter.
|
||||
*
|
||||
* @param originParser the origin parser
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public static void setOriginParser(DubboOriginParser originParser) {
|
||||
AssertUtil.notNull(originParser, "originParser cannot be null");
|
||||
DubboAdapterGlobalConfig.originParser = originParser;
|
||||
}
|
||||
|
||||
private DubboAdapterGlobalConfig() {}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import com.alibaba.dubbo.common.Constants;
|
||||
import com.alibaba.dubbo.common.extension.Activate;
|
||||
import com.alibaba.dubbo.rpc.Filter;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.Result;
|
||||
import com.alibaba.dubbo.rpc.RpcContext;
|
||||
import com.alibaba.dubbo.rpc.RpcException;
|
||||
|
||||
import static com.alibaba.dubbo.common.Constants.CONSUMER;
|
||||
|
||||
/**
|
||||
* Puts current consumer's application name in the attachment of each invocation.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@Activate(group = CONSUMER)
|
||||
public class DubboAppContextFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
String application = invoker.getUrl().getParameter(Constants.APPLICATION_KEY);
|
||||
if (application != null) {
|
||||
RpcContext.getContext().setAttachment(DubboUtils.DUBBO_APPLICATION_KEY, application);
|
||||
}
|
||||
return invoker.invoke(invocation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public final class DubboUtils {
|
||||
|
||||
public static final String DUBBO_APPLICATION_KEY = "dubboApplication";
|
||||
|
||||
public static String getApplication(Invocation invocation, String defaultValue) {
|
||||
if (invocation == null || invocation.getAttachments() == null) {
|
||||
throw new IllegalArgumentException("Bad invocation instance");
|
||||
}
|
||||
return invocation.getAttachment(DUBBO_APPLICATION_KEY, defaultValue);
|
||||
}
|
||||
|
||||
private DubboUtils() {}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
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.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.dubbo.common.extension.Activate;
|
||||
import com.alibaba.dubbo.rpc.Filter;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.Result;
|
||||
import com.alibaba.dubbo.rpc.RpcException;
|
||||
|
||||
import static com.alibaba.dubbo.common.Constants.CONSUMER;
|
||||
|
||||
/**
|
||||
* <p>Dubbo service consumer filter for Sentinel. Auto activated by default.</p>
|
||||
*
|
||||
* If you want to disable the consumer filter, you can configure:
|
||||
* <pre>
|
||||
* <dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/>
|
||||
* </pre>
|
||||
*
|
||||
* @author leyou
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@Activate(group = CONSUMER)
|
||||
public class SentinelDubboConsumerFilter extends AbstractDubboFilter implements Filter {
|
||||
|
||||
public SentinelDubboConsumerFilter() {
|
||||
RecordLog.info("Sentinel Dubbo consumer filter initialized");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
Entry interfaceEntry = null;
|
||||
Entry methodEntry = null;
|
||||
try {
|
||||
String prefix = DubboAdapterGlobalConfig.getDubboConsumerPrefix();
|
||||
String interfaceResourceName = getInterfaceName(invoker, prefix);
|
||||
String methodResourceName = getMethodResourceName(invoker, invocation, prefix);
|
||||
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT);
|
||||
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC,
|
||||
EntryType.OUT, invocation.getArguments());
|
||||
|
||||
Result result = invoker.invoke(invocation);
|
||||
if (result.hasException()) {
|
||||
Throwable e = result.getException();
|
||||
// Record common exception.
|
||||
Tracer.traceEntry(e, interfaceEntry);
|
||||
Tracer.traceEntry(e, methodEntry);
|
||||
}
|
||||
return result;
|
||||
} catch (BlockException e) {
|
||||
return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e);
|
||||
} catch (RpcException e) {
|
||||
Tracer.traceEntry(e, interfaceEntry);
|
||||
Tracer.traceEntry(e, methodEntry);
|
||||
throw e;
|
||||
} finally {
|
||||
if (methodEntry != null) {
|
||||
methodEntry.exit(1, invocation.getArguments());
|
||||
}
|
||||
if (interfaceEntry != null) {
|
||||
interfaceEntry.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo;
|
||||
|
||||
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.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.dubbo.common.extension.Activate;
|
||||
import com.alibaba.dubbo.rpc.Filter;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.Result;
|
||||
import com.alibaba.dubbo.rpc.RpcException;
|
||||
|
||||
import static com.alibaba.dubbo.common.Constants.PROVIDER;
|
||||
|
||||
/**
|
||||
* <p>Dubbo service provider filter for Sentinel. Auto activated by default.</p>
|
||||
*
|
||||
* If you want to disable the provider filter, you can configure:
|
||||
* <pre>
|
||||
* <dubbo:provider filter="-sentinel.dubbo.provider.filter"/>
|
||||
* </pre>
|
||||
*
|
||||
* @author leyou
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@Activate(group = PROVIDER)
|
||||
public class SentinelDubboProviderFilter extends AbstractDubboFilter implements Filter {
|
||||
|
||||
public SentinelDubboProviderFilter() {
|
||||
RecordLog.info("Sentinel Dubbo provider filter initialized");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
// Get origin caller.
|
||||
String origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation);
|
||||
if (null == origin) {
|
||||
origin = "";
|
||||
}
|
||||
|
||||
Entry interfaceEntry = null;
|
||||
Entry methodEntry = null;
|
||||
try {
|
||||
String prefix = DubboAdapterGlobalConfig.getDubboProviderPrefix();
|
||||
String methodResourceName = getMethodResourceName(invoker, invocation, prefix);
|
||||
String interfaceName = getInterfaceName(invoker, prefix);
|
||||
ContextUtil.enter(methodResourceName, origin);
|
||||
interfaceEntry = SphU.entry(interfaceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN);
|
||||
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC,
|
||||
EntryType.IN, invocation.getArguments());
|
||||
|
||||
Result result = invoker.invoke(invocation);
|
||||
if (result.hasException()) {
|
||||
Throwable e = result.getException();
|
||||
// Record common exception.
|
||||
Tracer.traceEntry(e, interfaceEntry);
|
||||
Tracer.traceEntry(e, methodEntry);
|
||||
}
|
||||
return result;
|
||||
} catch (BlockException e) {
|
||||
return DubboAdapterGlobalConfig.getProviderFallback().handle(invoker, invocation, e);
|
||||
} catch (RpcException e) {
|
||||
Tracer.traceEntry(e, interfaceEntry);
|
||||
Tracer.traceEntry(e, methodEntry);
|
||||
throw e;
|
||||
} finally {
|
||||
if (methodEntry != null) {
|
||||
methodEntry.exit(1, invocation.getArguments());
|
||||
}
|
||||
if (interfaceEntry != null) {
|
||||
interfaceEntry.exit();
|
||||
}
|
||||
ContextUtil.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.fallback;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.block.SentinelRpcException;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.Result;
|
||||
import com.alibaba.dubbo.rpc.RpcResult;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class DefaultDubboFallback implements DubboFallback {
|
||||
|
||||
@Override
|
||||
public Result handle(Invoker<?> invoker, Invocation invocation, BlockException ex) {
|
||||
// Just wrap the exception. edit by wzg923 2020/9/23
|
||||
RpcResult result = new RpcResult();
|
||||
result.setException(new SentinelRpcException(ex.toRuntimeException()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.fallback;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.Result;
|
||||
|
||||
/**
|
||||
* Fallback handler for Dubbo services.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface DubboFallback {
|
||||
|
||||
/**
|
||||
* Handle the block exception and provide fallback result.
|
||||
*
|
||||
* @param invoker Dubbo invoker
|
||||
* @param invocation Dubbo invocation
|
||||
* @param ex block exception
|
||||
* @return fallback result
|
||||
*/
|
||||
Result handle(Invoker<?> invoker, Invocation invocation, BlockException ex);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.fallback;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.DubboAdapterGlobalConfig;
|
||||
|
||||
/**
|
||||
* <p>Global fallback registry for Dubbo.</p>
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @deprecated use {@link DubboAdapterGlobalConfig} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class DubboFallbackRegistry {
|
||||
|
||||
public static DubboFallback getConsumerFallback() {
|
||||
return DubboAdapterGlobalConfig.getConsumerFallback();
|
||||
}
|
||||
|
||||
public static void setConsumerFallback(DubboFallback consumerFallback) {
|
||||
DubboAdapterGlobalConfig.setConsumerFallback(consumerFallback);
|
||||
}
|
||||
|
||||
public static DubboFallback getProviderFallback() {
|
||||
return DubboAdapterGlobalConfig.getProviderFallback();
|
||||
}
|
||||
|
||||
public static void setProviderFallback(DubboFallback providerFallback) {
|
||||
DubboAdapterGlobalConfig.setProviderFallback(providerFallback);
|
||||
}
|
||||
|
||||
private DubboFallbackRegistry() {}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.dubbo.origin;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
|
||||
/**
|
||||
* Default Dubbo origin parser.
|
||||
*
|
||||
* @author tiecheng
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public class DefaultDubboOriginParser implements DubboOriginParser {
|
||||
|
||||
@Override
|
||||
public String parse(Invoker<?> invoker, Invocation invocation) {
|
||||
return DubboUtils.getApplication(invocation, "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.dubbo.origin;
|
||||
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
|
||||
/**
|
||||
* Customized origin parser for Dubbo provider filter.
|
||||
*
|
||||
* @author tiecheng
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public interface DubboOriginParser {
|
||||
|
||||
/**
|
||||
* Parses the origin (caller) from Dubbo invocation.
|
||||
*
|
||||
* @param invoker Dubbo invoker
|
||||
* @param invocation Dubbo invocation
|
||||
* @return the parsed origin
|
||||
*/
|
||||
String parse(Invoker<?> invoker, Invocation invocation);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
sentinel.dubbo.provider.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter
|
||||
sentinel.dubbo.consumer.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboConsumerFilter
|
||||
dubbo.application.context.name.filter=com.alibaba.csp.sentinel.adapter.dubbo.DubboAppContextFilter
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.alibaba.csp.sentinel;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
import com.alibaba.dubbo.rpc.RpcContext;
|
||||
|
||||
/**
|
||||
* Base test class, provide common methods for subClass
|
||||
* The package is same as CtSph, to call CtSph.resetChainMap() method for test
|
||||
*
|
||||
* Note: Only for test. DO NOT USE IN PRODUCTION!
|
||||
*
|
||||
* @author cdfive
|
||||
*/
|
||||
public class BaseTest {
|
||||
|
||||
/**
|
||||
* Clean up resources for context, clusterNodeMap, processorSlotChainMap
|
||||
*/
|
||||
protected static void cleanUpAll() {
|
||||
RpcContext.removeContext();
|
||||
ClusterBuilderSlot.getClusterNodeMap().clear();
|
||||
CtSph.resetChainMap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.alibaba.csp.sentinel.adapter.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.Result;
|
||||
import com.alibaba.dubbo.rpc.RpcException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author cdfive
|
||||
*/
|
||||
public class AbstractDubboFilterTest {
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "true");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "");
|
||||
}
|
||||
|
||||
private AbstractDubboFilter filter = new AbstractDubboFilter() {
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetResourceName() {
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
when(invoker.getInterface()).thenReturn(DemoService.class);
|
||||
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
Method method = DemoService.class.getMethods()[0];
|
||||
when(invocation.getMethodName()).thenReturn(method.getName());
|
||||
when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
|
||||
|
||||
String resourceName = filter.getMethodResourceName(invoker, invocation);
|
||||
|
||||
assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetResourceNameWithPrefix() {
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
when(invoker.getInterface()).thenReturn(DemoService.class);
|
||||
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
Method method = DemoService.class.getMethods()[0];
|
||||
when(invocation.getMethodName()).thenReturn(method.getName());
|
||||
when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
|
||||
|
||||
//test with default prefix
|
||||
String resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderPrefix());
|
||||
System.out.println("resourceName = " + resourceName);
|
||||
assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
|
||||
resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerPrefix());
|
||||
assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
|
||||
|
||||
|
||||
//test with custom prefix
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:");
|
||||
SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:");
|
||||
resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderPrefix());
|
||||
assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
|
||||
resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerPrefix());
|
||||
assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.alibaba.csp.sentinel.adapter.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.BaseTest;
|
||||
import com.alibaba.dubbo.common.URL;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.RpcContext;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author cdfive
|
||||
*/
|
||||
public class DubboAppContextFilterTest extends BaseTest {
|
||||
|
||||
private DubboAppContextFilter filter = new DubboAppContextFilter();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cleanUpAll();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
cleanUpAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokeApplicationKey() {
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
URL url = URL.valueOf("test://test:111/test?application=serviceA");
|
||||
when(invoker.getUrl()).thenReturn(url);
|
||||
|
||||
filter.invoke(invoker, invocation);
|
||||
verify(invoker).invoke(invocation);
|
||||
|
||||
String application = RpcContext.getContext().getAttachment(DubboUtils.DUBBO_APPLICATION_KEY);
|
||||
assertEquals("serviceA", application);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokeNullApplicationKey() {
|
||||
Invoker invoker = mock(Invoker.class);
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
URL url = URL.valueOf("test://test:111/test?application=");
|
||||
when(invoker.getUrl()).thenReturn(url);
|
||||
|
||||
filter.invoke(invoker, invocation);
|
||||
verify(invoker).invoke(invocation);
|
||||
|
||||
String application = RpcContext.getContext().getAttachment(DubboUtils.DUBBO_APPLICATION_KEY);
|
||||
assertNull(application);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.alibaba.csp.sentinel.adapter.dubbo;
|
||||
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author cdfive
|
||||
*/
|
||||
public class DubboUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testGetApplication() {
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
when(invocation.getAttachments()).thenReturn(new HashMap<String, String>());
|
||||
when(invocation.getAttachment(DubboUtils.DUBBO_APPLICATION_KEY, "")).thenReturn("consumerA");
|
||||
|
||||
String application = DubboUtils.getApplication(invocation, "");
|
||||
verify(invocation).getAttachment(DubboUtils.DUBBO_APPLICATION_KEY, "");
|
||||
|
||||
assertEquals("consumerA", application);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetApplicationNoAttachments() {
|
||||
Invocation invocation = mock(Invocation.class);
|
||||
when(invocation.getAttachments()).thenReturn(null);
|
||||
when(invocation.getAttachment(DubboUtils.DUBBO_APPLICATION_KEY, "")).thenReturn("consumerA");
|
||||
|
||||
DubboUtils.getApplication(invocation, "");
|
||||
|
||||
fail("No attachments in invocation, IllegalArgumentException should be thrown!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.alibaba.csp.sentinel.adapter.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.BaseTest;
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.node.Node;
|
||||
import com.alibaba.csp.sentinel.node.StatisticNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.Result;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author cdfive
|
||||
*/
|
||||
public class SentinelDubboConsumerFilterTest extends BaseTest {
|
||||
|
||||
private SentinelDubboConsumerFilter filter = new SentinelDubboConsumerFilter();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cleanUpAll();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
cleanUpAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvoke() {
|
||||
final Invoker invoker = mock(Invoker.class);
|
||||
when(invoker.getInterface()).thenReturn(DemoService.class);
|
||||
|
||||
final Invocation invocation = mock(Invocation.class);
|
||||
Method method = DemoService.class.getMethods()[0];
|
||||
when(invocation.getMethodName()).thenReturn(method.getName());
|
||||
when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
|
||||
|
||||
final Result result = mock(Result.class);
|
||||
when(result.hasException()).thenReturn(false);
|
||||
when(invoker.invoke(invocation)).thenAnswer(new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
verifyInvocationStructure(invoker, invocation);
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
filter.invoke(invoker, invocation);
|
||||
verify(invoker).invoke(invocation);
|
||||
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNull(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply verify invocation structure in memory:
|
||||
* EntranceNode(defaultContextName)
|
||||
* --InterfaceNode(interfaceName)
|
||||
* ----MethodNode(resourceName)
|
||||
*/
|
||||
private void verifyInvocationStructure(Invoker invoker, Invocation invocation) {
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNotNull(context);
|
||||
|
||||
// As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context
|
||||
// In actual project, a consumer is usually also a provider, the context will be created by SentinelDubboProviderFilter
|
||||
// If consumer is on the top of Dubbo RPC invocation chain, use default context
|
||||
String resourceName = filter.getMethodResourceName(invoker, invocation);
|
||||
assertEquals(Constants.CONTEXT_DEFAULT_NAME, context.getName());
|
||||
assertEquals("", context.getOrigin());
|
||||
|
||||
DefaultNode entranceNode = context.getEntranceNode();
|
||||
ResourceWrapper entranceResource = entranceNode.getId();
|
||||
assertEquals(Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName());
|
||||
assertSame(EntryType.IN, entranceResource.getEntryType());
|
||||
|
||||
// As SphU.entry(interfaceName, EntryType.OUT);
|
||||
Set<Node> childList = entranceNode.getChildList();
|
||||
assertEquals(1, childList.size());
|
||||
DefaultNode interfaceNode = (DefaultNode) childList.iterator().next();
|
||||
ResourceWrapper interfaceResource = interfaceNode.getId();
|
||||
assertEquals(DemoService.class.getName(), interfaceResource.getName());
|
||||
assertSame(EntryType.OUT, interfaceResource.getEntryType());
|
||||
|
||||
// As SphU.entry(resourceName, EntryType.OUT);
|
||||
childList = interfaceNode.getChildList();
|
||||
assertEquals(1, childList.size());
|
||||
DefaultNode methodNode = (DefaultNode) childList.iterator().next();
|
||||
ResourceWrapper methodResource = methodNode.getId();
|
||||
assertEquals(resourceName, methodResource.getName());
|
||||
assertSame(EntryType.OUT, methodResource.getEntryType());
|
||||
|
||||
// Verify curEntry
|
||||
Entry curEntry = context.getCurEntry();
|
||||
assertSame(methodNode, curEntry.getCurNode());
|
||||
assertSame(interfaceNode, curEntry.getLastNode());
|
||||
assertNull(curEntry.getOriginNode());// As context origin is not "", no originNode should be created in curEntry
|
||||
|
||||
// Verify clusterNode
|
||||
ClusterNode methodClusterNode = methodNode.getClusterNode();
|
||||
ClusterNode interfaceClusterNode = interfaceNode.getClusterNode();
|
||||
assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode
|
||||
|
||||
// As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode
|
||||
Map<String, StatisticNode> methodOriginCountMap = methodClusterNode.getOriginCountMap();
|
||||
assertEquals(0, methodOriginCountMap.size());
|
||||
|
||||
Map<String, StatisticNode> interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap();
|
||||
assertEquals(0, interfaceOriginCountMap.size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.alibaba.csp.sentinel.adapter.dubbo;
|
||||
|
||||
import com.alibaba.csp.sentinel.BaseTest;
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.context.ContextUtil;
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.node.Node;
|
||||
import com.alibaba.csp.sentinel.node.StatisticNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.Result;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author cdfive
|
||||
*/
|
||||
public class SentinelDubboProviderFilterTest extends BaseTest {
|
||||
|
||||
private SentinelDubboProviderFilter filter = new SentinelDubboProviderFilter();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cleanUpAll();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
cleanUpAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvoke() {
|
||||
final String originApplication = "consumerA";
|
||||
|
||||
final Invoker invoker = mock(Invoker.class);
|
||||
when(invoker.getInterface()).thenReturn(DemoService.class);
|
||||
|
||||
final Invocation invocation = mock(Invocation.class);
|
||||
Method method = DemoService.class.getMethods()[0];
|
||||
when(invocation.getMethodName()).thenReturn(method.getName());
|
||||
when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
|
||||
when(invocation.getAttachment(DubboUtils.DUBBO_APPLICATION_KEY, "")).thenReturn(originApplication);
|
||||
|
||||
final Result result = mock(Result.class);
|
||||
when(result.hasException()).thenReturn(false);
|
||||
when(invoker.invoke(invocation)).thenAnswer(new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
verifyInvocationStructure(originApplication, invoker, invocation);
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
filter.invoke(invoker, invocation);
|
||||
verify(invoker).invoke(invocation);
|
||||
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNull(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply verify invocation structure in memory:
|
||||
* EntranceNode(resourceName)
|
||||
* --InterfaceNode(interfaceName)
|
||||
* ----MethodNode(resourceName)
|
||||
*/
|
||||
private void verifyInvocationStructure(String originApplication, Invoker invoker, Invocation invocation) {
|
||||
Context context = ContextUtil.getContext();
|
||||
assertNotNull(context);
|
||||
|
||||
// As ContextUtil.enter(resourceName, application) in SentinelDubboProviderFilter
|
||||
String resourceName = filter.getMethodResourceName(invoker, invocation);
|
||||
assertEquals(resourceName, context.getName());
|
||||
assertEquals(originApplication, context.getOrigin());
|
||||
|
||||
DefaultNode entranceNode = context.getEntranceNode();
|
||||
ResourceWrapper entranceResource = entranceNode.getId();
|
||||
assertEquals(resourceName, entranceResource.getName());
|
||||
assertSame(EntryType.IN, entranceResource.getEntryType());
|
||||
|
||||
// As SphU.entry(interfaceName, EntryType.IN);
|
||||
Set<Node> childList = entranceNode.getChildList();
|
||||
assertEquals(1, childList.size());
|
||||
DefaultNode interfaceNode = (DefaultNode) childList.iterator().next();
|
||||
ResourceWrapper interfaceResource = interfaceNode.getId();
|
||||
assertEquals(DemoService.class.getName(), interfaceResource.getName());
|
||||
assertSame(EntryType.IN, interfaceResource.getEntryType());
|
||||
|
||||
// As SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments());
|
||||
childList = interfaceNode.getChildList();
|
||||
assertEquals(1, childList.size());
|
||||
DefaultNode methodNode = (DefaultNode) childList.iterator().next();
|
||||
ResourceWrapper methodResource = methodNode.getId();
|
||||
assertEquals(resourceName, methodResource.getName());
|
||||
assertSame(EntryType.IN, methodResource.getEntryType());
|
||||
|
||||
// Verify curEntry
|
||||
Entry curEntry = context.getCurEntry();
|
||||
assertSame(methodNode, curEntry.getCurNode());
|
||||
assertSame(interfaceNode, curEntry.getLastNode());
|
||||
assertNotNull(curEntry.getOriginNode());// As context origin is not "", originNode should be created
|
||||
|
||||
// Verify clusterNode
|
||||
ClusterNode methodClusterNode = methodNode.getClusterNode();
|
||||
ClusterNode interfaceClusterNode = interfaceNode.getClusterNode();
|
||||
assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode
|
||||
|
||||
// As context origin is not "", the StatisticNode should be created in originCountMap of ClusterNode
|
||||
Map<String, StatisticNode> methodOriginCountMap = methodClusterNode.getOriginCountMap();
|
||||
assertEquals(1, methodOriginCountMap.size());
|
||||
assertTrue(methodOriginCountMap.containsKey(originApplication));
|
||||
|
||||
Map<String, StatisticNode> interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap();
|
||||
assertEquals(1, interfaceOriginCountMap.size());
|
||||
assertTrue(interfaceOriginCountMap.containsKey(originApplication));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.fallback;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.DubboAdapterGlobalConfig;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.block.SentinelRpcException;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.Result;
|
||||
import com.alibaba.dubbo.rpc.RpcResult;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class DubboFallbackRegistryTest {
|
||||
|
||||
@Test
|
||||
public void testDefaultFallback() {
|
||||
// Test for default fallback.
|
||||
BlockException ex = new FlowException("xxx");
|
||||
Result result = new DefaultDubboFallback().handle(null, null, ex);
|
||||
Assert.assertTrue(result.hasException());
|
||||
Assert.assertEquals(SentinelRpcException.class, result.getException().getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomFallback() {
|
||||
BlockException ex = new FlowException("xxx");
|
||||
DubboAdapterGlobalConfig.setConsumerFallback(new DubboFallback() {
|
||||
@Override
|
||||
public Result handle(Invoker<?> invoker, Invocation invocation, BlockException e) {
|
||||
return new RpcResult("Error: " + e.getClass().getName());
|
||||
}
|
||||
});
|
||||
Result result = DubboAdapterGlobalConfig.getConsumerFallback()
|
||||
.handle(null, null, ex);
|
||||
Assert.assertFalse("The invocation should not fail", result.hasException());
|
||||
Assert.assertEquals("Error: " + ex.getClass().getName(), result.getValue());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
* https://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.dubbo.origin;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.DubboAdapterGlobalConfig;
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.RpcInvocation;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author tiecheng
|
||||
*/
|
||||
public class DubboOriginRegistryTest {
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
DubboAdapterGlobalConfig.setOriginParser(new DefaultDubboOriginParser());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testDefaultOriginParserFail() {
|
||||
DubboAdapterGlobalConfig.getOriginParser().parse(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultOriginParserSuccess() {
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
String dubboName = "sentinel";
|
||||
invocation.setAttachment(DubboUtils.DUBBO_APPLICATION_KEY, dubboName);
|
||||
String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation);
|
||||
Assert.assertEquals(dubboName, origin);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomOriginParser() {
|
||||
DubboAdapterGlobalConfig.setOriginParser(new DubboOriginParser() {
|
||||
@Override
|
||||
public String parse(Invoker<?> invoker, Invocation invocation) {
|
||||
return invocation.getAttachment(DubboUtils.DUBBO_APPLICATION_KEY, "default") + "_" + invocation
|
||||
.getMethodName();
|
||||
}
|
||||
});
|
||||
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation);
|
||||
Assert.assertEquals("default_null", origin);
|
||||
|
||||
String dubboName = "sentinel";
|
||||
invocation.setAttachment(DubboUtils.DUBBO_APPLICATION_KEY, dubboName);
|
||||
origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation);
|
||||
Assert.assertEquals(dubboName + "_null", origin);
|
||||
|
||||
invocation.setMethodName("hello");
|
||||
origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation);
|
||||
Assert.assertEquals(dubboName + "_hello", origin);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.provider;
|
||||
|
||||
/**
|
||||
* @author leyou
|
||||
*/
|
||||
public interface DemoService {
|
||||
String sayHello(String name, int n);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.dubbo.provider.impl;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
|
||||
|
||||
/**
|
||||
* @author leyou
|
||||
*/
|
||||
public class DemoServiceImpl implements DemoService {
|
||||
public String sayHello(String name, int n) {
|
||||
return "Hello " + name + ", " + n;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://code.alibabatech.com/schema/dubbo
|
||||
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<dubbo:application name="demo-consumer"/>
|
||||
<dubbo:registry address="multicast://224.5.6.7:1234"/>
|
||||
<dubbo:protocol name="dubbo" port="20880"/>
|
||||
<dubbo:service interface="com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService" ref="demoServiceImp" />
|
||||
<bean id="demoServiceImp" class="com.alibaba.csp.sentinel.adapter.dubbo.provider.impl.DemoServiceImpl"/>
|
||||
|
||||
<dubbo:reference id="demoService" interface="com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService"/>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://code.alibabatech.com/schema/dubbo
|
||||
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<dubbo:application name="demo-provider"/>
|
||||
<dubbo:registry address="multicast://224.5.6.7:1234"/>
|
||||
<dubbo:protocol name="dubbo" port="20880"/>
|
||||
<dubbo:service interface="com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService" ref="demoServiceImp" />
|
||||
<bean id="demoServiceImp" class="com.alibaba.csp.sentinel.adapter.dubbo.provider.impl.DemoServiceImpl"/>
|
||||
|
||||
<dubbo:reference id="demoService" interface="com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService"/>
|
||||
|
||||
</beans>
|
||||
36
sentinel/sentinel-adapter/sentinel-grpc-adapter/README.md
Normal file
36
sentinel/sentinel-adapter/sentinel-grpc-adapter/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Sentinel gRPC Adapter
|
||||
|
||||
Sentinel gRPC Adapter provides client and server interceptor for gRPC services.
|
||||
|
||||
> Note that currently the interceptor only supports unary methods in gRPC.
|
||||
|
||||
## Client Interceptor
|
||||
|
||||
Example:
|
||||
|
||||
```java
|
||||
public class ServiceClient {
|
||||
|
||||
private final ManagedChannel channel;
|
||||
|
||||
ServiceClient(String host, int port) {
|
||||
this.channel = ManagedChannelBuilder.forAddress(host, port)
|
||||
.intercept(new SentinelGrpcClientInterceptor()) // Add the client interceptor.
|
||||
.build();
|
||||
// Init your stub here.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Server Interceptor
|
||||
|
||||
Example:
|
||||
|
||||
```java
|
||||
import io.grpc.Server;
|
||||
|
||||
Server server = ServerBuilder.forPort(port)
|
||||
.addService(new MyServiceImpl()) // Add your service.
|
||||
.intercept(new SentinelGrpcServerInterceptor()) // Add the server interceptor.
|
||||
.build();
|
||||
```
|
||||
98
sentinel/sentinel-adapter/sentinel-grpc-adapter/pom.xml
Normal file
98
sentinel/sentinel-adapter/sentinel-grpc-adapter/pom.xml
Normal file
@@ -0,0 +1,98 @@
|
||||
<?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.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>sentinel-grpc-adapter</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<grpc.version>1.30.2</grpc.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty</artifactId>
|
||||
<version>${grpc.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
<version>${grpc.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-stub</artifactId>
|
||||
<version>${grpc.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- workaround for compile in JDK 11 -->
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>${javax.annotation-api.version}</version>
|
||||
</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>
|
||||
<version>${fastjson.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
<version>1.5.0.Final</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.xolstice.maven.plugins</groupId>
|
||||
<artifactId>protobuf-maven-plugin</artifactId>
|
||||
<version>0.5.1</version>
|
||||
<configuration>
|
||||
<protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
|
||||
<pluginId>grpc-java</pluginId>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
|
||||
</pluginArtifact>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
<goal>test-compile-custom</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.grpc;
|
||||
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.SphU;
|
||||
import com.alibaba.csp.sentinel.Tracer;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.ClientCall;
|
||||
import io.grpc.ClientInterceptor;
|
||||
import io.grpc.ForwardingClientCall;
|
||||
import io.grpc.ForwardingClientCallListener;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.Status;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* <p>gRPC client interceptor for Sentinel. Currently it only works with unary methods.</p>
|
||||
* <p>
|
||||
* Example code:
|
||||
* <pre>
|
||||
* public class ServiceClient {
|
||||
*
|
||||
* private final ManagedChannel channel;
|
||||
*
|
||||
* ServiceClient(String host, int port) {
|
||||
* this.channel = ManagedChannelBuilder.forAddress(host, port)
|
||||
* .intercept(new SentinelGrpcClientInterceptor()) // Add the client interceptor.
|
||||
* .build();
|
||||
* // Init your stub here.
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* For server interceptor, see {@link SentinelGrpcServerInterceptor}.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class SentinelGrpcClientInterceptor implements ClientInterceptor {
|
||||
private static final Status FLOW_CONTROL_BLOCK = Status.UNAVAILABLE.withDescription(
|
||||
"Flow control limit exceeded (client side)");
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> methodDescriptor,
|
||||
CallOptions callOptions, Channel channel) {
|
||||
String fullMethodName = methodDescriptor.getFullMethodName();
|
||||
Entry entry = null;
|
||||
try {
|
||||
entry = SphU.asyncEntry(fullMethodName, EntryType.OUT);
|
||||
final AtomicReference<Entry> atomicReferenceEntry = new AtomicReference<>(entry);
|
||||
// Allow access, forward the call.
|
||||
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
|
||||
channel.newCall(methodDescriptor, callOptions)) {
|
||||
@Override
|
||||
public void start(Listener<RespT> responseListener, Metadata headers) {
|
||||
super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {
|
||||
@Override
|
||||
public void onClose(Status status, Metadata trailers) {
|
||||
Entry entry = atomicReferenceEntry.get();
|
||||
if (entry != null) {
|
||||
// Record the exception metrics.
|
||||
if (!status.isOk()) {
|
||||
Tracer.traceEntry(status.asRuntimeException(), entry);
|
||||
}
|
||||
entry.exit();
|
||||
atomicReferenceEntry.set(null);
|
||||
}
|
||||
super.onClose(status, trailers);
|
||||
}
|
||||
}, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some Exceptions will only call cancel.
|
||||
*/
|
||||
@Override
|
||||
public void cancel(@Nullable String message, @Nullable Throwable cause) {
|
||||
Entry entry = atomicReferenceEntry.get();
|
||||
// Some Exceptions will call onClose and cancel.
|
||||
if (entry != null) {
|
||||
// Record the exception metrics.
|
||||
Tracer.traceEntry(cause, entry);
|
||||
entry.exit();
|
||||
atomicReferenceEntry.set(null);
|
||||
}
|
||||
super.cancel(message, cause);
|
||||
}
|
||||
};
|
||||
} catch (BlockException e) {
|
||||
// Flow control threshold exceeded, block the call.
|
||||
return new ClientCall<ReqT, RespT>() {
|
||||
@Override
|
||||
public void start(Listener<RespT> responseListener, Metadata headers) {
|
||||
responseListener.onClose(FLOW_CONTROL_BLOCK, new Metadata());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void request(int numMessages) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(@Nullable String message, @Nullable Throwable cause) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halfClose() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(ReqT message) {
|
||||
}
|
||||
};
|
||||
} catch (RuntimeException e) {
|
||||
// Catch the RuntimeException newCall throws, entry is guaranteed to exit.
|
||||
if (entry != null) {
|
||||
Tracer.traceEntry(e, entry);
|
||||
entry.exit();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 1999-2018 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.grpc;
|
||||
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.SphU;
|
||||
import com.alibaba.csp.sentinel.Tracer;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import io.grpc.ForwardingServerCall;
|
||||
import io.grpc.ForwardingServerCallListener;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.ServerCallHandler;
|
||||
import io.grpc.ServerInterceptor;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* <p>gRPC server interceptor for Sentinel. Currently it only works with unary methods.</p>
|
||||
* <p>
|
||||
* Example code:
|
||||
* <pre>
|
||||
* Server server = ServerBuilder.forPort(port)
|
||||
* .addService(new MyServiceImpl()) // Add your service.
|
||||
* .intercept(new SentinelGrpcServerInterceptor()) // Add the server interceptor.
|
||||
* .build();
|
||||
* </pre>
|
||||
* <p>
|
||||
* For client interceptor, see {@link SentinelGrpcClientInterceptor}.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class SentinelGrpcServerInterceptor implements ServerInterceptor {
|
||||
private static final Status FLOW_CONTROL_BLOCK = Status.UNAVAILABLE.withDescription(
|
||||
"Flow control limit exceeded (server side)");
|
||||
private static final StatusRuntimeException STATUS_RUNTIME_EXCEPTION = new StatusRuntimeException(Status.CANCELLED);
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
|
||||
String fullMethodName = call.getMethodDescriptor().getFullMethodName();
|
||||
// Remote address: serverCall.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
|
||||
Entry entry = null;
|
||||
try {
|
||||
entry = SphU.asyncEntry(fullMethodName, EntryType.IN);
|
||||
final AtomicReference<Entry> atomicReferenceEntry = new AtomicReference<>(entry);
|
||||
// Allow access, forward the call.
|
||||
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(
|
||||
next.startCall(
|
||||
new ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(call) {
|
||||
@Override
|
||||
public void close(Status status, Metadata trailers) {
|
||||
Entry entry = atomicReferenceEntry.get();
|
||||
if (entry != null) {
|
||||
// Record the exception metrics.
|
||||
if (!status.isOk()) {
|
||||
Tracer.traceEntry(status.asRuntimeException(), entry);
|
||||
}
|
||||
//entry exit when the call be closed
|
||||
entry.exit();
|
||||
}
|
||||
super.close(status, trailers);
|
||||
}
|
||||
}, headers)) {
|
||||
/**
|
||||
* If call was canceled, onCancel will be called. and the close will not be called
|
||||
* so the server is encouraged to abort processing to save resources by onCancel
|
||||
* @see ServerCall.Listener#onCancel()
|
||||
*/
|
||||
@Override
|
||||
public void onCancel() {
|
||||
Entry entry = atomicReferenceEntry.get();
|
||||
if (entry != null) {
|
||||
Tracer.traceEntry(STATUS_RUNTIME_EXCEPTION, entry);
|
||||
entry.exit();
|
||||
atomicReferenceEntry.set(null);
|
||||
}
|
||||
super.onCancel();
|
||||
}
|
||||
};
|
||||
} catch (BlockException e) {
|
||||
call.close(FLOW_CONTROL_BLOCK, new Metadata());
|
||||
return new ServerCall.Listener<ReqT>() {
|
||||
};
|
||||
} catch (RuntimeException e) {
|
||||
// Catch the RuntimeException startCall throws, entry is guaranteed to exit.
|
||||
if (entry != null) {
|
||||
Tracer.traceEntry(e, entry);
|
||||
entry.exit();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user