Merge branch 'apache-3.2' into apache-3.3

# Conflicts:
#	dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java
#	dubbo-test/dubbo-test-modules/src/test/java/org/apache/dubbo/dependency/FileTest.java
This commit is contained in:
Albumen Kevin 2023-06-12 20:52:32 +08:00
commit fd14e7b76f
80 changed files with 924 additions and 150 deletions

View File

@ -109,5 +109,9 @@ dubbo-spring-boot-tracing-otel-zipkin-starter
dubbo-spring-boot-tracing-otel-otlp-starter
dubbo-spring-boot-observability-starter
dubbo-spring-boot-starter
dubbo-spring-boot-starters
dubbo-nacos-spring-boot-starter
dubbo-zookeeper-spring-boot-starter
dubbo-zookeeper-curator5-spring-boot-starter
dubbo-spring-security
dubbo-xds

View File

@ -128,7 +128,7 @@ public abstract class AbstractConfigurator implements Configurator {
if (apiVersion != null && apiVersion.startsWith(RULE_VERSION_V30)) {
ConditionMatch matcher = (ConditionMatch) configuratorUrl.getAttribute(MATCH_CONDITION);
if (matcher != null) {
if (matcher.isMatch(url)) {
if (matcher.isMatch(host, url)) {
return doConfigure(url, configuratorUrl.removeParameters(conditionKeys));
} else {
logger.debug("Cannot apply configurator rule, param mismatch, current params are " + url + ", params in rule is " + matcher);

View File

@ -26,6 +26,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
public class ConditionMatch {
private AddressMatch address;
private AddressMatch providerAddress;
private ListStringMatch service;
private ListStringMatch app;
private List<ParamMatch> param;
@ -38,6 +39,14 @@ public class ConditionMatch {
this.address = address;
}
public AddressMatch getProviderAddress() {
return providerAddress;
}
public void setProviderAddress(AddressMatch providerAddress) {
this.providerAddress = providerAddress;
}
public ListStringMatch getService() {
return service;
}
@ -62,8 +71,12 @@ public class ConditionMatch {
this.param = param;
}
public boolean isMatch(URL url) {
if (getAddress() != null && !getAddress().isMatch(url.getAddress())) {
public boolean isMatch(String host, URL url) {
if (getAddress() != null && !getAddress().isMatch(host)) {
return false;
}
if (getProviderAddress() != null && !getProviderAddress().isMatch(url.getAddress())) {
return false;
}
@ -90,6 +103,7 @@ public class ConditionMatch {
public String toString() {
return "ConditionMatch{" +
"address='" + address + '\'' +
"providerAddress='" + providerAddress + '\'' +
", service='" + service + '\'' +
", app='" + app + '\'' +
", param='" + param + '\'' +

View File

@ -40,12 +40,12 @@ public class ParamMatch {
}
public boolean isMatch(URL url) {
if (key == null) {
if (key == null || value == null) {
return false;
}
String input = url.getParameter(key);
return input != null && value.isMatch(input);
return value.isMatch(input);
}
@Override

View File

@ -64,8 +64,8 @@ public abstract class ListenableStateRouter<T> extends AbstractStateRouter<T> im
@Override
public synchronized void process(ConfigChangedEvent event) {
if (logger.isDebugEnabled()) {
logger.debug("Notification of condition rule, change type is: " + event.getChangeType() +
if (logger.isInfoEnabled()) {
logger.info("Notification of condition rule, change type is: " + event.getChangeType() +
", raw rule is:\n " + event.getContent());
}

View File

@ -63,8 +63,8 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
@Override
public synchronized void process(ConfigChangedEvent event) {
if (logger.isDebugEnabled()) {
logger.debug("Notification of tag rule, change type is: " + event.getChangeType() + ", raw rule is:\n " +
if (logger.isInfoEnabled()) {
logger.info("Notification of tag rule, change type is: " + event.getChangeType() + ", raw rule is:\n " +
event.getContent());
}

View File

@ -39,7 +39,7 @@ public class ParamMatch {
}
public boolean isMatch(String input) {
if (getValue() != null && input != null) {
if (getValue() != null) {
return getValue().isMatch(input);
}
return false;

View File

@ -40,14 +40,14 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.apache.dubbo.common.constants.CommonConstants.BROADCAST_CLUSTER;
import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY;
import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
import static org.apache.dubbo.rpc.Constants.SCOPE_KEY;
import static org.apache.dubbo.rpc.Constants.SCOPE_REMOTE;
import static org.apache.dubbo.rpc.Constants.SCOPE_LOCAL;
import static org.apache.dubbo.rpc.Constants.SCOPE_REMOTE;
import static org.apache.dubbo.rpc.cluster.Constants.PEER_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.BROADCAST_CLUSTER;
/**
* ScopeClusterInvoker is a cluster invoker which handles the invocation logic of a single service in a specific scope.
@ -100,7 +100,19 @@ public class ScopeClusterInvoker<T> implements ClusterInvoker<T>, ExporterChange
@Override
public boolean isAvailable() {
return isExported.get() || directory.isAvailable();
if (peerFlag || isBroadcast()) {
// If it's a point-to-point direct connection or broadcasting, it should be called remotely.
return invoker.isAvailable();
}
if (injvmFlag && isForceLocal()) {
// If it's a local call, it should be called locally.
return isExported.get();
}
if (injvmFlag && isExported.get()) {
// If allow local call, check if local exported first
return true;
}
return invoker.isAvailable();
}
@Override
@ -128,7 +140,7 @@ public class ScopeClusterInvoker<T> implements ClusterInvoker<T>, ExporterChange
@Override
public Result invoke(Invocation invocation) throws RpcException {
// When broadcasting, it should be called remotely.
if (BROADCAST_CLUSTER.equalsIgnoreCase(getUrl().getParameter(CLUSTER_KEY))) {
if (isBroadcast()) {
if (logger.isDebugEnabled()) {
logger.debug("Performing broadcast call for method: " + invocation.getMethodName() + " of service: " + getUrl().getServiceKey());
}
@ -155,6 +167,10 @@ public class ScopeClusterInvoker<T> implements ClusterInvoker<T>, ExporterChange
return invoker.invoke(invocation);
}
private boolean isBroadcast() {
return BROADCAST_CLUSTER.equalsIgnoreCase(getUrl().getParameter(CLUSTER_KEY));
}
@Override
public void onExporterChangeExport(Exporter<?> exporter) {
if (isExported.get()) {
@ -230,9 +246,9 @@ public class ScopeClusterInvoker<T> implements ClusterInvoker<T>, ExporterChange
private boolean isInjvmExported() {
Boolean localInvoke = RpcContext.getServiceContext().getLocalInvoke();
boolean isExportedValue = isExported.get();
boolean local = (localInvoke != null && localInvoke);
boolean localOnce = (localInvoke != null && localInvoke);
// Determine whether this call is local
if (isExportedValue && local) {
if (isExportedValue && localOnce) {
return true;
}
@ -242,8 +258,7 @@ public class ScopeClusterInvoker<T> implements ClusterInvoker<T>, ExporterChange
}
// When calling locally, determine whether it does not meet the requirements
if (!isExportedValue && (SCOPE_LOCAL.equalsIgnoreCase(getUrl().getParameter(SCOPE_KEY)) ||
Boolean.TRUE.toString().equalsIgnoreCase(getUrl().getParameter(LOCAL_PROTOCOL)) || local)) {
if (!isExportedValue && (isForceLocal() || localOnce)) {
// If it's supposed to be exported to the local JVM ,but it's not, throw an exception
throw new RpcException("Local service for " + getUrl().getServiceInterface() + " has not been exposed yet!");
}
@ -251,6 +266,11 @@ public class ScopeClusterInvoker<T> implements ClusterInvoker<T>, ExporterChange
return isExportedValue && injvmFlag;
}
private boolean isForceLocal() {
return SCOPE_LOCAL.equalsIgnoreCase(getUrl().getParameter(SCOPE_KEY)) ||
Boolean.TRUE.toString().equalsIgnoreCase(getUrl().getParameter(LOCAL_PROTOCOL));
}
/**
* Creates a new Invoker for the current ScopeClusterInvoker and exports it to the local JVM.
*/

View File

@ -187,11 +187,11 @@ class ConfigParserTest {
URL notMatchURL3 = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value_not_match");// key not match
ConditionMatch matcher = (ConditionMatch) url.getAttribute(MATCH_CONDITION);
Assertions.assertTrue(matcher.isMatch(matchURL1));
Assertions.assertTrue(matcher.isMatch(matchURL2));
Assertions.assertFalse(matcher.isMatch(notMatchURL1));
Assertions.assertFalse(matcher.isMatch(notMatchURL2));
Assertions.assertFalse(matcher.isMatch(notMatchURL3));
Assertions.assertTrue(matcher.isMatch(matchURL1.getAddress(), matchURL1));
Assertions.assertTrue(matcher.isMatch(matchURL2.getAddress(), matchURL2));
Assertions.assertFalse(matcher.isMatch(notMatchURL1.getAddress(), notMatchURL1));
Assertions.assertFalse(matcher.isMatch(notMatchURL2.getAddress(), notMatchURL2));
Assertions.assertFalse(matcher.isMatch(notMatchURL3.getAddress(), notMatchURL3));
}
}
@ -211,8 +211,8 @@ class ConfigParserTest {
URL notMatchURL = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value_not_match");// key not match
ConditionMatch matcher = (ConditionMatch) url.getAttribute(MATCH_CONDITION);
Assertions.assertTrue(matcher.isMatch(matchURL));
Assertions.assertFalse(matcher.isMatch(notMatchURL));
Assertions.assertTrue(matcher.isMatch(matchURL.getAddress(), matchURL));
Assertions.assertFalse(matcher.isMatch(notMatchURL.getAddress(), notMatchURL));
}
}
@ -232,8 +232,8 @@ class ConfigParserTest {
URL notMatchURL = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value_not_match");// key not match
ConditionMatch matcher = (ConditionMatch) url.getAttribute(MATCH_CONDITION);
Assertions.assertTrue(matcher.isMatch(matchURL));
Assertions.assertFalse(matcher.isMatch(notMatchURL));
Assertions.assertTrue(matcher.isMatch(matchURL.getAddress(), matchURL));
Assertions.assertFalse(matcher.isMatch(notMatchURL.getAddress(), notMatchURL));
}
}

View File

@ -19,7 +19,6 @@ package org.apache.dubbo.rpc.cluster.support.wrapper;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.metrics.event.MetricsDispatcher;
import org.apache.dubbo.rpc.Exporter;
import org.apache.dubbo.rpc.Invocation;
@ -165,7 +164,7 @@ class ScopeClusterInvokerTest {
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("doSomething4");
invocation.setParameterTypes(new Class[]{});
Assertions.assertTrue(cluster.isAvailable(), "");
Assertions.assertFalse(cluster.isAvailable(), "");
RpcInvocation finalInvocation = invocation;
Assertions.assertThrows(RpcException.class, () -> cluster.invoke(finalInvocation));

View File

@ -52,14 +52,18 @@ public class ThreadlessExecutor extends AbstractExecutorService {
* Waits until there is a task, executes the task and all queued tasks (if there're any). The task is either a normal
* response or a timeout response.
*/
public void waitAndDrain() throws InterruptedException {
public void waitAndDrain(long deadline) throws InterruptedException {
throwIfInterrupted();
Runnable runnable = queue.poll();
if (runnable == null) {
waiter = Thread.currentThread();
try {
while ((runnable = queue.poll()) == null) {
LockSupport.park(this);
long restTime = deadline - System.nanoTime();
if (restTime <= 0) {
return;
}
LockSupport.parkNanos(this, restTime);
throwIfInterrupted();
}
} finally {

View File

@ -92,6 +92,11 @@ public class ModuleConfig extends AbstractConfig {
*/
private Integer exportThreadNum;
/**
* The timeout to check references
*/
private Long checkReferenceTimeout;
public ModuleConfig() {
super();
}
@ -251,4 +256,12 @@ public class ModuleConfig extends AbstractConfig {
public void setExportAsync(Boolean exportAsync) {
this.exportAsync = exportAsync;
}
public Long getCheckReferenceTimeout() {
return checkReferenceTimeout;
}
public void setCheckReferenceTimeout(Long checkReferenceTimeout) {
this.checkReferenceTimeout = checkReferenceTimeout;
}
}

View File

@ -16,10 +16,11 @@
*/
package org.apache.dubbo.common.threadpool;
import org.apache.dubbo.common.URL;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.concurrent.atomic.AtomicBoolean;
class ThreadlessExecutorTest {
private static final ThreadlessExecutor executor;
@ -33,11 +34,13 @@ class ThreadlessExecutorTest {
executor.execute(()->{throw new RuntimeException("test");});
}
executor.waitAndDrain();
executor.waitAndDrain(123);
executor.execute(()->{});
AtomicBoolean invoked = new AtomicBoolean(false);
executor.execute(()->{invoked.set(true);});
executor.waitAndDrain();
executor.waitAndDrain(123);
Assertions.assertTrue(invoked.get());
executor.shutdown();
}

View File

@ -250,7 +250,7 @@ public class PageServlet extends HttpServlet {
+ n
+ "_' + i + '_' + j).innerHTML; if (iv.length > 0 && (tv.length < iv.length || tv.indexOf(iv) == -1)) { m = false; break; } } } document.getElementById('tr_"
+ n
+ "_' + i).style.display = (m ? '' : 'none');}\" sytle=\"width: 100%\" />";
+ "_' + i).style.display = (m ? '' : 'none');}\" style=\"width: 100%\" />";
}
writer.println(" <td>" + col + "</td>");
}

View File

@ -95,5 +95,21 @@ public interface Invoker<T> extends org.apache.dubbo.rpc.Invoker<T> {
public org.apache.dubbo.rpc.Invoker<T> getOriginal() {
return invoker;
}
@Override
public int hashCode() {
return invoker.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CompatibleInvoker)) {
return false;
}
return invoker.equals(o);
}
}
}

View File

@ -743,6 +743,10 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
checkStubAndLocal(interfaceClass);
ConfigValidationUtils.checkMock(interfaceClass, this);
if (StringUtils.isEmpty(url)) {
checkRegistry();
}
resolveFile();
ConfigValidationUtils.validateReferenceConfig(this);
postProcessConfig();

View File

@ -837,12 +837,18 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
if (StringUtils.isNotEmpty(configCenter.getConfigFile())) {
String configContent = dynamicConfiguration.getProperties(configCenter.getConfigFile(), configCenter.getGroup());
if (StringUtils.isNotEmpty(configContent)) {
logger.info(String.format("Got global remote configuration from config center with key-%s and group-%s: \n %s", configCenter.getConfigFile(), configCenter.getGroup(), configContent));
}
String appGroup = getApplication().getName();
String appConfigContent = null;
String appConfigFile = null;
if (isNotEmpty(appGroup)) {
appConfigFile = isNotEmpty(configCenter.getAppConfigFile()) ? configCenter.getAppConfigFile() : configCenter.getConfigFile();
appConfigContent = dynamicConfiguration.getProperties(appConfigFile, appGroup);
if (StringUtils.isNotEmpty(appConfigContent)) {
logger.info(String.format("Got application specific remote configuration from config center with key %s and group %s: \n %s", appConfigFile, appGroup, appConfigContent));
}
}
try {
Map<String, String> configMap = parseProperties(configContent);

View File

@ -46,6 +46,7 @@ import org.apache.dubbo.rpc.model.ProviderModel;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@ -395,8 +396,10 @@ public class DefaultModuleDeployer extends AbstractDeployer<ModuleModel> impleme
}
private void checkReferences() {
Optional<ModuleConfig> module = configManager.getModule();
long timeout = module.map(ModuleConfig::getCheckReferenceTimeout).orElse(30000L);
for (ReferenceConfigBase<?> rc : configManager.getReferences()) {
referenceCache.check(rc, 3000);
referenceCache.check(rc, timeout);
}
}

View File

@ -20,7 +20,7 @@ import java.util.HashSet;
import java.util.Set;
/**
* Hold a set of DubboSpringInitCustomizer, for register customizers by programing.
* Hold a set of DubboSpringInitCustomizer, for register customizers by programming.
* <p>All customizers are store in thread local, and they will be clear after apply once.</p>
*
* <p>Usages:</p>

View File

@ -624,6 +624,11 @@
<xsd:documentation><![CDATA[ Thread num for asynchronous export pool size. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="check-reference-timeout" type="xsd:long">
<xsd:annotation>
<xsd:documentation><![CDATA[ The timeout to check references. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="registryType">

View File

@ -17,11 +17,6 @@
package org.apache.dubbo.configcenter.support.nacos;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
import com.alibaba.nacos.api.exception.NacosException;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
@ -38,6 +33,12 @@ import org.apache.dubbo.metrics.config.event.ConfigCenterEvent;
import org.apache.dubbo.metrics.event.MetricsEventBus;
import org.apache.dubbo.rpc.model.ApplicationModel;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
import com.alibaba.nacos.api.exception.NacosException;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@ -107,11 +108,15 @@ public class NacosDynamicConfiguration implements DynamicConfiguration {
try {
for (int i = 0; i < retryTimes + 1; i++) {
tmpConfigServices = NacosFactory.createConfigService(nacosProperties);
if (!check || (UP.equals(tmpConfigServices.getServerStatus()) && testConfigService(tmpConfigServices))) {
String serverStatus = tmpConfigServices.getServerStatus();
boolean configServiceAvailable = testConfigService(tmpConfigServices);
if (!check || (UP.equals(serverStatus) && configServiceAvailable)) {
break;
} else {
logger.warn(LoggerCodeConstants.CONFIG_ERROR_NACOS, "", "",
"Failed to connect to nacos config server. " +
"Server status: " + serverStatus + ". " +
"Config Service Available: " + configServiceAvailable + ". " +
(i < retryTimes ? "Dubbo will try to retry in " + sleepMsBetweenRetries + ". " : "Exceed retry max times.") +
"Try times: " + (i + 1));
}

View File

@ -36,7 +36,7 @@ import java.util.Map;
*
* @Consumers & @Produces can be not used ,we will make sure the content-type of request by arg type
* but the Request method is forbidden disappear
* parameters which annotation are not present , it is from the body (jaxrs anntation is diffrent from spring web from param(only request param can ignore anntation))
* parameters which annotation are not present , it is from the body (jaxrs annotation is different from spring web from param(only request param can ignore annotation))
*
* Every method only one param from body
*

View File

@ -167,7 +167,7 @@
<eureka.version>1.10.18</eureka.version>
<!-- Fabric8 for Kubernetes -->
<fabric8_kubernetes_version>6.6.2</fabric8_kubernetes_version>
<fabric8_kubernetes_version>6.7.1</fabric8_kubernetes_version>
<!-- Alibaba -->
<alibaba_spring_context_support_version>1.0.11</alibaba_spring_context_support_version>

View File

@ -501,6 +501,11 @@
<type>pom</type>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starters</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-observability-starters</artifactId>
@ -531,6 +536,21 @@
<artifactId>dubbo-spring-boot-observability-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-zookeeper-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-zookeeper-curator5-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<!-- test -->
<dependency>

View File

@ -124,18 +124,20 @@ public abstract class AbstractServiceNameMapping implements ServiceNameMapping {
@Override
public MappingListener stopListen(URL subscribeURL, MappingListener listener) {
synchronized (mappingListeners) {
String mappingKey = ServiceNameMapping.buildMappingKey(subscribeURL);
Set<MappingListener> listeners = mappingListeners.get(mappingKey);
//todo, remove listener from remote metadata center
if (CollectionUtils.isNotEmpty(listeners)) {
listeners.remove(listener);
listener.stop();
removeListener(subscribeURL, listener);
}
if (CollectionUtils.isEmpty(listeners)) {
mappingListeners.remove(mappingKey);
removeCachedMapping(mappingKey);
removeMappingLock(mappingKey);
if (listener != null) {
String mappingKey = ServiceNameMapping.buildMappingKey(subscribeURL);
Set<MappingListener> listeners = mappingListeners.get(mappingKey);
//todo, remove listener from remote metadata center
if (CollectionUtils.isNotEmpty(listeners)) {
listeners.remove(listener);
listener.stop();
removeListener(subscribeURL, listener);
}
if (CollectionUtils.isEmpty(listeners)) {
mappingListeners.remove(mappingKey);
removeCachedMapping(mappingKey);
removeMappingLock(mappingKey);
}
}
return listener;
}

View File

@ -17,6 +17,8 @@
package org.apache.dubbo.metadata.rest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
/**
@ -32,8 +34,13 @@ public class PathMatcher {
private boolean hasPathVariable;
private String contextPath;
private String httpMethod;
// for provider http method compare
private boolean needCompareMethod = true;
// for provider http method compare,http 405
private boolean needCompareHttpMethod = true;
// compare method directly (for get Invoker by method)
private boolean needCompareServiceMethod = false;
// service method
private Method method;
public PathMatcher(String path) {
@ -53,7 +60,14 @@ public class PathMatcher {
setHttpMethod(httpMethod);
}
public PathMatcher(Method method) {
this.method = method;
}
private void dealPathVariable(String path) {
if (path == null) {
return;
}
this.pathSplits = path.split(SEPARATOR);
for (String pathSplit : pathSplits) {
@ -99,6 +113,10 @@ public class PathMatcher {
return new PathMatcher(path, version, group, port, method).noNeedHttpMethodCompare();
}
public static PathMatcher getInvokeCreatePathMatcher(Method serviceMethod) {
return new PathMatcher(serviceMethod).setNeedCompareServiceMethod(true);
}
public boolean hasPathVariable() {
return hasPathVariable;
}
@ -117,7 +135,20 @@ public class PathMatcher {
}
private PathMatcher noNeedHttpMethodCompare() {
this.needCompareMethod = false;
this.needCompareHttpMethod = false;
return this;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
private PathMatcher setNeedCompareServiceMethod(boolean needCompareServiceMethod) {
this.needCompareServiceMethod = needCompareServiceMethod;
return this;
}
@ -126,18 +157,48 @@ public class PathMatcher {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PathMatcher that = (PathMatcher) o;
return pathEqual(that)
&& Objects.equals(version, that.version)
&& (this.needCompareMethod ? Objects.equals(httpMethod, that.httpMethod) : true)
return serviceMethodEqual(that, this)
|| pathMatch(that);
}
private boolean pathMatch(PathMatcher that) {
return (!that.needCompareServiceMethod && !needCompareServiceMethod) // no need service method compare
&& pathEqual(that) // path compare
&& Objects.equals(version, that.version) // service version compare
&& httpMethodMatch(that) // http method compare
&& Objects.equals(group, that.group) && Objects.equals(port, that.port);
}
/**
* it is needed to compare http method when one of needCompareHttpMethod is true,and don`t compare when both needCompareHttpMethod are false
*
* @param that
* @return
*/
private boolean httpMethodMatch(PathMatcher that) {
return !that.needCompareHttpMethod || !this.needCompareHttpMethod ? true: Objects.equals(this.httpMethod, that.httpMethod);
}
private boolean serviceMethodEqual(PathMatcher thatPathMatcher, PathMatcher thisPathMatcher) {
Method thatMethod = thatPathMatcher.method;
Method thisMethod = thisPathMatcher.method;
return thatMethod != null
&& thisMethod != null
&& (thatPathMatcher.needCompareServiceMethod || thisPathMatcher.needCompareServiceMethod)
&& thisMethod.getName().equals(thatMethod.getName())
&& Arrays.equals(thisMethod.getParameterTypes(), thatMethod.getParameterTypes());
}
@Override
public int hashCode() {
return Objects.hash(version, group, port);
}
private boolean pathEqual(PathMatcher pathMatcher) {
// path is null return false directly
if (this.path == null || pathMatcher.path == null) {
return false;
}
// no place hold

View File

@ -111,6 +111,7 @@ public class ServiceRestMetadata implements Serializable {
public void addRestMethodMetadata(RestMethodMetadata restMethodMetadata) {
PathMatcher pathMather = new PathMatcher(restMethodMetadata.getRequest().getPath(),
this.getVersion(), this.getGroup(), this.getPort(),restMethodMetadata.getRequest().getMethod());
pathMather.setMethod(restMethodMetadata.getReflectMethod());
addPathToServiceMap(pathMather, restMethodMetadata);
addMethodToServiceMap(restMethodMetadata);
getMeta().add(restMethodMetadata);

View File

@ -20,6 +20,8 @@ import org.apache.dubbo.metadata.rest.PathMatcher;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
public class PathMatcherTest {
@Test
@ -65,4 +67,37 @@ public class PathMatcherTest {
Assertions.assertEquals(pathMatherMeta, pathMatherMeta1);
Assertions.assertEquals(pathMatherMeta.toString(), pathMatherMeta1.toString());
}
@Test
void testMethodCompare() {
Method hashCode = null;
Method equals = null;
try {
hashCode = Object.class.getDeclaredMethod("hashCode");
equals = Object.class.getDeclaredMethod("equals", Object.class);
} catch (NoSuchMethodException e) {
}
// no need to compare service method
PathMatcher pathMatcher = new PathMatcher(hashCode);
PathMatcher pathMatchers = new PathMatcher(hashCode);
Assertions.assertNotEquals(pathMatcher, pathMatchers);
// equal
PathMatcher pathMatherMetaHashCode = PathMatcher.getInvokeCreatePathMatcher(hashCode);
PathMatcher pathMatherMetaHashCodes = new PathMatcher(hashCode);
Assertions.assertEquals(pathMatherMetaHashCode, pathMatherMetaHashCodes);
PathMatcher pathMatherMetaEquals = PathMatcher.getInvokeCreatePathMatcher(equals);
PathMatcher pathMatherMetaEqual = PathMatcher.getInvokeCreatePathMatcher(equals);
Assertions.assertEquals(pathMatherMetaEqual, pathMatherMetaEquals);
Assertions.assertNotEquals(pathMatherMetaHashCode, pathMatherMetaEquals);
}
}

View File

@ -248,7 +248,9 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
serviceDiscovery.unsubscribe(url, listener);
String protocolServiceKey = url.getProtocolServiceKey();
Set<String> serviceNames = serviceNameMapping.getMapping(url);
serviceNameMapping.stopListen(url, mappingListeners.remove(protocolServiceKey));
if (mappingListeners.get(protocolServiceKey) != null) {
serviceNameMapping.stopListen(url, mappingListeners.remove(protocolServiceKey));
}
if (CollectionUtils.isNotEmpty(serviceNames)) {
String serviceNamesKey = toStringKeys(serviceNames);
Lock appSubscriptionLock = getAppSubscription(serviceNamesKey);

View File

@ -119,6 +119,18 @@ public class RegistryDirectory<T> extends DynamicDirectory<T> {
@Override
public void subscribe(URL url) {
// Fail-fast detection protocol spi
String queryProtocols = this.queryMap.get(PROTOCOL_KEY);
if (StringUtils.isNotBlank(queryProtocols)) {
String[] acceptProtocols = queryProtocols.split(",");
for (String acceptProtocol : acceptProtocols) {
if (!moduleModel.getApplicationModel().getExtensionLoader(Protocol.class).hasExtension(acceptProtocol)) {
throw new IllegalStateException("No such extension org.apache.dubbo.rpc.Protocol by name " + acceptProtocol + ", please check whether related SPI module is missing");
}
}
}
ApplicationModel applicationModel = url.getApplicationModel();
MetricsEventBus.post(RegistryEvent.toSubscribeEvent(applicationModel),() ->
{

View File

@ -121,11 +121,15 @@ public class NacosConnectionManager {
try {
for (int i = 0; i < retryTimes + 1; i++) {
namingService = NacosFactory.createNamingService(nacosProperties);
if (!check || (UP.equals(namingService.getServerStatus()) && testNamingService(namingService))) {
String serverStatus = namingService.getServerStatus();
boolean namingServiceAvailable = testNamingService(namingService);
if (!check || (UP.equals(serverStatus) && namingServiceAvailable)) {
break;
} else {
logger.warn(LoggerCodeConstants.REGISTRY_NACOS_EXCEPTION, "", "",
"Failed to connect to nacos naming server. " +
"Server status: " + serverStatus + ". " +
"Naming Service Available: " + namingServiceAvailable + ". " +
(i < retryTimes ? "Dubbo will try to retry in " + sleepMsBetweenRetries + ". " : "Exceed retry max times.") +
"Try times: " + (i + 1));
}

View File

@ -137,6 +137,8 @@ public interface Constants {
int DEFAULT_RECONNECT_PERIOD = 2000;
String CHANNEL_SHUTDOWN_TIMEOUT_KEY = "channel.shutdown.timeout";
String SEND_RECONNECT_KEY = "send.reconnect";
String CHECK_KEY = "check";

View File

@ -19,6 +19,7 @@ package org.apache.dubbo.remoting.exchange.support;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.resource.GlobalResourceInitializer;
import org.apache.dubbo.common.serialize.SerializationException;
import org.apache.dubbo.common.timer.HashedWheelTimer;
import org.apache.dubbo.common.timer.Timeout;
import org.apache.dubbo.common.timer.Timer;
@ -26,7 +27,6 @@ import org.apache.dubbo.common.timer.TimerTask;
import org.apache.dubbo.common.utils.NamedThreadFactory;
import org.apache.dubbo.remoting.Channel;
import org.apache.dubbo.remoting.RemotingException;
import org.apache.dubbo.common.serialize.SerializationException;
import org.apache.dubbo.remoting.TimeoutException;
import org.apache.dubbo.remoting.exchange.Request;
import org.apache.dubbo.remoting.exchange.Response;
@ -150,22 +150,39 @@ public class DefaultFuture extends CompletableFuture<Object> {
*
* @param channel channel to close
*/
public static void closeChannel(Channel channel) {
public static void closeChannel(Channel channel, long timeout) {
long deadline = timeout > 0 ? System.currentTimeMillis() + timeout : 0;
for (Map.Entry<Long, Channel> entry : CHANNELS.entrySet()) {
if (channel.equals(entry.getValue())) {
DefaultFuture future = getFuture(entry.getKey());
if (future != null && !future.isDone()) {
Response disconnectResponse = new Response(future.getId());
disconnectResponse.setStatus(Response.CHANNEL_INACTIVE);
disconnectResponse.setErrorMessage("Channel " +
channel + " is inactive. Directly return the unFinished request : " +
(logger.isDebugEnabled() ? future.getRequest() : future.getRequest().copyWithoutData()));
DefaultFuture.received(channel, disconnectResponse);
long restTime = deadline - System.currentTimeMillis();
if (restTime > 0) {
try {
future.get(restTime, TimeUnit.MILLISECONDS);
} catch (java.util.concurrent.TimeoutException ignore) {
logger.warn(PROTOCOL_TIMEOUT_SERVER, "", "",
"Trying to close channel " + channel + ", but response is not received in "
+ timeout + "ms, and the request id is " + future.id);
} catch (Throwable ignore) {}
}
if (!future.isDone()) {
respInactive(channel, future);
}
}
}
}
}
private static void respInactive(Channel channel, DefaultFuture future) {
Response disconnectResponse = new Response(future.getId());
disconnectResponse.setStatus(Response.CHANNEL_INACTIVE);
disconnectResponse.setErrorMessage("Channel " +
channel + " is inactive. Directly return the unFinished request : " +
(logger.isDebugEnabled() ? future.getRequest() : future.getRequest().copyWithoutData()));
DefaultFuture.received(channel, disconnectResponse);
}
public static void received(Channel channel, Response response) {
received(channel, response, false);
}

View File

@ -18,6 +18,7 @@ package org.apache.dubbo.remoting.exchange.support.header;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.Version;
import org.apache.dubbo.common.config.ConfigurationUtils;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.remoting.Channel;
@ -30,6 +31,7 @@ import org.apache.dubbo.remoting.exchange.Response;
import org.apache.dubbo.remoting.exchange.support.DefaultFuture;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@ -48,6 +50,8 @@ final class HeaderExchangeChannel implements ExchangeChannel {
private final Channel channel;
private final long shutdownTimeout;
private volatile boolean closed = false;
HeaderExchangeChannel(Channel channel) {
@ -55,6 +59,10 @@ final class HeaderExchangeChannel implements ExchangeChannel {
throw new IllegalArgumentException("channel == null");
}
this.channel = channel;
this.shutdownTimeout = Optional.ofNullable(channel.getUrl())
.map(URL::getOrDefaultApplicationModel)
.map(ConfigurationUtils::getServerShutdownTimeout)
.orElse(DEFAULT_TIMEOUT);
}
static HeaderExchangeChannel getOrAddChannel(Channel ch) {
@ -159,7 +167,7 @@ final class HeaderExchangeChannel implements ExchangeChannel {
closed = true;
try {
// graceful close
DefaultFuture.closeChannel(channel);
DefaultFuture.closeChannel(channel, shutdownTimeout);
} catch (Exception e) {
logger.warn(TRANSPORT_FAILED_CLOSE, "", "", e.getMessage(), e);
}

View File

@ -17,6 +17,7 @@
package org.apache.dubbo.remoting.exchange.support.header;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.ConfigurationUtils;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.NetUtils;
@ -132,6 +133,8 @@ public class HeaderExchangeHandler implements ChannelHandlerDelegate {
public void connected(Channel channel) throws RemotingException {
ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
handler.connected(exchangeChannel);
channel.setAttribute(Constants.CHANNEL_SHUTDOWN_TIMEOUT_KEY,
ConfigurationUtils.getServerShutdownTimeout(channel.getUrl().getOrDefaultApplicationModel()));
}
@Override
@ -140,7 +143,12 @@ public class HeaderExchangeHandler implements ChannelHandlerDelegate {
try {
handler.disconnected(exchangeChannel);
} finally {
DefaultFuture.closeChannel(channel);
int shutdownTimeout = 0;
Object timeoutObj = channel.getAttribute(Constants.CHANNEL_SHUTDOWN_TIMEOUT_KEY);
if (timeoutObj instanceof Integer) {
shutdownTimeout = (Integer) timeoutObj;
}
DefaultFuture.closeChannel(channel, shutdownTimeout);
HeaderExchangeChannel.removeChannel(channel);
}
}

View File

@ -142,7 +142,7 @@ class DefaultFutureTest {
try {
new InterruptThread(Thread.currentThread()).start();
while (!f. isDone()){
executor.waitAndDrain();
executor.waitAndDrain(Long.MAX_VALUE);
}
f.get();
} catch (Exception e) {
@ -167,7 +167,7 @@ class DefaultFutureTest {
ExecutorService executor = ExtensionLoader.getExtensionLoader(ExecutorRepository.class)
.getDefaultExtension().createExecutorIfAbsent(URL.valueOf("dubbo://127.0.0.1:23456"));
DefaultFuture.newFuture(channel, request, 1000, executor);
DefaultFuture.closeChannel(channel);
DefaultFuture.closeChannel(channel, 0);
Assertions.assertFalse(executor.isTerminated());
}

View File

@ -184,7 +184,7 @@ public class AsyncRpcResult implements Result {
ThreadlessExecutor threadlessExecutor = (ThreadlessExecutor) executor;
try {
while (!responseFuture.isDone()) {
threadlessExecutor.waitAndDrain();
threadlessExecutor.waitAndDrain(Long.MAX_VALUE);
}
} finally {
threadlessExecutor.shutdown();
@ -195,17 +195,27 @@ public class AsyncRpcResult implements Result {
@Override
public Result get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
long deadline = System.nanoTime() + unit.toNanos(timeout);
if (executor != null && executor instanceof ThreadlessExecutor) {
ThreadlessExecutor threadlessExecutor = (ThreadlessExecutor) executor;
try {
while (!responseFuture.isDone()) {
threadlessExecutor.waitAndDrain();
long restTime = deadline - System.nanoTime();
if (restTime > 0) {
threadlessExecutor.waitAndDrain(deadline);
} else {
throw new TimeoutException("Timeout after " + unit.toMillis(timeout) + "ms waiting for result.");
}
}
} finally {
threadlessExecutor.shutdown();
}
}
return responseFuture.get(timeout, unit);
long restTime = deadline - System.nanoTime();
if (!responseFuture.isDone() && restTime < 0) {
throw new TimeoutException("Timeout after " + unit.toMillis(timeout) + "ms waiting for result.");
}
return responseFuture.get(restTime, TimeUnit.NANOSECONDS);
}
@Override

View File

@ -42,7 +42,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
@ -63,7 +62,7 @@ public abstract class AbstractProtocol implements Protocol, ScopeModelAware {
/**
* <host:port, ProtocolServer>
*/
protected final ConcurrentMap<String, ProtocolServer> serverMap = new ConcurrentHashMap<>();
protected final Map<String, ProtocolServer> serverMap = new ConcurrentHashMap<>();
// TODO SoftReference
protected final Set<Invoker<?>> invokers = new ConcurrentHashSet<>();

View File

@ -19,9 +19,11 @@ package org.apache.dubbo.rpc.protocol;
import org.apache.dubbo.common.Parameters;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.remoting.Channel;
import org.apache.dubbo.remoting.ChannelHandler;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.remoting.RemotingException;
import org.apache.dubbo.remoting.RemotingServer;
import org.apache.dubbo.rpc.Exporter;
@ -30,7 +32,6 @@ import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.ProtocolServer;
import org.apache.dubbo.rpc.ProxyFactory;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import java.net.InetSocketAddress;
@ -41,6 +42,8 @@ import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_UNSUPPORTED;
/**
@ -48,7 +51,7 @@ import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_UNS
*/
public abstract class AbstractProxyProtocol extends AbstractProtocol {
protected final List<Class<?>> rpcExceptions = new CopyOnWriteArrayList<Class<?>>();
private final List<Class<?>> rpcExceptions = new CopyOnWriteArrayList<Class<?>>();
protected ProxyFactory proxyFactory;
@ -84,35 +87,7 @@ public abstract class AbstractProxyProtocol extends AbstractProtocol {
return exporter;
}
}
final Runnable runnable = doExport(proxyFactory.getProxy(
new Invoker<T>() {
@Override
public Class<T> getInterface() {
return invoker.getInterface();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
RpcContext.getServiceContext().getObjectAttachments().forEach(invocation::setObjectAttachment);
return invoker.invoke(invocation);
}
@Override
public URL getUrl() {
return invoker.getUrl();
}
@Override
public boolean isAvailable() {
return invoker.isAvailable();
}
@Override
public void destroy() {
invoker.destroy();
}
}, true), invoker.getInterface(),
invoker.getUrl());
final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl());
exporter = new AbstractExporter<T>(invoker) {
@Override
public void afterUnExport() {
@ -130,6 +105,46 @@ public abstract class AbstractProxyProtocol extends AbstractProtocol {
return exporter;
}
@Override
protected <T> Invoker<T> protocolBindingRefer(final Class<T> type, final URL url) throws RpcException {
final Invoker<T> target = proxyFactory.getInvoker(doRefer(type, url), type, url);
Invoker<T> invoker = new AbstractInvoker<T>(type, url) {
@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
try {
Result result = target.invoke(invocation);
// FIXME result is an AsyncRpcResult instance.
Throwable e = result.getException();
if (e != null) {
for (Class<?> rpcException : rpcExceptions) {
if (rpcException.isAssignableFrom(e.getClass())) {
throw getRpcException(type, url, invocation, e);
}
}
}
return result;
} catch (RpcException e) {
if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
e.setCode(getErrorCode(e.getCause()));
}
throw e;
} catch (Throwable e) {
throw getRpcException(type, url, invocation, e);
}
}
@Override
public void destroy() {
super.destroy();
target.destroy();
invokers.remove(this);
AbstractProxyProtocol.this.destroyInternal(url);
}
};
invokers.add(invoker);
return invoker;
}
// used to destroy unused clients and other resource
protected void destroyInternal(URL url) {
// subclass override
@ -142,12 +157,22 @@ public abstract class AbstractProxyProtocol extends AbstractProtocol {
return re;
}
protected String getAddr(URL url) {
String bindIp = url.getParameter(Constants.BIND_IP_KEY, url.getHost());
if (url.getParameter(ANYHOST_KEY, false)) {
bindIp = ANYHOST_VALUE;
}
return NetUtils.getIpByHost(bindIp) + ":" + url.getParameter(Constants.BIND_PORT_KEY, url.getPort());
}
protected int getErrorCode(Throwable e) {
return RpcException.UNKNOWN_EXCEPTION;
}
protected abstract <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException;
protected abstract <T> T doRefer(Class<T> type, URL url) throws RpcException;
protected class ProxyProtocolServer implements ProtocolServer {
private RemotingServer server;

View File

@ -22,7 +22,6 @@ import org.apache.dubbo.metadata.rest.PathMatcher;
import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.protocol.rest.exception.DoublePathCheckException;
import org.apache.dubbo.rpc.protocol.rest.exception.PathNoFoundException;
import org.apache.dubbo.rpc.protocol.rest.pair.InvokerAndRestMethodMetadataPair;
import java.util.Map;
@ -56,31 +55,26 @@ public class PathAndInvokerMapper {
});
}
/**
* acquire metadata & invoker by service info
*
* @param path
* @param version
* @param group
* @param port
* get rest method metadata by path matcher
* @param pathMatcher
* @return
*/
public InvokerAndRestMethodMetadataPair getRestMethodMetadata(String path, String version, String group, Integer port,String method) {
PathMatcher pathMather = PathMatcher.getInvokeCreatePathMatcher(path, version, group, port,method);
public InvokerAndRestMethodMetadataPair getRestMethodMetadata(PathMatcher pathMatcher) {
// first search from pathToServiceMapNoPathVariable
if (pathToServiceMapNoPathVariable.containsKey(pathMather)) {
return pathToServiceMapNoPathVariable.get(pathMather);
if (pathToServiceMapNoPathVariable.containsKey(pathMatcher)) {
return pathToServiceMapNoPathVariable.get(pathMatcher);
}
// second search from pathToServiceMapContainPathVariable
if (pathToServiceMapContainPathVariable.containsKey(pathMather)) {
return pathToServiceMapContainPathVariable.get(pathMather);
if (pathToServiceMapContainPathVariable.containsKey(pathMatcher)) {
return pathToServiceMapContainPathVariable.get(pathMatcher);
}
throw new PathNoFoundException("rest service Path no found, current path info:" + pathMather);
return null;
}
/**

View File

@ -35,7 +35,6 @@ import org.apache.dubbo.rpc.protocol.rest.annotation.metadata.MetadataResolver;
import org.apache.dubbo.rpc.protocol.rest.deploy.ServiceDeployer;
import org.apache.dubbo.rpc.protocol.rest.deploy.ServiceDeployerManager;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

View File

@ -20,16 +20,21 @@ import org.apache.dubbo.common.BaseServiceMetadata;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.metadata.rest.ArgInfo;
import org.apache.dubbo.metadata.rest.PathMatcher;
import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcInvocation;
import org.apache.dubbo.rpc.protocol.rest.annotation.ParamParserManager;
import org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.ProviderParseContext;
import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant;
import org.apache.dubbo.rpc.protocol.rest.exception.ParamParseException;
import org.apache.dubbo.rpc.protocol.rest.pair.InvokerAndRestMethodMetadataPair;
import org.apache.dubbo.rpc.protocol.rest.request.RequestFacade;
import org.apache.dubbo.rpc.protocol.rest.util.HttpHeaderUtil;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
@ -77,15 +82,15 @@ public class RestRPCInvocationUtil {
* create parseMethodArgs context
*
* @param request
* @param servletRequest
* @param servletResponse
* @param originRequest
* @param originResponse
* @param restMethodMetadata
* @return
*/
private static ProviderParseContext createParseContext(RequestFacade request, Object servletRequest, Object servletResponse, RestMethodMetadata restMethodMetadata) {
private static ProviderParseContext createParseContext(RequestFacade request, Object originRequest, Object originResponse, RestMethodMetadata restMethodMetadata) {
ProviderParseContext parseContext = new ProviderParseContext(request);
parseContext.setResponse(servletResponse);
parseContext.setRequest(servletRequest);
parseContext.setResponse(originResponse);
parseContext.setRequest(originRequest);
Object[] objects = new Object[restMethodMetadata.getArgInfos().size()];
parseContext.setArgs(Arrays.asList(objects));
@ -124,19 +129,105 @@ public class RestRPCInvocationUtil {
/**
* get path mapping
* get InvokerAndRestMethodMetadataPair by path matcher
*
* @param request
* @param pathAndInvokerMapper
* @param pathMatcher
* @return
*/
public static InvokerAndRestMethodMetadataPair getRestMethodMetadata(RequestFacade request, PathAndInvokerMapper pathAndInvokerMapper) {
public static InvokerAndRestMethodMetadataPair getRestMethodMetadataAndInvokerPair(PathMatcher pathMatcher) {
PathAndInvokerMapper pathAndInvokerMapper = (PathAndInvokerMapper) RpcContext.getServerAttachment().getObjectAttachment(RestConstant.PATH_AND_INVOKER_MAPPER);
if (pathAndInvokerMapper == null) {
return null;
}
return pathAndInvokerMapper.getRestMethodMetadata(pathMatcher);
}
/**
* get InvokerAndRestMethodMetadataPair from rpc context
*
* @param request
* @return
*/
public static InvokerAndRestMethodMetadataPair getRestMethodMetadataAndInvokerPair(RequestFacade request) {
PathMatcher pathMather = createPathMatcher(request);
return getRestMethodMetadataAndInvokerPair(pathMather);
}
/**
* get invoker by request
*
* @param request
* @return
*/
public static Invoker getInvokerByRequest(RequestFacade request) {
PathMatcher pathMatcher = createPathMatcher(request);
return getInvoker(pathMatcher);
}
/**
* get invoker by service method
*
* compare method`s name,param types
*
* @param serviceMethod
* @return
*/
public static Invoker getInvokerByServiceInvokeMethod(Method serviceMethod) {
if (serviceMethod == null) {
return null;
}
InvokerAndRestMethodMetadataPair pair = getRestMethodMetadataAndInvokerPair(PathMatcher.getInvokeCreatePathMatcher(serviceMethod));
if (pair == null) {
return null;
}
return pair.getInvoker();
}
/**
* get invoker by path matcher
*
* @param pathMatcher
* @return
*/
public static Invoker getInvoker(PathMatcher pathMatcher) {
InvokerAndRestMethodMetadataPair pair = getRestMethodMetadataAndInvokerPair(pathMatcher);
if (pair == null) {
return null;
}
return pair.getInvoker();
}
/**
* create path matcher by request
*
* @param request
* @return
*/
public static PathMatcher createPathMatcher(RequestFacade request) {
String path = request.getPath();
String version = request.getHeader(RestHeaderEnum.VERSION.getHeader());
String group = request.getHeader(RestHeaderEnum.GROUP.getHeader());
String method = request.getMethod();
return pathAndInvokerMapper.getRestMethodMetadata(path, version, group, null, method);
return PathMatcher.getInvokeCreatePathMatcher(path, version, group, null, method);
}

View File

@ -63,5 +63,10 @@ public interface RestConstant {
int IDLE_TIMEOUT = -1;
int KEEP_ALIVE_TIMEOUT = 60;
/**
* ServerAttachment pathAndInvokerMapper key
*/
String PATH_AND_INVOKER_MAPPER = "pathAndInvokerMapper";
}

View File

@ -17,6 +17,7 @@
package org.apache.dubbo.rpc.protocol.rest.handler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpRequest;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.metadata.rest.RestMethodMetadata;
@ -45,6 +46,8 @@ import org.apache.dubbo.rpc.protocol.rest.util.MediaTypeUtil;
import java.io.IOException;
import static org.apache.dubbo.rpc.protocol.rest.constans.RestConstant.PATH_AND_INVOKER_MAPPER;
/**
* netty http request handler
*/
@ -78,6 +81,8 @@ public class NettyHttpHandler implements HttpHandler<NettyRequestFacade, NettyHt
// set response
RpcContext.getServiceContext().setResponse(nettyHttpResponse);
RpcContext.getServerAttachment().setObjectAttachment(PATH_AND_INVOKER_MAPPER, pathAndInvokerMapper);
// TODO add request filter chain
FullHttpRequest nettyHttpRequest = requestFacade.getRequest();
@ -102,9 +107,14 @@ public class NettyHttpHandler implements HttpHandler<NettyRequestFacade, NettyHt
}
private void doHandler(FullHttpRequest nettyHttpRequest, NettyHttpResponse nettyHttpResponse, RequestFacade request) throws Exception {
protected void doHandler(HttpRequest nettyHttpRequest, NettyHttpResponse nettyHttpResponse, RequestFacade request) throws Exception {
// acquire metadata by request
InvokerAndRestMethodMetadataPair restMethodMetadataPair = RestRPCInvocationUtil.getRestMethodMetadata(request, pathAndInvokerMapper);
InvokerAndRestMethodMetadataPair restMethodMetadataPair = RestRPCInvocationUtil.getRestMethodMetadataAndInvokerPair(request);
// path NoFound 404
if (restMethodMetadataPair == null) {
throw new PathNoFoundException("rest service Path no found, current path info:" + RestRPCInvocationUtil.createPathMatcher(request));
}
Invoker invoker = restMethodMetadataPair.getInvoker();

View File

@ -49,6 +49,8 @@ import org.apache.dubbo.rpc.protocol.rest.rest.HttpMethodServiceImpl;
import org.apache.dubbo.rpc.protocol.rest.rest.RestDemoForTestException;
import org.apache.dubbo.rpc.protocol.rest.rest.RestDemoService;
import org.apache.dubbo.rpc.protocol.rest.rest.RestDemoServiceImpl;
import org.apache.dubbo.rpc.protocol.rest.rest.TestGetInvokerService;
import org.apache.dubbo.rpc.protocol.rest.rest.TestGetInvokerServiceImpl;
import org.hamcrest.CoreMatchers;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.junit.jupiter.api.AfterEach;
@ -596,6 +598,27 @@ class JaxrsRestProtocolTest {
exporter.unexport();
}
@Test
void testGetInvoker() {
Assertions.assertDoesNotThrow(()->{
URL exportUrl = URL.valueOf("rest://127.0.0.1:" + availablePort + "/rest?interface=org.apache.dubbo.rpc.protocol.rest.rest.TestGetInvokerService");
TestGetInvokerService server = new TestGetInvokerServiceImpl();
URL url = this.registerProvider(exportUrl, server, DemoService.class);
Exporter<TestGetInvokerService> exporter = protocol.export(proxy.getInvoker(server, TestGetInvokerService.class, url));
TestGetInvokerService invokerService = this.proxy.getProxy(protocol.refer(TestGetInvokerService.class, url));
String invoker = invokerService.getInvoker();
Assertions.assertEquals("success", invoker);
exporter.unexport();
});
}
private URL registerProvider(URL url, Object impl, Class<?> interfaceClass) {
ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass);
ProviderModel providerModel = new ProviderModel(

View File

@ -0,0 +1,30 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.rest.rest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@Path("/test")
public interface TestGetInvokerService {
@GET
@Path("/getInvoker")
String getInvoker();
}

View File

@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.rest.rest;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.protocol.rest.RestRPCInvocationUtil;
import org.apache.dubbo.rpc.protocol.rest.request.RequestFacade;
import org.junit.jupiter.api.Assertions;
import java.lang.reflect.Method;
public class TestGetInvokerServiceImpl implements TestGetInvokerService {
@Override
public String getInvoker() {
Object request = RpcContext.getServiceContext().getRequest();
Invoker invokerByRequest = RestRPCInvocationUtil.getInvokerByRequest((RequestFacade) request);
Method hello = null;
Method hashcode = null;
try {
hello = TestGetInvokerServiceImpl.class.getDeclaredMethod("getInvoker");
hashcode = TestGetInvokerServiceImpl.class.getDeclaredMethod("hashcode");
} catch (NoSuchMethodException e) {
}
Invoker invokerByServiceInvokeMethod = RestRPCInvocationUtil.getInvokerByServiceInvokeMethod(hello);
Invoker invoker = RestRPCInvocationUtil.getInvokerByServiceInvokeMethod(hashcode);
Assertions.assertEquals(invokerByRequest, invokerByServiceInvokeMethod);
Assertions.assertNull(invoker);
return "success";
}
}

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You 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.
-->
<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>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starters</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
<version>${revision}</version>
<packaging>jar</packaging>
<description>Apache Dubbo Nacos Spring Boot Starter</description>
<properties>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You 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.
-->
<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>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starters</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-zookeeper-curator5-spring-boot-starter</artifactId>
<version>${revision}</version>
<packaging>jar</packaging>
<description>Apache Dubbo Zookeeper Curator5 Spring Boot Starter</description>
<properties>
<curator5_version>5.1.0</curator5_version>
<zookeeper_version>3.8.1</zookeeper_version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>parent</artifactId>
<version>${zookeeper_version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>${curator5_version}</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>${zookeeper_version}</version>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
</dependency>
<dependency>
<groupId>jline</groupId>
<artifactId>jline</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
</dependency>
<dependency>
<groupId>org.xerial.snappy</groupId>
<artifactId>snappy-java</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You 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.
-->
<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>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starters</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>dubbo-zookeeper-spring-boot-starter</artifactId>
<version>${revision}</version>
<packaging>jar</packaging>
<description>Apache Dubbo Zookeeper Spring Boot Starter</description>
<properties>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -20,7 +20,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot</artifactId>
<artifactId>dubbo-spring-boot-starters</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -40,7 +40,7 @@
<properties>
<micrometer.version>1.11.0</micrometer.version>
<micrometer-tracing.version>1.1.1</micrometer-tracing.version>
<opentelemetry.version>1.26.0</opentelemetry.version>
<opentelemetry.version>1.27.0</opentelemetry.version>
<zipkin-reporter.version>2.16.4</zipkin-reporter.version>
<prometheus-client.version>0.16.0</prometheus-client.version>
</properties>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You 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.
-->
<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>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-spring-boot-starters</artifactId>
<packaging>pom</packaging>
<description>Apache Dubbo Spring Boot Starters</description>
<modules>
<module>observability</module>
<module>dubbo-nacos-spring-boot-starter</module>
<module>dubbo-zookeeper-spring-boot-starter</module>
<module>dubbo-zookeeper-curator5-spring-boot-starter</module>
</modules>
</project>

View File

@ -36,7 +36,7 @@
<module>dubbo-spring-boot-autoconfigure</module>
<module>dubbo-spring-boot-compatible</module>
<module>dubbo-spring-boot-starter</module>
<module>dubbo-spring-boot-observability-starters</module>
<module>dubbo-spring-boot-starters</module>
</modules>
<properties>
@ -45,7 +45,7 @@
<!-- Fix the bug of log4j refer:https://github.com/apache/logging-log4j2/pull/608 -->
<log4j2_version>2.20.0</log4j2_version>
<!-- Spring boot buddy is lower than the delivery dependency package version and can only show the defined dependency version -->
<byte-buddy.version>1.14.4</byte-buddy.version>
<byte-buddy.version>1.14.5</byte-buddy.version>
</properties>
<dependencyManagement>

View File

@ -417,6 +417,21 @@
<artifactId>dubbo-spring-boot-observability-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-zookeeper-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-zookeeper-curator5-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<!-- test -->
<dependency>

View File

@ -64,7 +64,7 @@ class FileTest {
ignoredModulesInDubboAll.add(Pattern.compile("dubbo-metadata-processor"));
ignoredModulesInDubboAll.add(Pattern.compile("dubbo-native.*"));
ignoredModulesInDubboAll.add(Pattern.compile("dubbo-config-spring6.*"));
ignoredModulesInDubboAll.add(Pattern.compile("dubbo-spring-boot.*"));
ignoredModulesInDubboAll.add(Pattern.compile(".*spring-boot.*"));
ignoredModulesInDubboAll.add(Pattern.compile("dubbo-annotation-processor.*"));
ignoredModulesInDubboAll.add(Pattern.compile("dubbo-maven-plugin"));
}

View File

@ -515,7 +515,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>2.0.1</version>
<version>2.1.0</version>
<executions>
<execution>
<id>license-check</id>