This commit is contained in:
shuhongfan
2023-09-04 16:40:17 +08:00
commit cf5ac25c14
8267 changed files with 1305066 additions and 0 deletions

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

View File

@@ -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.

View File

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

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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() {
}
}

View File

@@ -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>
* &lt;dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/&gt;
* </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();
}
}
}

View File

@@ -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>
* &lt;dubbo:provider filter="-sentinel.dubbo.provider.filter"/&gt;
* </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();
}
}
}

View File

@@ -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() {}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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() {}
}

View File

@@ -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, "");
}
}

View File

@@ -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);
}

View File

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

View File

@@ -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();
}
}

View File

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

View File

@@ -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, "");
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

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

View File

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

View File

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

View File

@@ -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);
}
}
```

View File

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

View File

@@ -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();
}
}
}
};
}
}

View File

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

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

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

View File

@@ -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);
}
}

View File

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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

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

View File

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

View File

@@ -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() {}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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);
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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);
}*/
}

View File

@@ -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);
}*/
}

View File

@@ -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());
}
}

View File

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

View File

@@ -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()));
}
}

View File

@@ -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()));
}
}

View File

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

View File

@@ -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)";
}

View File

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

View File

@@ -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() {}
}

View File

@@ -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);
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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() {}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

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

View File

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

View File

@@ -0,0 +1 @@
com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewayFlowSlot

View File

@@ -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));
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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());
}
}

View File

@@ -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>());
}
}

View 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 interfaceresourceName format is `interfaceName`e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService`
- Service methodresourceName 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.

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

View File

@@ -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);
}
}

View File

@@ -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() {}
}

View File

@@ -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);
}
}

View File

@@ -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() {}
}

View File

@@ -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>
* &lt;dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/&gt;
* </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();
}
}
}
}

View File

@@ -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>
* &lt;dubbo:provider filter="-sentinel.dubbo.provider.filter"/&gt;
* </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();
}
}
}

View File

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

View File

@@ -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);
}

View File

@@ -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() {}
}

View File

@@ -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, "");
}
}

View File

@@ -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);
}

View File

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

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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!");
}
}

View File

@@ -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());
}
}

View File

@@ -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));
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

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

View File

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

View File

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

View 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();
```

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

View File

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

View File

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