【OSPP】Json check compatiblity (#12910)

* Initialize

* update code style

* delete redundant file

* Change position

* delete redundant import

* Add JsonCompatibilityUtils

* Delete unused import

* change funcs

* Add test case

* update color class

* add license

* fix style

* check interface

* Fix log style

* Add attribbute of check-json-level

* Update log style

* Update log style

* Fix bugs

* Add test and document

* Fix bugs

* Delete yaml

* Delete xml

* Add license

* Add license

* Merge upstream branch

* Update IllegalStateException
This commit is contained in:
hjyp 2023-08-29 11:53:15 +08:00 committed by GitHub
parent 679ce4496c
commit f91246ab09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 298 additions and 0 deletions

View File

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

View File

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

View File

@ -1692,6 +1692,11 @@
<xsd:documentation><![CDATA[ The protocol context path. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="json-check-level" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The level of json compatibility check. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="register" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The protocol can be register to registry. ]]></xsd:documentation>

View File

@ -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<String> 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<String> 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 <T> Invoker<T> protocolBindingRefer(final Class<T> type, final URL url) throws RpcException {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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