diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
index fcf113302f..3380621774 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
@@ -128,6 +128,8 @@ public interface CommonConstants {
String THREAD_POOL_EXHAUSTED_LISTENERS_KEY = "thread-pool-exhausted-listeners";
+ String JSON_CHECK_LEVEL_KEY = "jsonCheckLevel";
+
String THREADS_KEY = "threads";
String QUEUES_KEY = "queues";
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ProtocolConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ProtocolConfig.java
index c6be90b3db..20220945ae 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ProtocolConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ProtocolConfig.java
@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Optional;
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
+import static org.apache.dubbo.common.constants.CommonConstants.JSON_CHECK_LEVEL_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.SSL_ENABLED_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.THREAD_POOL_EXHAUSTED_LISTENERS_KEY;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION;
@@ -229,6 +230,8 @@ public class ProtocolConfig extends AbstractConfig {
*/
private String extProtocol;
+ private String jsonCheckLevel;
+
public ProtocolConfig() {
}
@@ -331,6 +334,15 @@ public class ProtocolConfig extends AbstractConfig {
this.threadname = threadname;
}
+ @Parameter(key = JSON_CHECK_LEVEL_KEY)
+ public String getJsonCheckLevel() {
+ return jsonCheckLevel;
+ }
+
+ public void setJsonCheckLevel(String jsonCheckLevel) {
+ this.jsonCheckLevel = jsonCheckLevel;
+ }
+
@Parameter(key = THREAD_POOL_EXHAUSTED_LISTENERS_KEY)
public String getThreadPoolExhaustedListeners() {
return threadPoolExhaustedListeners;
diff --git a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
index ea3e643132..029bffc592 100644
--- a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
+++ b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
@@ -1692,6 +1692,11 @@
+
+
+
+
+
diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java
index d2d5d917a7..9c1fc67f95 100644
--- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java
+++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java
@@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.protocol.rest;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.ConcurrentHashMapUtils;
+import org.apache.dubbo.common.utils.JsonCompatibilityUtil;
import org.apache.dubbo.metadata.rest.ServiceRestMetadata;
import org.apache.dubbo.remoting.api.pu.DefaultPuHandler;
import org.apache.dubbo.remoting.exchange.PortUnificationExchanger;
@@ -37,6 +38,7 @@ import org.apache.dubbo.rpc.protocol.rest.deploy.ServiceDeployerManager;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -47,6 +49,9 @@ import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.REST_SERVICE_DEPLOYER_URL_ATTRIBUTE_KEY;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_ERROR_CLOSE_CLIENT;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_ERROR_CLOSE_SERVER;
+import static org.apache.dubbo.rpc.protocol.rest.constans.RestConstant.JSON_CHECK_LEVEL;
+import static org.apache.dubbo.rpc.protocol.rest.constans.RestConstant.JSON_CHECK_LEVEL_STRICT;
+import static org.apache.dubbo.rpc.protocol.rest.constans.RestConstant.JSON_CHECK_LEVEL_WARN;
import static org.apache.dubbo.rpc.protocol.rest.constans.RestConstant.PATH_SEPARATOR;
public class RestProtocol extends AbstractProtocol {
@@ -90,6 +95,10 @@ public class RestProtocol extends AbstractProtocol {
MetadataResolver.resolveProviderServiceMetadata(url.getServiceModel().getProxyObject().getClass(),
url, getContextPath(url));
+ // check json compatibility
+ String jsonCheckLevel = url.getUrlParam().getParameter(JSON_CHECK_LEVEL);
+ checkJsonCompatibility(invoker.getInterface(), jsonCheckLevel);
+
// deploy service
URL newURL = ServiceDeployerManager.deploy(url, serviceRestMetadata, invoker);
@@ -113,6 +122,29 @@ public class RestProtocol extends AbstractProtocol {
return exporter;
}
+ private void checkJsonCompatibility(Class> clazz, String jsonCheckLevel) throws RpcException {
+
+ if (jsonCheckLevel == null || JSON_CHECK_LEVEL_WARN.equals(jsonCheckLevel)) {
+ boolean compatibility = JsonCompatibilityUtil.checkClassCompatibility(clazz);
+ if (!compatibility) {
+ List unsupportedMethods = JsonCompatibilityUtil.getUnsupportedMethods(clazz);
+ assert unsupportedMethods != null;
+ logger.warn("", "", "", String.format("Interface %s does not support json serialization, the specific methods are %s.", clazz.getName(), unsupportedMethods));
+ } else {
+ logger.debug("Check json compatibility complete, all methods of {} can be serialized using json.", clazz.getName());
+ }
+ } else if (JSON_CHECK_LEVEL_STRICT.equals(jsonCheckLevel)) {
+ boolean compatibility = JsonCompatibilityUtil.checkClassCompatibility(clazz);
+ if (!compatibility) {
+ List unsupportedMethods = JsonCompatibilityUtil.getUnsupportedMethods(clazz);
+ assert unsupportedMethods != null;
+ throw new IllegalStateException(String.format("Interface %s does not support json serialization, the specific methods are %s.", clazz.getName(), unsupportedMethods));
+ } else {
+ logger.debug("Check json compatibility complete, all methods of {} can be serialized using json.", clazz.getName());
+ }
+ }
+ }
+
@Override
protected Invoker protocolBindingRefer(final Class type, final URL url) throws RpcException {
diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/constans/RestConstant.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/constans/RestConstant.java
index fc2edc66ad..2c62f5cec0 100644
--- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/constans/RestConstant.java
+++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/constans/RestConstant.java
@@ -57,6 +57,11 @@ public interface RestConstant {
String IDLE_TIMEOUT_PARAM = "idle.timeout";
String KEEP_ALIVE_TIMEOUT_PARAM = "keep.alive.timeout";
+ String JSON_CHECK_LEVEL = "jsonCheckLevel";
+ String JSON_CHECK_LEVEL_DISABLED = "disabled";
+ String JSON_CHECK_LEVEL_WARN = "warn";
+ String JSON_CHECK_LEVEL_STRICT = "strict";
+
int MAX_REQUEST_SIZE = 1024 * 1024 * 10;
int MAX_INITIAL_LINE_LENGTH = 4096;
int MAX_HEADER_SIZE = 8192;
diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/JsonCompatibilityCheckTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/JsonCompatibilityCheckTest.java
new file mode 100644
index 0000000000..2f871f2078
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/JsonCompatibilityCheckTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.rpc.Exporter;
+import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.ProxyFactory;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ModuleServiceRepository;
+import org.apache.dubbo.rpc.model.ProviderModel;
+import org.apache.dubbo.rpc.model.ServiceDescriptor;
+import org.apache.dubbo.rpc.protocol.rest.compatibility.RestDemoService;
+import org.apache.dubbo.rpc.protocol.rest.compatibility.RestDemoServiceImpl;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.dubbo.rpc.protocol.rest.constans.RestConstant.JSON_CHECK_LEVEL;
+import static org.apache.dubbo.rpc.protocol.rest.constans.RestConstant.JSON_CHECK_LEVEL_DISABLED;
+import static org.apache.dubbo.rpc.protocol.rest.constans.RestConstant.JSON_CHECK_LEVEL_STRICT;
+import static org.apache.dubbo.rpc.protocol.rest.constans.RestConstant.JSON_CHECK_LEVEL_WARN;
+
+
+public class JsonCompatibilityCheckTest {
+
+ private final int availablePort = NetUtils.getAvailablePort();
+ private final URL exportUrl = URL.valueOf("rest://127.0.0.1:" + availablePort + "/rest?interface=org.apache.dubbo.rpc.protocol.rest.compatibility.RestDemoService");
+ private final ModuleServiceRepository repository = ApplicationModel.defaultModel().getDefaultModule().getServiceRepository();
+
+ private final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("rest");
+ private final ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+
+
+ @Test
+ public void testJsonCheckDisabled() {
+
+ RestDemoService server = new RestDemoServiceImpl();
+
+ URL url = this.registerProvider(exportUrl, server, RestDemoService.class);
+
+ url = url.addParameter(JSON_CHECK_LEVEL, JSON_CHECK_LEVEL_DISABLED);
+
+ Exporter exporter = protocol.export(proxy.getInvoker(server, RestDemoService.class, url));
+
+ exporter.unexport();
+ }
+
+ @Test
+ public void testJsonCheckWarn() {
+ RestDemoService server = new RestDemoServiceImpl();
+
+ URL url = this.registerProvider(exportUrl, server, RestDemoService.class);
+
+ url = url.addParameter(JSON_CHECK_LEVEL, JSON_CHECK_LEVEL_WARN);
+
+ Exporter exporter = protocol.export(proxy.getInvoker(server, RestDemoService.class, url));
+
+ exporter.unexport();
+ }
+
+ @Test
+ public void testJsonCheckStrict() {
+ RestDemoService server = new RestDemoServiceImpl();
+
+ URL url = this.registerProvider(exportUrl, server, RestDemoService.class);
+
+ URL newUrl = url.addParameter(JSON_CHECK_LEVEL, JSON_CHECK_LEVEL_STRICT);
+
+ Assertions.assertThrowsExactly(IllegalStateException.class, () -> {
+ Exporter exporter = null;
+ try {
+ exporter = protocol.export(proxy.getInvoker(server, RestDemoService.class, newUrl));
+ } finally {
+ if (exporter != null) exporter.unexport();
+ }
+ });
+ }
+
+ private URL registerProvider(URL url, Object impl, Class> interfaceClass) {
+ ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass);
+ ProviderModel providerModel = new ProviderModel(
+ url.getServiceKey(),
+ impl,
+ serviceDescriptor,
+ null,
+ null);
+ repository.registerProvider(providerModel);
+ return url.setServiceModel(providerModel);
+ }
+}
diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/Apple.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/Apple.java
new file mode 100644
index 0000000000..2122660f6a
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/Apple.java
@@ -0,0 +1,26 @@
+/*
+ * 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.compatibility;
+
+public class Apple implements Fruit {
+
+ @Override
+ public String sayName() {
+ return "This is apple";
+ }
+}
diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/Fruit.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/Fruit.java
new file mode 100644
index 0000000000..c7c36aa4c3
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/Fruit.java
@@ -0,0 +1,22 @@
+/*
+ * 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.compatibility;
+
+public interface Fruit {
+ String sayName();
+}
diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/RestDemoService.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/RestDemoService.java
new file mode 100644
index 0000000000..a487b37a47
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/RestDemoService.java
@@ -0,0 +1,46 @@
+/*
+ * 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.compatibility;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+@Path("rest")
+public interface RestDemoService {
+ @GET
+ @Path("hello")
+ @Consumes({MediaType.APPLICATION_JSON})
+ String sayHello(String name);
+
+ @GET
+ @Path("hi")
+ @Consumes({MediaType.APPLICATION_JSON})
+ String sayHi();
+
+ @GET
+ @Path("fruit")
+ @Consumes({MediaType.APPLICATION_JSON})
+ Fruit sayFruit();
+
+ @GET
+ @Path("apple")
+ @Consumes({MediaType.APPLICATION_JSON})
+ Apple sayApple();
+}
diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/RestDemoServiceImpl.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/RestDemoServiceImpl.java
new file mode 100644
index 0000000000..f1b94f4455
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/compatibility/RestDemoServiceImpl.java
@@ -0,0 +1,39 @@
+/*
+ * 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.compatibility;
+
+public class RestDemoServiceImpl implements RestDemoService {
+ @Override
+ public String sayHello(String name) {
+ return "hello";
+ }
+
+ @Override
+ public String sayHi() {
+ return "hi";
+ }
+
+ @Override
+ public Fruit sayFruit() {
+ return new Apple();
+ }
+
+ @Override
+ public Apple sayApple() {
+ return new Apple();
+ }
+}