From b5bd138243a77a90d44d1c2351329fa6fc779a63 Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Thu, 25 Jul 2024 20:31:24 +0800 Subject: [PATCH 1/3] Generic param support --- .../dubbo/common/utils/MethodUtils.java | 15 ++ .../support/jaxrs/BodyArgumentResolver.java | 2 +- .../jaxrs/FallbackArgumentResolver.java | 6 +- .../jaxrs/JaxrsRequestMappingResolver.java | 3 +- .../rest/support/jaxrs/JaxrsRestToolKit.java | 2 +- .../jaxrs/filter/InterceptorContextImpl.java | 2 +- .../MatrixVariableArgumentResolver.java | 2 +- .../spring/RequestBodyArgumentResolver.java | 6 +- .../SpringMvcRequestMappingResolver.java | 19 ++- .../support/spring/SpringRestToolKit.java | 47 ++++-- .../support/spring/RestProtocolTest.groovy | 142 ++++++++++++++---- .../spring/service/SpringDemoService.java | 64 +++++++- .../spring/service/SpringDemoServiceImpl.java | 10 +- .../src/test/resources/log4j2-test.xml | 1 + .../http12/message/HttpMessageDecoder.java | 16 ++ .../http12/message/codec/JsonCodec.java | 12 ++ .../http12/message/codec/JsonPbCodec.java | 8 + .../http12/message/codec/YamlCodec.java | 17 +++ .../tri/h12/HttpMessageDecoderWrapper.java | 11 ++ .../dubbo/rpc/protocol/tri/rest/Messages.java | 4 +- .../tri/rest/RestHttpMessageCodec.java | 17 ++- .../rest/argument/GeneralTypeConverter.java | 49 +++++- .../NamedValueArgumentResolverSupport.java | 17 +-- .../DefaultRequestMappingRegistry.java | 27 +++- .../tri/rest/mapping/RequestMapping.java | 8 + .../mapping/RestRequestHandlerMapping.java | 8 + .../condition/ServiceVersionCondition.java | 2 +- .../rest/mapping/meta/AnnotationSupport.java | 2 +- .../tri/rest/mapping/meta/BeanMeta.java | 2 +- .../tri/rest/mapping/meta/MethodMeta.java | 61 +++++++- .../mapping/meta/MethodParameterMeta.java | 9 -- .../tri/rest/mapping/meta/NamedValueMeta.java | 13 ++ .../tri/rest/mapping/meta/ParameterMeta.java | 26 ++-- .../basic/BasicRequestMappingResolver.java | 5 +- .../basic/FallbackArgumentResolver.java | 66 ++++++-- .../support/basic/ParamArgumentResolver.java | 8 +- .../tri/rest/util/AbstractRestToolKit.java | 13 +- .../protocol/tri/rest/util/MethodWalker.java | 3 - .../protocol/tri/rest/util/RequestUtils.java | 110 +++++--------- .../support/basic/RestProtocolTest.groovy | 41 ++++- .../tri/rest/service/DemoService.java | 21 +++ .../tri/rest/service/DemoServiceImpl.java | 43 ++++++ 42 files changed, 722 insertions(+), 218 deletions(-) diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java index a4b829ce455..a826cd1df5c 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java @@ -462,4 +462,19 @@ static long invokeAndReturnLong(Method method, Object targetObj) { return -1; } } + + static String toShortString(Method method) { + StringBuilder sb = new StringBuilder(64); + sb.append(method.getDeclaringClass().getName()); + sb.append('.').append(method.getName()).append('('); + Class[] parameterTypes = method.getParameterTypes(); + for (int i = 0, len = parameterTypes.length; i < len; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(parameterTypes[i].getSimpleName()); + } + sb.append(')'); + return sb.toString(); + } } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.java index 5d461341033..fc3640aa10d 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.java @@ -51,6 +51,6 @@ public Object resolve( throw new RestException(e); } } - return RequestUtils.decodeBody(request, type); + return RequestUtils.decodeBody(request, parameter.getActualGenericType()); } } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java index 0ecc924df79..07076ba08fa 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java @@ -45,7 +45,7 @@ protected NamedValueMeta createNamedValueMeta(ParameterMeta param) { @Override protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { - Object value = RequestUtils.decodeBody(request, meta.type()); + Object value = RequestUtils.decodeBody(request, meta.genericType()); if (value != null) { return value; } @@ -65,7 +65,7 @@ protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request throw new RestException(e); } } - Object value = RequestUtils.decodeBody(request, meta.type()); + Object value = RequestUtils.decodeBody(request, meta.genericType()); if (value != null) { return value; } @@ -77,7 +77,7 @@ protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request @Override protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { - Object value = RequestUtils.decodeBody(request, meta.type()); + Object value = RequestUtils.decodeBody(request, meta.genericType()); if (value != null) { return value; } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java index 72cb3253513..f9fc66768eb 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java @@ -22,7 +22,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver; -import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationSupport; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; @@ -76,7 +75,7 @@ public RequestMapping resolve(MethodMeta methodMeta) { return builder(methodMeta, path, httpMethod) .name(methodMeta.getMethod().getName()) .contextPath(methodMeta.getServiceMeta().getContextPath()) - .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) + .service(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion()) .cors(globalCorsMeta) .build(); } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRestToolKit.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRestToolKit.java index 56c3365e535..4ce80939a82 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRestToolKit.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRestToolKit.java @@ -54,7 +54,7 @@ public Object convert(Object value, ParameterMeta parameter) { } ParamConverter converter = paramConverterFactory.getParamConverter( - parameter.getType(), parameter.getGenericType(), parameter.getRealAnnotations()); + parameter.getType(), parameter.getGenericType(), parameter.getRawAnnotations()); if (converter != null) { return value instanceof String ? converter.fromString((String) value) : converter.toString(value); } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java index ee67447bb51..1f64c21f9e3 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java @@ -56,7 +56,7 @@ public void removeProperty(String name) { @Override public Annotation[] getAnnotations() { - return getHandler().getMethod().getRealAnnotations(); + return getHandler().getMethod().getRawAnnotations(); } @Override diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java index 1ca99a5cb08..f68ff9cacf1 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java @@ -117,7 +117,7 @@ private static class MatrixNamedValueMeta extends NamedValueMeta { private final String pathVar; - public MatrixNamedValueMeta(String name, boolean required, String defaultValue, String pathVar) { + MatrixNamedValueMeta(String name, boolean required, String defaultValue, String pathVar) { super(name, required, defaultValue); this.pathVar = pathVar; } diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java index 4bc2bf14013..dcf3173f358 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java @@ -50,7 +50,7 @@ protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResp } return meta.parameterMeta().bind(request, response); } - return RequestUtils.decodeBody(request, meta.type()); + return RequestUtils.decodeBody(request, meta.genericType()); } @Override @@ -66,7 +66,7 @@ protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request if (RequestUtils.isFormOrMultiPart(request)) { return request.formParameterValues(meta.name()); } - return RequestUtils.decodeBody(request, meta.type()); + return RequestUtils.decodeBody(request, meta.genericType()); } @Override @@ -74,6 +74,6 @@ protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpR if (RequestUtils.isFormOrMultiPart(request)) { return RequestUtils.getFormParametersMap(request); } - return RequestUtils.decodeBody(request, meta.type()); + return RequestUtils.decodeBody(request, meta.genericType()); } } diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java index 728156a98d3..bdb3aff0197 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -24,7 +24,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver; -import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; @@ -70,9 +69,11 @@ public RequestMapping resolve(ServiceMeta serviceMeta) { String[] methods = requestMapping == null ? httpExchange.getStringArray("method") : requestMapping.getStringArray("method"); + String[] paths = requestMapping == null ? httpExchange.getValueArray() : requestMapping.getValueArray(); return builder(requestMapping, httpExchange, serviceMeta.findMergedAnnotation(Annotations.ResponseStatus)) .method(methods) .name(serviceMeta.getType().getSimpleName()) + .path(paths) .contextPath(serviceMeta.getContextPath()) .cors(buildCorsMeta(serviceMeta.findMergedAnnotation(Annotations.CrossOrigin), methods)) .build(); @@ -92,14 +93,20 @@ public RequestMapping resolve(MethodMeta methodMeta) { } ServiceMeta serviceMeta = methodMeta.getServiceMeta(); + String name = methodMeta.getMethod().getName(); String[] methods = requestMapping == null ? httpExchange.getStringArray("method") : requestMapping.getStringArray("method"); + String[] paths = requestMapping == null ? httpExchange.getValueArray() : requestMapping.getValueArray(); + if (paths.length == 0) { + paths = new String[] {'/' + name}; + } return builder(requestMapping, httpExchange, methodMeta.findMergedAnnotation(Annotations.ResponseStatus)) .method(methods) - .name(methodMeta.getMethod().getName()) + .name(name) + .path(paths) .contextPath(serviceMeta.getContextPath()) - .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) + .service(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion()) .cors(buildCorsMeta(methodMeta.findMergedAnnotation(Annotations.CrossOrigin), methods)) .build(); } @@ -116,12 +123,10 @@ private Builder builder( } } if (requestMapping == null) { - return builder.path(httpExchange.getValueArray()) - .consume(httpExchange.getStringArray("contentType")) + return builder.consume(httpExchange.getStringArray("contentType")) .produce(httpExchange.getStringArray("accept")); } - return builder.path(requestMapping.getValueArray()) - .param(requestMapping.getStringArray("params")) + return builder.param(requestMapping.getStringArray("params")) .header(requestMapping.getStringArray("headers")) .consume(requestMapping.getStringArray("consumes")) .produce(requestMapping.getStringArray("produces")); diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringRestToolKit.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringRestToolKit.java index a2638c7748d..e8c75ae6c89 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringRestToolKit.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringRestToolKit.java @@ -16,12 +16,17 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.support.spring; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.config.spring.extension.SpringExtensionInjector; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.Messages; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.RestException; import org.apache.dubbo.rpc.protocol.tri.rest.argument.GeneralTypeConverter; import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodParameterMeta; @@ -49,6 +54,9 @@ final class SpringRestToolKit implements RestToolKit { + private static final Logger LOGGER = LoggerFactory.getLogger(SpringRestToolKit.class); + + private final Map cache = CollectionUtils.newConcurrentHashMap(); private final ConfigurableBeanFactory beanFactory; private final PropertyPlaceholderHelper placeholderHelper; private final ConfigurationWrapper configuration; @@ -98,23 +106,42 @@ public String resolvePlaceholders(String text) { @Override public Object convert(Object value, ParameterMeta parameter) { - if (value instanceof Collection) { + boolean tried = false; + if (value instanceof Collection || value instanceof Map) { + tried = true; Object target = typeConverter.convert(value, parameter.getGenericType()); if (target != null) { return target; } } - TypeDescriptor targetType = (TypeDescriptor) parameter.getTypeDescriptor(); - if (targetType == null) { - MethodParameterMeta meta = (MethodParameterMeta) parameter; - targetType = new TypeDescriptor(new MethodParameter(meta.getMethod(), meta.getIndex())); - parameter.setTypeDescriptor(targetType); + if (parameter instanceof MethodParameterMeta) { + TypeDescriptor targetType = cache.computeIfAbsent( + (MethodParameterMeta) parameter, + k -> new TypeDescriptor(new MethodParameter(k.getMethod(), k.getIndex()))); + TypeDescriptor sourceType = TypeDescriptor.forObject(value); + if (conversionService.canConvert(sourceType, targetType)) { + try { + return conversionService.convert(value, sourceType, targetType); + } catch (Throwable t) { + LOGGER.debug( + "Spring convert value '{}' from type [{}] to type [{}] failed", + value, + value.getClass(), + parameter.getGenericType(), + t); + } + } } - TypeDescriptor sourceType = TypeDescriptor.forObject(value); - if (conversionService.canConvert(sourceType, targetType)) { - return conversionService.convert(value, sourceType, targetType); + Object target = tried ? null : typeConverter.convert(value, parameter.getGenericType()); + if (target == null && value != null) { + throw new RestException( + Messages.ARGUMENT_CONVERT_ERROR, + parameter.getName(), + value, + value.getClass(), + parameter.getGenericType()); } - return typeConverter.convert(value, parameter.getGenericType()); + return target; } @Override diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RestProtocolTest.groovy b/dubbo-plugin/dubbo-rest-spring/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RestProtocolTest.groovy index ec1935b1221..88b222a337e 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RestProtocolTest.groovy +++ b/dubbo-plugin/dubbo-rest-spring/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RestProtocolTest.groovy @@ -17,56 +17,138 @@ package org.apache.dubbo.rpc.protocol.tri.rest.support.spring -import org.apache.dubbo.rpc.protocol.tri.rest.service.DemoServiceImpl +import org.apache.dubbo.remoting.http12.message.MediaType +import org.apache.dubbo.rpc.protocol.tri.rest.service.Book import org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service.SpringDemoServiceImpl +import org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service.SpringDemoService import org.apache.dubbo.rpc.protocol.tri.rest.test.BaseServiceTest +import org.apache.dubbo.rpc.protocol.tri.test.TestRequest import org.apache.dubbo.rpc.protocol.tri.test.TestRunnerBuilder class RestProtocolTest extends BaseServiceTest { @Override void setupService(TestRunnerBuilder builder) { - builder.provider(new DemoServiceImpl()) - builder.provider(new SpringDemoServiceImpl()) + builder.provider(SpringDemoService.class, new SpringDemoServiceImpl()) } def "hello world"() { expect: runner.get(path) == output where: - path | output - '/hello?name=world' | 'hello world' - '/hello/?name=world' | 'hello world' - '/hello.yml?name=world' | 'hello world' + path | output + '/spring/hello?name=world' | 'hello world' + '/spring/hello/?name=world' | 'hello world' + '/spring/hello.yml?name=world' | 'hello world' + } + + def "list argument body test"() { + expect: + runner.post(path, body) contains output + where: + path | body | output + '/spring/listArgBodyTest?age=2' | '[1,2]' | '[1,2]' + } + + def "map argument body test"() { + expect: + runner.post(path, body) contains output + where: + path | body | output + '/spring/mapArgBodyTest?age=2' | '{"1":["2",3],"4":[5,"6"]}' | '{4:[5,6],1:[2,3]}' + } + + def "bean argument get test"() { + expect: + runner.get(path, Book.class).price == output + where: + path | output + '/spring/beanArgTest' | 0 + '/spring/beanArgTest?quote=5' | 5 + } + + def "bean argument post test"() { + expect: + runner.post(path, body, Book.class).price == output + where: + path | body | output + '/spring/beanArgTest' | [:] | 0 + '/spring/beanArgTest' | [price: 6] | 6 + '/spring/beanArgTest2' | '{"price": 5}' | 5 } def "spring bean argument test"() { expect: runner.get(path) contains output where: - path | output - '/springBean?id=1&name=sam' | '"id":1,"name":"sam"' - '/springBean?name=sam&phone=123&email=a@b.com' | '"email":"a@b.com","name":"sam","phone":"123"' - '/springBean?group.name=g1&group.owner.name=jack' | '"group":{"id":0,"name":"g1","owner":{"name":"jack"' - '/springBean?group.parent.parent.children[0].name=xx' | '"group":{"id":0,"parent":{"id":0,"parent":{"children":[{"id":0,"name":"xx"}],"id":0}}}' - '/springBean?ids=3&ids=4' | '"ids":[3,4]' - '/springBean?ids[]=3&ids[]=4' | '"ids":[3,4]' - '/springBean?ids[1]=3&ids[2]=4' | '"ids":[0,3,4]' - '/springBean?scores=3&scores=4' | '"scores":[3,4]' - '/springBean?scores[]=3&scores[]=4' | '"scores":[3,4]' - '/springBean?scores[1]=3&scores[2]=4' | '"scores":[null,3,4]' - '/springBean?tags[0].name=a&tags[0].value=b&tags[1].name=c&tags[1].value=d' | '"tags":[{"name":"a","value":"b"},{"name":"c","value":"d"}]' - '/springBean?tagsA[0].name=a&tagsA[0].value=b&tagsA[1].name=c&tagsA[1].value=d' | '"tagsA":[{"name":"a","value":"b"},{"name":"c","value":"d"}]' - '/springBean?tagsB[0].name=e&tagsB[1].name=c&tagsB[1].value=d' | '"tagsB":[{"name":"e","value":"b"},{"name":"c","value":"d"}]' - '/springBean?tagsC[0].name=e&tagsC[1].name=c&tagsC[1].value=d' | '"tagsC":[{"name":"e","value":"b"},{"name":"c","value":"d"}]' - '/springBean?groupMaps[0].one.name=a&groupMaps[1].two.name=b' | '"groupMaps":[{},{}]' //x - '/springBean?id=1&features.a=xx&features.b=2' | '"features":{},"id":1' //x - '/springBean?id=1&features[a]=xx&features[b]=2' | '"features":{"a":"xx","b":"2"}' - '/springBean?group.id=2&group.features.a=1&group.features.b=xx' | '"group":{"features":{},"id":2}' //x - '/springBean?tagMap.a.name=a&tagMap.a.value=b&tagMap.b.name=c&tagMap.b.value=d' | '"tagMap":{}' //x - '/springBean?tagMapA.a.name=e&tagMapA.b.name=c&tagMapA.b.value=d' | '"tagMapA":{"a":{"name":"a","value":"b"}}' //x - '/springBean?tagMapB[2].name=a&tagMapB[2].value=b&tagMapB[3].name=c' | '"tagMapB":{2:{"name":"a","value":"b"},3:{"name":"c"}}' - '/springBean?groupsMap.one[0].name=a&groupsMap.one[1].name=b&groupsMap.two[1].name=c' | '"groupsMap":{}' //x + path | output + '/spring/bean?id=1&name=sam' | '"id":1,"name":"sam"' + '/spring/bean?name=sam&phone=123&email=a@b.com' | '"email":"a@b.com","name":"sam","phone":"123"' + '/spring/bean?group.name=g1&group.owner.name=jack' | '"group":{"id":0,"name":"g1","owner":{"name":"jack"' + '/spring/bean?group.parent.parent.children[0].name=xx' | '"group":{"id":0,"parent":{"id":0,"parent":{"children":[{"id":0,"name":"xx"}],"id":0}}}' + '/spring/bean?ids=3&ids=4' | '"ids":[3,4]' + '/spring/bean?ids[]=3&ids[]=4' | '"ids":[3,4]' + '/spring/bean?ids[1]=3&ids[2]=4' | '"ids":[0,3,4]' + '/spring/bean?scores=3&scores=4' | '"scores":[3,4]' + '/spring/bean?scores[]=3&scores[]=4' | '"scores":[3,4]' + '/spring/bean?scores[1]=3&scores[2]=4' | '"scores":[null,3,4]' + '/spring/bean?tags[0].name=a&tags[0].value=b&tags[1].name=c&tags[1].value=d' | '"tags":[{"name":"a","value":"b"},{"name":"c","value":"d"}]' + '/spring/bean?tagsA[0].name=a&tagsA[0].value=b&tagsA[1].name=c&tagsA[1].value=d' | '"tagsA":[{"name":"a","value":"b"},{"name":"c","value":"d"}]' + '/spring/bean?tagsB[0].name=e&tagsB[1].name=c&tagsB[1].value=d' | '"tagsB":[{"name":"e","value":"b"},{"name":"c","value":"d"}]' + '/spring/bean?tagsC[0].name=e&tagsC[1].name=c&tagsC[1].value=d' | '"tagsC":[{"name":"e","value":"b"},{"name":"c","value":"d"}]' + '/spring/bean?id=1&features[a]=xx&features[b]=2' | '"features":{"a":"xx","b":"2"}' + '/spring/bean?tagMapB[2].name=a&tagMapB[2].value=b&tagMapB[3].name=c' | '"tagMapB":{2:{"name":"a","value":"b"},3:{"name":"c"}}' + } + + def "bean body test"() { + expect: + runner.post(path, body) contains output + where: + path | body | output + '/spring/beanBodyTest?age=2' | '[{"id":1,"name":"g1"},{"id":2,"name":"g2"}]' | '[{"id":1,"name":"g1"},{"id":2,"name":"g2"}]' + } + + def "urlEncodeForm test"() { + given: + def request = new TestRequest( + path: path, + contentType: MediaType.APPLICATION_FROM_URLENCODED, + params: [ + 'name': 'Sam', + 'age' : 8 + ] + ) + expect: + runner.post(request) == output + where: + path | output + '/spring/argTest' | 'Sam is 8 years old' + } + + def "no interface method test"() { + expect: + runner.get(path) contains output + where: + path | output + '/spring/noInterface' | 'ok' + '/spring/noInterfaceAndMapping' | '404' + } + + def "use interface argument name test"() { + expect: + runner.get(path) contains output + where: + path | output + '/spring/argNameTest?name=Sam' | 'Sam' + } + + def "pb server stream test"() { + expect: + runner.posts(path, body).size() == output + where: + path | body | output + '/spring/pbServerStream' | '{"service": "3"}' | 3 + '/spring/pbServerStream' | '{}' | 0 } } diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java index bdf66f566a6..4d64b125abc 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java @@ -16,12 +16,70 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service; +import org.apache.dubbo.common.stream.StreamObserver; +import org.apache.dubbo.rpc.protocol.tri.rest.service.Book; +import org.apache.dubbo.rpc.protocol.tri.rest.service.User.Group; import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx; -import org.springframework.web.bind.annotation.GetMapping; +import java.util.List; +import java.util.Map; +import io.grpc.health.v1.HealthCheckRequest; +import io.grpc.health.v1.HealthCheckResponse; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("/spring") public interface SpringDemoService { - @GetMapping("/springBean") - UserEx beanArgTest(UserEx user); + @RequestMapping + String hello(String name); + + @RequestMapping + String argTest(String name, int age); + + @RequestMapping + Book beanArgTest(@RequestBody(required = false) Book book, Integer quote); + + @RequestMapping + Book beanArgTest2(@RequestBody Book book); + + @RequestMapping("/bean") + UserEx advanceBeanArgTest(UserEx user); + + @RequestMapping + List listArgBodyTest(@RequestBody List list, int age); + + @RequestMapping + List listArgBodyTest2(List list, int age); + + @RequestMapping + Map> mapArgBodyTest(@RequestBody Map> map, int age); + + @RequestMapping + Map> mapArgBodyTest2(Map> map, int age); + + @RequestMapping + List beanBodyTest(@RequestBody List groups, int age); + + @RequestMapping + List beanBodyTest2(List groups, int age); + + @RequestMapping + Book buy(Book book); + + @RequestMapping("/buy2") + Book buy(Book book, int count); + + @RequestMapping + String say(String name, Long count); + + @RequestMapping + String say(String name); + + @RequestMapping + String argNameTest(String name); + + @RequestMapping + void pbServerStream(@RequestBody HealthCheckRequest request, StreamObserver responseObserver); } diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java index 97ef3cfe71c..e2954d27e5f 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java @@ -16,12 +16,6 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service; -import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx; +import org.apache.dubbo.rpc.protocol.tri.rest.service.DemoServiceImpl; -public class SpringDemoServiceImpl implements SpringDemoService { - - @Override - public UserEx beanArgTest(UserEx user) { - return user; - } -} +public class SpringDemoServiceImpl extends DemoServiceImpl implements SpringDemoService {} diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/resources/log4j2-test.xml b/dubbo-plugin/dubbo-rest-spring/src/test/resources/log4j2-test.xml index 91337f333b1..8fff1d4ace1 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/test/resources/log4j2-test.xml +++ b/dubbo-plugin/dubbo-rest-spring/src/test/resources/log4j2-test.xml @@ -24,6 +24,7 @@ + diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java index 448646bb368..53c4e847799 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java @@ -20,6 +20,8 @@ import org.apache.dubbo.remoting.http12.exception.DecodeException; import java.io.InputStream; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.nio.charset.Charset; import static java.nio.charset.StandardCharsets.UTF_8; @@ -28,6 +30,16 @@ public interface HttpMessageDecoder extends CodecMediaType { Object decode(InputStream inputStream, Class targetType, Charset charset) throws DecodeException; + default Object decode(InputStream inputStream, Type targetType, Charset charset) throws DecodeException { + if (targetType instanceof Class) { + return decode(inputStream, (Class) targetType, charset); + } + if (targetType instanceof ParameterizedType) { + return decode(inputStream, (Class) ((ParameterizedType) targetType).getRawType(), charset); + } + throw new DecodeException("targetType " + targetType + " is not a class"); + } + default Object[] decode(InputStream inputStream, Class[] targetTypes, Charset charset) throws DecodeException { return new Object[] {decode(inputStream, ArrayUtils.isEmpty(targetTypes) ? null : targetTypes[0], charset)}; } @@ -36,6 +48,10 @@ default Object decode(InputStream inputStream, Class targetType) throws Decod return decode(inputStream, targetType, UTF_8); } + default Object decode(InputStream inputStream, Type targetType) throws DecodeException { + return decode(inputStream, targetType, UTF_8); + } + default Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { return decode(inputStream, targetTypes, UTF_8); } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java index edeee5e01b4..32281439cc6 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java @@ -26,6 +26,7 @@ import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.List; @@ -64,6 +65,17 @@ public Object decode(InputStream is, Class targetType, Charset charset) throw } } + @Override + public Object decode(InputStream is, Type targetType, Charset charset) throws DecodeException { + try { + return JsonUtils.toJavaObject(StreamUtils.toString(is, charset), targetType); + } catch (HttpStatusException e) { + throw e; + } catch (Throwable t) { + throw new DecodeException("Error decoding json", t); + } + } + @Override public Object[] decode(InputStream is, Class[] targetTypes, Charset charset) throws DecodeException { try { diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java index a3b2dbdea4d..bb87ba882ed 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.Type; import java.nio.charset.Charset; import com.google.protobuf.Message; @@ -63,6 +64,13 @@ public Object decode(InputStream is, Class targetType, Charset charset) throw return super.decode(is, targetType, charset); } + @Override + public Object decode(InputStream is, Type targetType, Charset charset) throws DecodeException { + return targetType instanceof Class + ? decode(is, (Class) targetType, charset) + : super.decode(is, targetType, charset); + } + @Override public Object[] decode(InputStream is, Class[] targetTypes, Charset charset) throws DecodeException { try { diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java index 9362bcaf8b1..f22555ecfd3 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java @@ -18,6 +18,7 @@ import org.apache.dubbo.common.utils.ClassUtils; import org.apache.dubbo.common.utils.DefaultSerializeClassChecker; +import org.apache.dubbo.common.utils.JsonUtils; import org.apache.dubbo.remoting.http12.exception.DecodeException; import org.apache.dubbo.remoting.http12.exception.EncodeException; import org.apache.dubbo.remoting.http12.exception.HttpStatusException; @@ -28,6 +29,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.Iterator; @@ -51,6 +53,21 @@ public Object decode(InputStream is, Class targetType, Charset charset) throw } } + @Override + public Object decode(InputStream is, Type targetType, Charset charset) throws DecodeException { + if (targetType instanceof Class) { + return decode(is, (Class) targetType, charset); + } + + try (InputStreamReader reader = new InputStreamReader(is, charset)) { + return JsonUtils.convertObject(createYaml().load(reader), targetType); + } catch (HttpStatusException e) { + throw e; + } catch (Throwable t) { + throw new DecodeException("Error decoding yaml", t); + } + } + @Override public Object[] decode(InputStream is, Class[] targetTypes, Charset charset) throws DecodeException { try (InputStreamReader reader = new InputStreamReader(is, charset)) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.java index b50a7c3d982..22bf82a8a95 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.java @@ -21,6 +21,7 @@ import org.apache.dubbo.remoting.http12.message.MediaType; import java.io.InputStream; +import java.lang.reflect.Type; import java.nio.charset.Charset; public final class HttpMessageDecoderWrapper implements HttpMessageDecoder { @@ -43,6 +44,11 @@ public Object decode(InputStream inputStream, Class targetType, Charset chars return httpMessageDecoder.decode(inputStream, targetType, this.charset); } + @Override + public Object decode(InputStream inputStream, Type targetType, Charset charset) throws DecodeException { + return httpMessageDecoder.decode(inputStream, targetType, this.charset); + } + @Override public Object[] decode(InputStream inputStream, Class[] targetTypes, Charset charset) throws DecodeException { return httpMessageDecoder.decode(inputStream, targetTypes, this.charset); @@ -53,6 +59,11 @@ public Object decode(InputStream inputStream, Class targetType) throws Decode return httpMessageDecoder.decode(inputStream, targetType, charset); } + @Override + public Object decode(InputStream inputStream, Type targetType) throws DecodeException { + return httpMessageDecoder.decode(inputStream, targetType, charset); + } + @Override public Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { return httpMessageDecoder.decode(inputStream, targetTypes, charset); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/Messages.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/Messages.java index 2abcff12207..3ded19a4414 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/Messages.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/Messages.java @@ -34,9 +34,9 @@ public enum Messages { AMBIGUOUS_MAPPING("Ambiguous mapping for ''{0}'': [{1}, {2}]"), EXTENSION_INIT_FAILED("Rest extension: ''{0}'' initialization failed for invoker: ''{1}''"), ARGUMENT_NAME_MISSING("Name for argument of type [{0}] not specified, and parameter name information not " - + "available via reflection. Ensure that the compiler uses the '-parameters' flag."), + + "available via reflection. Ensure that the compiler uses the '-parameters' flag"), ARGUMENT_VALUE_MISSING("Missing argument ''{0}'' for method parameter of type [{1}]", 412), - ARGUMENT_CONVERT_ERROR("Convert argument ''{0}'' value [{1}] from type [{2}] to type [{3}] error", 412), + ARGUMENT_CONVERT_ERROR("Could not convert argument ''{0}'' value ''{1}'' from type [{2}] to type [{3}]", 412), ARGUMENT_COULD_NOT_RESOLVED("Could not resolve ''{0}'', no suitable resolver", 400), ARGUMENT_BIND_ERROR("Bind argument ''{0}'' of type [{1}] error", 400), INTERNAL_ERROR("Rest internal error"); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestHttpMessageCodec.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestHttpMessageCodec.java index 1451c8d2d10..cd6df6f4d69 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestHttpMessageCodec.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestHttpMessageCodec.java @@ -29,7 +29,9 @@ import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; @@ -73,7 +75,7 @@ public Object decode(InputStream inputStream, Class targetType, Charset chars @Override public Object[] decode(InputStream inputStream, Class[] targetTypes, Charset charset) throws DecodeException { - request.setInputStream(inputStream); + request.setInputStream(decodeInputStream(inputStream)); ParameterMeta[] parameters = this.parameters; int len = parameters.length; if (len == 0) { @@ -91,6 +93,19 @@ public void encode(OutputStream os, Object data, Charset charset) throws EncodeE encode(os, data); } + private InputStream decodeInputStream(InputStream is) { + if (is.getClass() == ByteArrayInputStream.class) { + return is; + } + try { + byte[] bytes = new byte[is.available()]; + is.read(bytes); + return new ByteArrayInputStream(bytes); + } catch (IOException e) { + throw new DecodeException(e); + } + } + @Override public void encode(OutputStream os, Object data) throws EncodeException { if (data != null) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/GeneralTypeConverter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/GeneralTypeConverter.java index 89dc6153f77..11d40e67b38 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/GeneralTypeConverter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/GeneralTypeConverter.java @@ -615,8 +615,9 @@ private Object doConvert(Object source, Class targetClass) throws Excepti try { return JsonUtils.convertObject(source, targetClass); - } catch (Exception e) { - LOGGER.debug("JSON convert from [{}] to [{}] failed", sourceClass.getName(), targetClass.getName(), e); + } catch (Throwable t) { + String msg = "JSON convert value '{}' from type [{}] to type [{}] failed"; + LOGGER.debug(msg, source, sourceClass, targetClass, t); } return null; @@ -660,6 +661,20 @@ private Object doConvert(Object source, Type targetType) throws Exception { if (Collection.class.isAssignableFrom(targetClass)) { Type itemType = getActualGenericType(argTypes[0]); + if (itemType instanceof Class && targetClass.isInstance(source)) { + boolean same = true; + Class itemClass = (Class) itemType; + for (Object item : (Collection) source) { + if (item != null && !itemClass.isInstance(item)) { + same = false; + break; + } + } + if (same) { + return source; + } + } + Collection items = toCollection(source); Collection targetItems = createCollection(targetClass, items.size()); for (Object item : items) { @@ -671,6 +686,28 @@ private Object doConvert(Object source, Type targetType) throws Exception { if (Map.class.isAssignableFrom(targetClass)) { Type keyType = argTypes[0]; Type valueType = argTypes[1]; + + if (keyType instanceof Class && valueType instanceof Class && targetClass.isInstance(source)) { + boolean same = true; + Class keyClass = (Class) keyType; + Class valueClass = (Class) valueType; + for (Map.Entry entry : ((Map) source).entrySet()) { + Object key = entry.getKey(); + if (key != null && !keyClass.isInstance(key)) { + same = false; + break; + } + Object value = entry.getValue(); + if (value != null && !valueClass.isInstance(value)) { + same = false; + break; + } + } + if (same) { + return source; + } + } + Class mapValueClass = TypeUtils.getMapValueType(targetClass); boolean multiValue = mapValueClass != null && Collection.class.isAssignableFrom(mapValueClass); @@ -725,9 +762,9 @@ private Object doConvert(Object source, Type targetType) throws Exception { try { return JsonUtils.convertObject(source, targetType); - } catch (Exception e) { - String name = source.getClass().getName(); - LOGGER.debug("JSON convert from [{}] to [{}] failed", name, targetType.getTypeName(), e); + } catch (Throwable t) { + String msg = "JSON convert value '{}' from type [{}] to type [{}] failed"; + LOGGER.debug(msg, source, source.getClass(), targetType, t); } return null; @@ -1099,7 +1136,7 @@ private Object jsonToObject(String value, Type targetType) { .determineHttpMessageDecoder(MediaType.APPLICATION_JSON.getName()) .decode(new ByteArrayInputStream(value.getBytes(UTF_8)), (Class) targetType); } catch (Throwable t) { - LOGGER.debug("Failed to parse [{}] from json string [{}]", targetType, value, t); + LOGGER.debug("Failed to parse value '{}' from json string '{}'", targetType, value, t); } } return null; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java index d7401c57cf6..80deb647b11 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java @@ -26,11 +26,9 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; -import java.lang.reflect.Type; import java.util.Collection; import java.util.Collections; import java.util.Map; -import java.util.Optional; public abstract class NamedValueArgumentResolverSupport { @@ -64,18 +62,13 @@ protected final NamedValueMeta updateNamedValueMeta(ParameterMeta parameterMeta, meta.setName(parameterMeta.getRequiredName()); } - Class type = parameterMeta.getType(); - Type genericType = parameterMeta.getGenericType(); - if (type == Optional.class) { - Class actualType = TypeUtils.getNestedActualType(genericType, 0); - meta.setType(actualType == null ? Object.class : actualType); - } else { - meta.setType(type); - } + Class type = parameterMeta.getActualType(); + meta.setType(type); + meta.setGenericType(parameterMeta.getActualGenericType()); if (type.isArray()) { - meta.setNestedTypes(new Class[] {type.getComponentType()}); + meta.setNestedTypes(new Class[] {type}); } else { - meta.setNestedTypes(TypeUtils.getNestedActualTypes(genericType)); + meta.setNestedTypes(TypeUtils.getNestedActualTypes(meta.genericType())); } meta.setParameterMeta(parameterMeta); return meta; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 17edec21012..4bc9a3f9b7a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -19,6 +19,9 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.ConfigurationUtils; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.config.nested.RestConfig; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.message.MethodMetadata; @@ -29,6 +32,7 @@ import org.apache.dubbo.rpc.model.ReflectionServiceDescriptor; import org.apache.dubbo.rpc.model.ServiceDescriptor; import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils; +import org.apache.dubbo.rpc.protocol.tri.rest.Messages; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import org.apache.dubbo.rpc.protocol.tri.rest.RestInitializeException; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RadixTree.Match; @@ -53,6 +57,8 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegistry { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRequestMappingRegistry.class); + private final FrameworkModel frameworkModel; private final ContentNegotiator contentNegotiator; private final ReadWriteLock lock = new ReentrantReadWriteLock(); @@ -100,6 +106,10 @@ public void register(Invoker invoker) { new MethodWalker().walk(service.getClass(), (classes, consumer) -> { for (int i = 0, size = resolvers.size(); i < size; i++) { RequestMappingResolver resolver = resolvers.get(i); + if (LOGGER.isInfoEnabled()) { + String name = resolver.getClass().getSimpleName(); + LOGGER.info("{} resolve rest mappings from [{}]", name, CollectionUtils.first(classes)); + } ServiceMeta serviceMeta = new ServiceMeta(classes, sd, service, url, resolver.getRestToolKit()); if (!resolver.accept(serviceMeta)) { continue; @@ -142,7 +152,12 @@ private void register0(RequestMapping mapping, HandlerMeta handler) { registration.mapping = mapping; registration.meta = handler; for (PathExpression path : mapping.getPathCondition().getExpressions()) { - tree.addPath(path, registration); + Registration exists = tree.addPath(path, registration); + if (exists == null) { + LOGGER.debug("Register rest mapping path: '" + path + "' -> " + mapping); + } else { + LOGGER.warn(Messages.DUPLICATE_MAPPING.format(path, mapping, exists.mapping)); + } } } finally { lock.writeLock().unlock(); @@ -232,6 +247,9 @@ public HandlerMeta lookup(HttpRequest request) { } return c1.variableMap.size() - c2.variableMap.size(); }); + + LOGGER.debug("Candidate rest mappings: {}", candidates); + Candidate first = candidates.get(0); Candidate second = candidates.get(1); if (first.mapping.compareTo(second.mapping, request) == 0) { @@ -246,6 +264,8 @@ public HandlerMeta lookup(HttpRequest request) { request.setAttribute(RestConstants.MAPPING_ATTRIBUTE, mapping); request.setAttribute(RestConstants.HANDLER_ATTRIBUTE, handler); + LOGGER.debug("Matched rest mapping={}, method={}", mapping, handler.getMethod()); + if (!winner.variableMap.isEmpty()) { request.setAttribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE, winner.variableMap); } @@ -368,5 +388,10 @@ private static final class Candidate { HandlerMeta meta; PathExpression expression; Map variableMap; + + @Override + public String toString() { + return "Candidate{mapping=" + mapping + ", method=" + meta.getMethod() + '}'; + } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index bd84d96f3e4..5e8171b8984 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -29,6 +29,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ProducesCondition; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ResponseMeta; @@ -420,6 +421,13 @@ public Builder custom(Condition customCondition) { return this; } + public Builder service(String ServiceGroup, String ServiceVersion) { + if (ServiceGroup != null || ServiceVersion != null) { + customCondition = new ServiceVersionCondition(ServiceGroup, ServiceVersion); + } + return this; + } + public Builder cors(CorsMeta cors) { this.cors = cors; return this; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java index e04b9794de1..ea7158be124 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java @@ -19,6 +19,8 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.beans.factory.ScopeBeanFactory; import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; @@ -41,6 +43,8 @@ @Activate(order = -2000) public final class RestRequestHandlerMapping implements RequestHandlerMapping { + private static final Logger LOGGER = LoggerFactory.getLogger(RestRequestHandlerMapping.class); + private final FrameworkModel frameworkModel; private final RequestMappingRegistry requestMappingRegistry; private final ArgumentResolver argumentResolver; @@ -60,6 +64,8 @@ public RestRequestHandlerMapping(FrameworkModel frameworkModel) { @Override public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) { + LOGGER.debug("Received http request: {}", request); + HandlerMeta meta = requestMappingRegistry.lookup(request); if (meta == null) { return null; @@ -93,6 +99,8 @@ public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpRespon codecUtils.determineHttpMessageDecoder(url, frameworkModel, requestMediaType)); } + LOGGER.debug("Content-type negotiate result: request='{}', response='{}'", requestMediaType, responseMediaType); + RequestHandler handler = new RequestHandler(meta.getInvoker()); handler.setHasStub(false); handler.setMethodDescriptor(meta.getMethodDescriptor()); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java index f00a26d57df..f1675c0a581 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java @@ -51,10 +51,10 @@ public ServiceVersionCondition match(HttpRequest request) { if (version == null) { version = request.header(RestConstants.HEADER_SERVICE_VERSION); } - if (version != null && !version.equals(this.version)) { return null; } + return this; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationSupport.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationSupport.java index 35592ce137f..c93a164ed86 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationSupport.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationSupport.java @@ -61,7 +61,7 @@ public final AnnotationMeta[] getAnnotations() { }); } - public final Annotation[] getRealAnnotations() { + public final Annotation[] getRawAnnotations() { AnnotationMeta[] annotations = getAnnotations(); int len = annotations.length; Annotation[] result = new Annotation[len]; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/BeanMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/BeanMeta.java index ce8fd206e19..6326ff1fc44 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/BeanMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/BeanMeta.java @@ -352,7 +352,7 @@ public String getDescription() { } } - public static final class NestedMeta extends NestableParameterMeta { + private static final class NestedMeta extends NestableParameterMeta { private final Class type; private final Type genericType; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodMeta.java index ce3e2350d66..6e8fe11427a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodMeta.java @@ -16,7 +16,11 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta; +import org.apache.dubbo.common.utils.MethodUtils; import org.apache.dubbo.rpc.model.MethodDescriptor; +import org.apache.dubbo.rpc.model.MethodDescriptor.RpcType; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; +import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; @@ -50,7 +54,14 @@ private Method initMethod(List hierarchy, MethodDescriptor methodDescrip } public void initParameters() { - int count = method.getParameterCount(); + RpcType rpcType = methodDescriptor.getRpcType(); + if (rpcType == RpcType.CLIENT_STREAM || rpcType == RpcType.BI_STREAM) { + Type genericType = TypeUtils.getNestedGenericType(method.getGenericReturnType(), 0); + parameters = new ParameterMeta[] {new StreamParameterMeta(getToolKit(), genericType, method, hierarchy)}; + return; + } + + int count = rpcType == RpcType.SERVER_STREAM ? 1 : method.getParameterCount(); List> parameterHierarchies = new ArrayList<>(count); for (int i = 0, len = hierarchy.size(); i < len; i++) { Method m = hierarchy.get(i); @@ -66,7 +77,6 @@ public void initParameters() { parameterHierarchy.add(mps[j]); } } - String[] parameterNames = getToolKit().getParameterNames(method); ParameterMeta[] parameters = new ParameterMeta[count]; for (int i = 0; i < count; i++) { @@ -136,6 +146,51 @@ public boolean equals(Object obj) { @Override public String toString() { - return "MethodMeta{method=" + method + '}'; + return "MethodMeta{" + MethodUtils.toShortString(method) + '}'; + } + + private static final class StreamParameterMeta extends ParameterMeta { + + private final Class type; + private final Type genericType; + private final AnnotatedElement element; + private final List elements; + + StreamParameterMeta( + RestToolKit toolKit, + Type genericType, + AnnotatedElement element, + List elements) { + super(toolKit, "value"); + type = TypeUtils.getActualType(genericType); + this.genericType = genericType; + this.element = element; + this.elements = elements; + } + + @Override + public String getDescription() { + return "Stream parameter [" + element + "]"; + } + + @Override + public Class getType() { + return type; + } + + @Override + public Type getGenericType() { + return genericType; + } + + @Override + protected AnnotatedElement getAnnotatedElement() { + return element; + } + + @Override + protected List getAnnotatedElements() { + return elements; + } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodParameterMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodParameterMeta.java index 845acf7d600..e9b82cf4828 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodParameterMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodParameterMeta.java @@ -16,9 +16,6 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta; -import org.apache.dubbo.rpc.model.MethodDescriptor; -import org.apache.dubbo.rpc.model.MethodDescriptor.RpcType; - import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.lang.reflect.Parameter; @@ -48,12 +45,6 @@ public Parameter getParameter() { return parameter; } - @Override - public boolean isSingle() { - MethodDescriptor methodDescriptor = methodMeta.getMethodDescriptor(); - return methodDescriptor.getRpcType() != RpcType.UNARY || methodDescriptor.getParameterClasses().length == 1; - } - @Override public int getIndex() { return index; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java index 04c77875b8e..1713668acca 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java @@ -16,6 +16,7 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta; +import java.lang.reflect.Type; import java.util.Arrays; public class NamedValueMeta { @@ -24,6 +25,7 @@ public class NamedValueMeta { private final boolean required; private final String defaultValue; private Class type; + private Type genericType; private Class[] nestedTypes; private ParameterMeta parameterMeta; @@ -63,6 +65,14 @@ public void setType(Class type) { this.type = type; } + public Type genericType() { + return genericType; + } + + public void setGenericType(Type genericType) { + this.genericType = genericType; + } + public Class nestedType() { return nestedTypes == null ? null : nestedTypes[0]; } @@ -99,6 +109,9 @@ public String toString() { } if (type != null) { sb.append(", type=").append(type); + if (genericType != type) { + sb.append(", genericType=").append(genericType); + } } if (nestedTypes != null) { sb.append(", nestedTypes=").append(Arrays.toString(nestedTypes)); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java index 2def7071227..33f02fdb311 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java @@ -25,6 +25,7 @@ import javax.annotation.Nullable; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection; import java.util.Optional; @@ -35,7 +36,7 @@ public abstract class ParameterMeta extends AnnotationSupport { private final String name; private Boolean simple; private Class actualType; - private Object typeDescriptor; + private Type actualGenericType; protected ParameterMeta(RestToolKit toolKit, String prefix, String name) { super(toolKit); @@ -93,22 +94,25 @@ public final Class getActualType() { return type; } - public final Object getTypeDescriptor() { - return typeDescriptor; - } - - public final void setTypeDescriptor(Object typeDescriptor) { - this.typeDescriptor = typeDescriptor; + public final Type getActualGenericType() { + Type type = actualGenericType; + if (type == null) { + type = getGenericType(); + if (type instanceof ParameterizedType && ((ParameterizedType) type).getRawType() == Optional.class) { + type = TypeUtils.getNestedGenericType(getGenericType(), 0); + if (type == null) { + type = Object.class; + } + } + actualGenericType = type; + } + return type; } public final Object bind(HttpRequest request, HttpResponse response) { return getToolKit().bind(this, request, response); } - public boolean isSingle() { - return false; - } - public int getIndex() { return -1; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java index db39ea43f17..7e2fc07d595 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java @@ -23,7 +23,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver; -import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; @@ -80,7 +79,7 @@ public RequestMapping resolve(MethodMeta methodMeta) { String[] paths = getPaths(mapping); if (paths.length == 0) { - builder.path(method.getName()).sig(TypeUtils.buildSig(method)); + builder.path('/' + method.getName()).sig(TypeUtils.buildSig(method)); } else { builder.path(paths); } @@ -90,7 +89,7 @@ public RequestMapping resolve(MethodMeta methodMeta) { globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); } return builder.name(method.getName()) - .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) + .service(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion()) .cors(globalCorsMeta) .build(); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java index ab4cbd97a64..ef788d60848 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java @@ -20,8 +20,15 @@ import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.rest.Param; +import org.apache.dubbo.remoting.http12.rest.ParamType; +import org.apache.dubbo.rpc.model.MethodDescriptor.RpcType; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import org.apache.dubbo.rpc.protocol.tri.rest.argument.AbstractArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodParameterMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.NamedValueMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; @@ -40,7 +47,21 @@ public boolean accept(ParameterMeta param) { @Override protected NamedValueMeta createNamedValueMeta(ParameterMeta param) { - return new NamedValueMeta(param.isAnnotated(Annotations.Nonnull), null); + boolean noBodyParam = true; + int paramCount = -1; + if (param instanceof MethodParameterMeta) { + MethodMeta methodMeta = ((MethodParameterMeta) param).getMethodMeta(); + ParameterMeta[] paramMetas = methodMeta.getParameters(); + for (ParameterMeta paramMeta : paramMetas) { + AnnotationMeta anno = paramMeta.findAnnotation(Param.class); + if (anno != null && anno.getAnnotation().type() == ParamType.Body) { + noBodyParam = false; + break; + } + } + paramCount = methodMeta.getMethodDescriptor().getRpcType() != RpcType.UNARY ? 1 : paramMetas.length; + } + return new FallbackNamedValueMeta(param.isAnnotated(Annotations.Nonnull), noBodyParam, paramCount); } @Override @@ -54,17 +75,24 @@ protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request } protected Object doResolveValue(NamedValueMeta meta, boolean single, HttpRequest request, HttpResponse response) { - ParameterMeta parameter = meta.parameterMeta(); + FallbackNamedValueMeta fm = (FallbackNamedValueMeta) meta; + if (HttpMethods.supportBody(request.method())) { - Object body = RequestUtils.decodeBody(request, parameter.getType(), parameter.isSingle()); - if (body != null) { - if (parameter.getType().isInstance(body)) { - return body; - } else if (body instanceof List) { + if (fm.paramCount == 1) { + try { + Object body = RequestUtils.decodeBody(request, meta.genericType()); + if (body != null) { + return body; + } + } catch (DecodeException ignored) { + } + } + if (fm.noBodyParam) { + Object body = RequestUtils.decodeBodyAsObject(request); + if (body instanceof List) { List list = (List) body; - int index = parameter.getIndex(); - if (index >= 0 && list.size() > index) { - return list.get(index); + if (list.size() == fm.paramCount) { + return list.get(meta.parameterMeta().getIndex()); } } else if (body instanceof Map) { Object value = ((Map) body).get(meta.name()); @@ -74,13 +102,15 @@ protected Object doResolveValue(NamedValueMeta meta, boolean single, HttpRequest } } } + if (single) { String value = request.parameter(meta.name()); - if (parameter.isSimple() || RestUtils.isMaybeJSONObject(value)) { + if (meta.parameterMeta().isSimple() || RestUtils.isMaybeJSONObject(value)) { return value; } - return parameter.bind(request, response); + return meta.parameterMeta().bind(request, response); } + return request.parameterValues(meta.name()); } @@ -88,4 +118,16 @@ protected Object doResolveValue(NamedValueMeta meta, boolean single, HttpRequest protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { return resolveValue(meta, request, response); } + + private static class FallbackNamedValueMeta extends NamedValueMeta { + + private final boolean noBodyParam; + private final int paramCount; + + FallbackNamedValueMeta(boolean required, boolean noBodyParam, int paramCount) { + super(required, null); + this.noBodyParam = noBodyParam; + this.paramCount = paramCount; + } + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/ParamArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/ParamArgumentResolver.java index 52456c898c0..264df7d3341 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/ParamArgumentResolver.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/ParamArgumentResolver.java @@ -84,7 +84,7 @@ protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResp if (RequestUtils.isFormOrMultiPart(request)) { return request.formParameter(meta.name()); } - return RequestUtils.decodeBody(request, meta.type()); + return RequestUtils.decodeBody(request, meta.genericType()); } return null; } @@ -135,7 +135,7 @@ protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request if (RequestUtils.isFormOrMultiPart(request)) { return request.formParameterValues(meta.name()); } - return RequestUtils.decodeBody(request, meta.type()); + return RequestUtils.decodeBody(request, meta.genericType()); } return null; } @@ -179,7 +179,7 @@ protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpR if (RequestUtils.isFormOrMultiPart(request)) { return RequestUtils.getFormParametersMap(request); } - return RequestUtils.decodeBody(request, meta.type()); + return RequestUtils.decodeBody(request, meta.genericType()); } return null; } @@ -209,7 +209,7 @@ private static class ParamNamedValueMeta extends NamedValueMeta { private final ParamType paramType; - public ParamNamedValueMeta(String name, boolean required, String defaultValue, ParamType paramType) { + ParamNamedValueMeta(String name, boolean required, String defaultValue, ParamType paramType) { super(name, required, defaultValue); this.paramType = paramType; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java index c35cd414d5a..7ddb719c583 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java @@ -19,6 +19,8 @@ import org.apache.dubbo.common.config.Environment; import org.apache.dubbo.common.utils.AnnotationUtils; import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.Messages; +import org.apache.dubbo.rpc.protocol.tri.rest.RestException; import org.apache.dubbo.rpc.protocol.tri.rest.argument.GeneralTypeConverter; import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; @@ -50,7 +52,16 @@ private Environment getEnvironment() { @Override public Object convert(Object value, ParameterMeta parameter) { - return typeConverter.convert(value, parameter.getGenericType()); + Object target = typeConverter.convert(value, parameter.getGenericType()); + if (target == null && value != null) { + throw new RestException( + Messages.ARGUMENT_CONVERT_ERROR, + parameter.getName(), + value, + value.getClass(), + parameter.getGenericType()); + } + return target; } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java index 61413c0633c..ccb7e95fdcf 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java @@ -54,9 +54,6 @@ private void walkHierarchy(Class clazz) { for (Method method : clazz.getDeclaredMethods()) { int modifiers = method.getModifiers(); if ((modifiers & (Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC) { - if (Modifier.isAbstract(modifiers) && method.getDeclaredAnnotations().length == 0) { - continue; - } methodsMap .computeIfAbsent(Key.of(method), k -> new ArrayList<>()) .add(method); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java index b75561a0eeb..45d4cde2f05 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java @@ -25,10 +25,10 @@ import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -169,92 +169,60 @@ public static List parseMatrixVariableValues(Map variabl return result == null ? Collections.emptyList() : result; } - public static Object decodeBody(HttpRequest request, Class type) { + public static Object decodeBody(HttpRequest request, Type type) { HttpMessageDecoder decoder = request.attribute(RestConstants.BODY_DECODER_ATTRIBUTE); if (decoder == null) { return null; } - Object value = request.attribute(RestConstants.BODY_ATTRIBUTE); - if (value == null) { - if (decoder.mediaType().isPureText()) { - type = String.class; - } - InputStream input = request.inputStream(); - try { - int available = input.available(); - if (available == 0) { - return emptyDefault(type); - } - } catch (IOException e) { - throw new DecodeException("Error reading input", e); - } - - value = decoder.decode(input, type, request.charsetOrDefault()); - request.setAttribute(RestConstants.BODY_ATTRIBUTE, value); - } - return value; - } - - public static Object decodeBody(HttpRequest request, Class type, boolean single) { - HttpMessageDecoder decoder = request.attribute(RestConstants.BODY_DECODER_ATTRIBUTE); - if (decoder == null) { - return null; - } - MediaType mediaType = decoder.mediaType(); - if (mediaType == MediaType.APPLICATION_FROM_URLENCODED || mediaType == MediaType.MULTIPART_FORM_DATA) { - return null; + if (decoder.mediaType().isPureText()) { + type = String.class; } + InputStream is = request.inputStream(); try { - InputStream input = request.inputStream(); - if (single) { - if (mediaType.isPureText()) { - type = String.class; - } - int available = input.available(); - if (available == 0) { - return emptyDefault(type); - } - if (!input.markSupported()) { - byte[] bytes = new byte[available]; - input.read(bytes); - input = new ByteArrayInputStream(bytes); - } - try { - return decoder.decode(input, type, request.charsetOrDefault()); - } catch (Throwable t) { - input.reset(); + int available = is.available(); + if (available == 0) { + if (type instanceof Class) { + Class clazz = (Class) type; + if (clazz == String.class) { + return StringUtils.EMPTY_STRING; + } + if (clazz == byte[].class) { + return new byte[0]; + } } + return null; } + } catch (IOException e) { + throw new DecodeException("Error reading is", e); + } - Object value = request.attribute(RestConstants.BODY_ATTRIBUTE); - if (value == null) { - if (mediaType == MediaType.APPLICATION_JSON || mediaType == MediaType.APPLICATION_YAML) { - type = Object.class; - } else if (mediaType.isPureText()) { - type = String.class; + boolean canMark = is.markSupported(); + try { + if (canMark) { + is.mark(Integer.MAX_VALUE); + } + return decoder.decode(is, type, request.charsetOrDefault()); + } finally { + try { + if (canMark) { + is.reset(); } else { - return null; - } - int available = input.available(); - if (available != 0) { - value = decoder.decode(input, type, request.charsetOrDefault()); + is.close(); } - request.setAttribute(RestConstants.BODY_ATTRIBUTE, value); + } catch (IOException ignored) { } - return value; - } catch (IOException e) { - throw new DecodeException("Error reading input", e); } } - private static Object emptyDefault(Class type) { - if (type == String.class) { - return StringUtils.EMPTY_STRING; - } - if (type == byte[].class) { - return new byte[0]; + public static Object decodeBodyAsObject(HttpRequest request) { + Object value = request.attribute(RestConstants.BODY_ATTRIBUTE); + if (value == null) { + value = decodeBody(request, Object.class); + if (value != null) { + request.setAttribute(RestConstants.BODY_ATTRIBUTE, value); + } } - return null; + return value; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy index 23692bc0207..572adc6f09f 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy @@ -51,6 +51,24 @@ class RestProtocolTest extends BaseServiceTest { '/argTest?name=Sam' | '{"age": 8}' | 'Sam is 8 years old' } + def "list argument body test"() { + expect: + runner.post(path, body) contains output + where: + path | body | output + '/listArgBodyTest?age=2' | '[1,2]' | '[1,2]' + '/listArgBodyTest2?age=1' | '[[1,2],2]' | '[1,2]' + } + + def "map argument body test"() { + expect: + runner.post(path, body) contains output + where: + path | body | output + '/mapArgBodyTest?age=2' | '{"1":["2",3],"4":[5,"6"]}' | '{4:[5,6],1:[2,3]}' + '/mapArgBodyTest2?age=1' | '[{"1":[2,3],"4":[5,6]},2]' | '{4:[5,6],1:[2,3]}' + } + def "bean argument test"() { expect: runner.post(path, body, Book.class).name == output @@ -77,12 +95,14 @@ class RestProtocolTest extends BaseServiceTest { expect: runner.post(path, body, Book.class).price == output where: - path | body | output - '/beanArgTest' | [] | 0 - '/beanArgTest' | [quote: 5] | 5 - '/beanArgTest' | [book: new Book(price: 6)] | 6 - '/beanArgTest' | [book: new Book(price: 6), quote: 5] | 5 - '/beanArgTest' | [price: 6, quote: 5] | 5 + path | body | output + '/beanArgTest' | [] | 0 + '/beanArgTest' | [quote: 5] | 5 + '/beanArgTest' | [book: new Book(price: 6)] | 6 + '/beanArgTest' | [book: new Book(price: 6), quote: 5] | 5 + '/beanArgTest' | [price: 6, quote: 5] | 5 + '/beanArgTest2' | '{"price": 5}' | 5 + '/beanArgTest2' | '[{"price": 5}]' | 5 } def "advance bean argument get test"() { @@ -116,6 +136,15 @@ class RestProtocolTest extends BaseServiceTest { '/bean?groupsMap.one[0].name=a&groupsMap.one[1].name=b&groupsMap.two[1].name=c' | '"groupsMap":{"one":[{"id":0,"name":"a"},{"id":0,"name":"b"}],"two":[null,{"id":0,"name":"c"}]}' } + def "bean body test"() { + expect: + runner.post(path, body) contains output + where: + path | body | output + '/beanBodyTest?age=2' | '[{"id":1,"name":"g1"},{"id":2,"name":"g2"}]' | '[{"id":1,"name":"g1"},{"id":2,"name":"g2"}]' + '/beanBodyTest2?age=1' | '[[{"id":1,"name":"g1"},{"id":2,"name":"g2"}],2]' | '[{"id":1,"name":"g1"},{"id":2,"name":"g2"}]' + } + def "urlEncodeForm test"() { given: def request = new TestRequest( diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoService.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoService.java index cb3cc8f1da5..732fcde91ed 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoService.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoService.java @@ -18,11 +18,18 @@ import org.apache.dubbo.common.stream.StreamObserver; import org.apache.dubbo.remoting.http12.rest.Mapping; +import org.apache.dubbo.remoting.http12.rest.Param; +import org.apache.dubbo.rpc.protocol.tri.rest.service.User.Group; import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx; +import java.util.List; +import java.util.Map; + import io.grpc.health.v1.HealthCheckRequest; import io.grpc.health.v1.HealthCheckResponse; +import static org.apache.dubbo.remoting.http12.rest.ParamType.Body; + @Mapping("/") public interface DemoService { @@ -32,9 +39,23 @@ public interface DemoService { Book beanArgTest(Book book, Integer quote); + Book beanArgTest2(Book book); + @Mapping("/bean") UserEx advanceBeanArgTest(UserEx user); + List listArgBodyTest(@Param(type = Body) List list, int age); + + List listArgBodyTest2(List list, int age); + + Map> mapArgBodyTest(@Param(type = Body) Map> map, int age); + + Map> mapArgBodyTest2(Map> map, int age); + + List beanBodyTest(@Param(type = Body) List groups, int age); + + List beanBodyTest2(List groups, int age); + Book buy(Book book); @Mapping("/buy2") diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoServiceImpl.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoServiceImpl.java index d17e71a00dd..39c2f9795ae 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoServiceImpl.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoServiceImpl.java @@ -17,10 +17,15 @@ package org.apache.dubbo.rpc.protocol.tri.rest.service; import org.apache.dubbo.common.stream.StreamObserver; +import org.apache.dubbo.common.utils.Assert; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.rest.Mapping; +import org.apache.dubbo.rpc.protocol.tri.rest.service.User.Group; import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx; +import java.util.List; +import java.util.Map; + import io.grpc.health.v1.HealthCheckRequest; import io.grpc.health.v1.HealthCheckResponse; import io.grpc.health.v1.HealthCheckResponse.ServingStatus; @@ -48,11 +53,49 @@ public Book beanArgTest(Book book, Integer quote) { return book; } + @Override + public Book beanArgTest2(Book book) { + return beanArgTest(book, null); + } + @Override public UserEx advanceBeanArgTest(UserEx user) { return user; } + @Override + public List listArgBodyTest(List list, int age) { + Assert.assertTrue(age == 2, "age must be 2"); + return list; + } + + @Override + public List listArgBodyTest2(List list, int age) { + return listArgBodyTest(list, age); + } + + @Override + public Map> mapArgBodyTest(Map> map, int age) { + Assert.assertTrue(age == 2, "age must be 2"); + return map; + } + + @Override + public Map> mapArgBodyTest2(Map> map, int age) { + return mapArgBodyTest(map, age); + } + + @Override + public List beanBodyTest(List groups, int age) { + Assert.assertTrue(age == 2, "age must be 2"); + return groups; + } + + @Override + public List beanBodyTest2(List groups, int age) { + return beanBodyTest(groups, age); + } + @Override public Book buy(Book book) { return book; From 6dd81e6c0f14f4bddefab700c023b18816aaad51 Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Thu, 25 Jul 2024 22:56:02 +0800 Subject: [PATCH 2/3] OPTIONS request support --- .../dubbo/remoting/http12/HttpMethods.java | 4 +-- .../dubbo/remoting/http12/rest/Mapping.java | 2 ++ .../tri/rest/mapping/RequestMapping.java | 4 +++ .../mapping/RestRequestHandlerMapping.java | 32 ++++++++++++++++++- .../mapping/condition/MethodsCondition.java | 15 ++++++--- .../basic/BasicRequestMappingResolver.java | 12 +++++-- 6 files changed, 58 insertions(+), 11 deletions(-) diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java index 8cdf0e9f735..4792ccd9bdb 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java @@ -20,12 +20,12 @@ public enum HttpMethods { GET, + HEAD, POST, PUT, + PATCH, DELETE, - HEAD, OPTIONS, - PATCH, TRACE; public static final byte[][] HTTP_METHODS_BYTES; diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java index a05e479306d..0aea2292ed4 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java @@ -42,4 +42,6 @@ String[] consumes() default {}; String[] produces() default {}; + + boolean disabled() default false; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java index 5e8171b8984..99abe12fc0c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -204,6 +204,10 @@ public PathCondition getPathCondition() { return pathCondition; } + public MethodsCondition getMethodsCondition() { + return methodsCondition; + } + public ProducesCondition getProducesCondition() { return producesCondition; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java index ea7158be124..1fe540af6d0 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java @@ -25,6 +25,9 @@ import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.HttpResult; +import org.apache.dubbo.remoting.http12.HttpStatus; +import org.apache.dubbo.remoting.http12.exception.HttpResultPayloadException; import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.remoting.http12.message.codec.CodecUtils; import org.apache.dubbo.rpc.model.FrameworkModel; @@ -35,11 +38,14 @@ import org.apache.dubbo.rpc.protocol.tri.rest.argument.CompositeArgumentResolver; import org.apache.dubbo.rpc.protocol.tri.rest.argument.GeneralTypeConverter; import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.MethodsCondition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; import org.apache.dubbo.rpc.protocol.tri.route.RequestHandler; import org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping; +import java.util.Set; + @Activate(order = -2000) public final class RestRequestHandlerMapping implements RequestHandlerMapping { @@ -71,6 +77,11 @@ public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpRespon return null; } + String method = request.method(); + if (HttpMethods.OPTIONS.name().equals(method)) { + handleOptionsRequest(request); + } + String requestMediaType = request.mediaType(); String responseMediaType = contentNegotiator.negotiate(request); if (responseMediaType != null) { @@ -90,7 +101,7 @@ public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpRespon typeConverter, codecUtils.determineHttpMessageEncoder(url, frameworkModel, responseMediaType)); - if (HttpMethods.supportBody(request.method()) && !RequestUtils.isFormOrMultiPart(request)) { + if (HttpMethods.supportBody(method) && !RequestUtils.isFormOrMultiPart(request)) { if (StringUtils.isEmpty(requestMediaType)) { requestMediaType = responseMediaType; } @@ -111,6 +122,25 @@ public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpRespon return handler; } + private static void handleOptionsRequest(HttpRequest request) { + RequestMapping mapping = request.attribute(RestConstants.MAPPING_ATTRIBUTE); + MethodsCondition condition = mapping.getMethodsCondition(); + if (condition == null) { + throw new HttpResultPayloadException(HttpResult.builder() + .status(HttpStatus.NO_CONTENT) + .header("allow", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS") + .build()); + } + Set methods = condition.getMethods(); + if (methods.size() == 1 && methods.contains(HttpMethods.OPTIONS.name())) { + return; + } + throw new HttpResultPayloadException(HttpResult.builder() + .status(HttpStatus.NO_CONTENT) + .header("allow", StringUtils.join(methods, ",")) + .build()); + } + @Override public String getType() { return TripleConstant.TRIPLE_HANDLER_TYPE_REST; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java index 96b8eec3c49..1790d50f61d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java @@ -52,17 +52,22 @@ public MethodsCondition combine(MethodsCondition other) { @Override public MethodsCondition match(HttpRequest request) { String method = request.method(); + + if (OPTIONS.name().equals(method)) { + if (request.hasHeader("origin") && request.hasHeader("access-control-request-method")) { + return new MethodsCondition(OPTIONS.name()); + } else { + return this; + } + } + if (methods.contains(method)) { return new MethodsCondition(method); } + if (HEAD.name().equals(method) && methods.contains(GET.name())) { return new MethodsCondition(GET.name()); } - if (OPTIONS.name().equals(method) - && request.hasHeader("origin") - && request.hasHeader("access-control-request-method")) { - return new MethodsCondition(OPTIONS.name()); - } return null; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java index 7e2fc07d595..7c6377f8737 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java @@ -18,6 +18,7 @@ import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.rest.Mapping; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; @@ -51,12 +52,13 @@ public RestToolKit getRestToolKit() { @Override public boolean accept(MethodMeta methodMeta) { - return methodMeta.getMethodDescriptor() != null || methodMeta.findAnnotation(Annotations.Mapping) != null; + AnnotationMeta mapping = methodMeta.findAnnotation(Mapping.class); + return mapping != null ? !mapping.getAnnotation().disabled() : methodMeta.getMethodDescriptor() != null; } @Override public RequestMapping resolve(ServiceMeta serviceMeta) { - AnnotationMeta mapping = serviceMeta.findAnnotation(Annotations.Mapping); + AnnotationMeta mapping = serviceMeta.findAnnotation(Mapping.class); Builder builder = builder(mapping); String[] paths = getPaths(mapping); @@ -74,7 +76,11 @@ public RequestMapping resolve(ServiceMeta serviceMeta) { @Override public RequestMapping resolve(MethodMeta methodMeta) { Method method = methodMeta.getMethod(); - AnnotationMeta mapping = methodMeta.findAnnotation(Annotations.Mapping); + AnnotationMeta mapping = methodMeta.findAnnotation(Mapping.class); + if (mapping != null && mapping.getAnnotation().disabled()) { + return null; + } + Builder builder = builder(mapping); String[] paths = getPaths(mapping); From f62f3972232073a28961296e5e017ab514a4c56e Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Thu, 25 Jul 2024 22:59:42 +0800 Subject: [PATCH 3/3] Fix yml constructor --- .../remoting/http12/message/codec/YamlCodec.java | 2 +- .../rest/mapping/DefaultRequestMappingRegistry.java | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java index f22555ecfd3..01cce80e436 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java @@ -71,7 +71,7 @@ public Object decode(InputStream is, Type targetType, Charset charset) throws De @Override public Object[] decode(InputStream is, Class[] targetTypes, Charset charset) throws DecodeException { try (InputStreamReader reader = new InputStreamReader(is, charset)) { - Yaml yaml = new Yaml(); + Yaml yaml = createYaml(); Iterator iterator = yaml.loadAll(reader).iterator(); int len = targetTypes.length; Object[] results = new Object[len]; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java index 4bc9a3f9b7a..8647c09e789 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -19,7 +19,8 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.ConfigurationUtils; -import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.constants.LoggerCodeConstants; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.config.nested.RestConfig; @@ -57,7 +58,8 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegistry { - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRequestMappingRegistry.class); + private static final ErrorTypeAwareLogger LOGGER = + LoggerFactory.getErrorTypeAwareLogger(DefaultRequestMappingRegistry.class); private final FrameworkModel frameworkModel; private final ContentNegotiator contentNegotiator; @@ -154,9 +156,10 @@ private void register0(RequestMapping mapping, HandlerMeta handler) { for (PathExpression path : mapping.getPathCondition().getExpressions()) { Registration exists = tree.addPath(path, registration); if (exists == null) { - LOGGER.debug("Register rest mapping path: '" + path + "' -> " + mapping); - } else { - LOGGER.warn(Messages.DUPLICATE_MAPPING.format(path, mapping, exists.mapping)); + LOGGER.debug("Register rest mapping path: '{}' -> {}", path, mapping); + } else if (LOGGER.isWarnEnabled()) { + String msg = Messages.DUPLICATE_MAPPING.format(path, mapping, exists.mapping); + LOGGER.warn(LoggerCodeConstants.INTERNAL_ERROR, "", "", msg); } } } finally {