From 5d573cb6b63cb1eae974f41c30a1dbae83f4e59f Mon Sep 17 00:00:00 2001 From: oxsean Date: Mon, 17 Jun 2024 10:17:05 +0000 Subject: [PATCH 01/12] Rest refine --- .artifacts | 1 - .editorconfig | 22 +- .../utils/SystemPropertyConfigUtils.java | 5 +- dubbo-dependencies-bom/pom.xml | 32 ++- dubbo-distribution/dubbo-all-shaded/pom.xml | 8 - dubbo-distribution/dubbo-all/pom.xml | 8 - dubbo-distribution/dubbo-bom/pom.xml | 5 - ...ptee.java => JaxrsHttpRequestAdapter.java} | 4 +- ...tee.java => JaxrsHttpResponseAdapter.java} | 4 +- .../filter/ContainerRequestContextImpl.java | 6 +- dubbo-plugin/dubbo-rest-servlet/pom.xml | 46 --- ...g.http12.message.HttpMessageAdapterFactory | 1 - ...rotocol.tri.rest.argument.ArgumentResolver | 1 - ...tocol.tri.rest.filter.RestExtensionAdapter | 1 - .../src/test/resources/log4j2-test.xml | 30 -- dubbo-plugin/dubbo-rest-spring/pom.xml | 7 +- dubbo-plugin/dubbo-triple-servlet/pom.xml | 127 +++++---- .../support/servlet/DummyFilterConfig.java | 0 .../support/servlet/DummyServletContext.java | 40 ++- .../rest/support/servlet/FilterAdapter.java | 0 .../tri/rest/support/servlet/Helper.java | 0 .../support/servlet/HttpSessionFactory.java | 0 .../servlet/ServletArgumentResolver.java | 0 .../ServletHttpMessageAdapterFactory.java | 10 +- .../servlet/ServletHttpRequestAdapter.java} | 10 +- .../servlet/ServletHttpResponseAdapter.java} | 11 +- ...g.http12.message.HttpMessageAdapterFactory | 2 + ...rotocol.tri.rest.argument.ArgumentResolver | 2 + ...tocol.tri.rest.filter.RestExtensionAdapter | 2 + dubbo-plugin/pom.xml | 1 - .../dubbo/remoting/http12/rest/Mapping.java | 45 +++ .../dubbo/remoting/http12/rest/Param.java | 39 +++ .../dubbo/remoting/http12/rest/ParamType.java | 28 ++ .../dubbo-remoting-zookeeper-curator5/pom.xml | 5 - dubbo-rpc/dubbo-rpc-triple/pom.xml | 16 ++ .../rpc/protocol/tri/TripleConstant.java | 1 - .../tri/h12/HttpRequestHandlerMapping.java | 121 -------- .../h12/grpc/GrpcRequestHandlerMapping.java | 57 +++- .../rpc/protocol/tri/rest/RestConstants.java | 1 + .../tri/rest/RestHttpMessageCodec.java | 4 + .../NamedValueArgumentResolverSupport.java | 6 +- .../protocol/tri/rest/mapping/RadixTree.java | 4 + .../tri/rest/mapping/RequestMapping.java | 13 +- .../mapping/condition/PathExpression.java | 19 +- .../rest/mapping/condition/PathParser.java | 47 ++-- .../rest/mapping/condition/PathSegment.java | 5 +- .../tri/rest/mapping/meta/CorsMeta.java | 16 +- .../mapping/meta/MethodParameterMeta.java | 1 + .../tri/rest/mapping/meta/ParameterMeta.java | 4 + .../tri/rest/support/basic/Annotations.java | 43 +++ .../basic/BasicRequestMappingResolver.java | 90 ++++++ .../basic/FallbackArgumentResolver.java | 105 +++++++ .../support/basic/ParamArgumentResolver.java | 211 ++++++++++++++ .../tri/rest/util/DefaultRestToolKit.java | 3 +- .../rpc/protocol/tri/rest/util/PathUtils.java | 27 +- .../protocol/tri/rest/util/RequestUtils.java | 27 ++ ...rotocol.tri.rest.argument.ArgumentResolver | 2 + ...ol.tri.rest.mapping.RequestMappingResolver | 1 + ...c.protocol.tri.route.RequestHandlerMapping | 3 +- .../protocol/tri/rest/RestProtocolTest.groovy | 66 +++++ .../tri/rest/mapping/RadixTreeTest.groovy | 47 ++++ .../condition/PathExpressionTest.groovy | 261 ++++++++++++++++++ .../tri/rest/test/BaseServiceTest.groovy} | 42 +-- .../tri/rest/util/PathUtilsTest.groovy | 147 ++++++++++ .../tri/rest/mapping/RadixTreeTest.java | 56 ---- .../mapping/condition/PathParserTest.java | 49 ---- .../rpc/protocol/tri/rest/service/Book.java | 65 +++++ .../tri/rest/service/DemoService.java | 32 +++ .../tri/rest/service/DemoServiceImpl.java | 35 +++ .../tri/test/MockH2StreamChannel.java | 78 ++++++ .../tri/test/MockHttp2OutputMessage.java | 43 +++ .../rpc/protocol/tri/test/TestProtocol.java | 65 +++++ .../rpc/protocol/tri/test/TestRequest.java | 172 ++++++++++++ .../rpc/protocol/tri/test/TestResponse.java | 102 +++++++ .../rpc/protocol/tri/test/TestRunner.java | 26 ++ .../protocol/tri/test/TestRunnerBuilder.java | 78 ++++++ .../rpc/protocol/tri/test/TestRunnerImpl.java | 205 ++++++++++++++ .../tri/test/TestServerTransportListener.java | 37 +++ .../internal/org.apache.dubbo.rpc.Protocol | 1 + ...rpc.protocol.tri.rest.filter.RestExtension | 0 ...tocol.tri.rest.filter.RestExtensionAdapter | 0 .../dubbo-spring-boot-3-autoconfigure/pom.xml | 45 +++ .../dubbo-spring-boot-starter/pom.xml | 1 - dubbo-test/dubbo-dependencies-all/pom.xml | 5 - pom.xml | 12 + 85 files changed, 2504 insertions(+), 496 deletions(-) rename dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/{JaxrsHttpRequestAdaptee.java => JaxrsHttpRequestAdapter.java} (97%) rename dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/{JaxrsHttpResponseAdaptee.java => JaxrsHttpResponseAdapter.java} (95%) delete mode 100644 dubbo-plugin/dubbo-rest-servlet/pom.xml delete mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory delete mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver delete mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter delete mode 100644 dubbo-plugin/dubbo-rest-servlet/src/test/resources/log4j2-test.xml rename dubbo-plugin/{dubbo-rest-servlet => dubbo-triple-servlet}/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java (100%) rename dubbo-plugin/{dubbo-rest-servlet => dubbo-triple-servlet}/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java (91%) rename dubbo-plugin/{dubbo-rest-servlet => dubbo-triple-servlet}/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java (100%) rename dubbo-plugin/{dubbo-rest-servlet => dubbo-triple-servlet}/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java (100%) rename dubbo-plugin/{dubbo-rest-servlet => dubbo-triple-servlet}/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.java (100%) rename dubbo-plugin/{dubbo-rest-servlet => dubbo-triple-servlet}/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java (100%) rename dubbo-plugin/{dubbo-rest-servlet => dubbo-triple-servlet}/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java (90%) rename dubbo-plugin/{dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdaptee.java => dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdapter.java} (98%) rename dubbo-plugin/{dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdaptee.java => dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdapter.java} (95%) create mode 100644 dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory create mode 100644 dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver create mode 100644 dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Param.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/ParamType.java delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpRequestHandlerMapping.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/Annotations.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/ParamArgumentResolver.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.groovy create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpressionTest.groovy rename dubbo-rpc/dubbo-rpc-triple/src/test/{java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/PathUtilsTest.java => groovy/org/apache/dubbo/rpc/protocol/tri/rest/test/BaseServiceTest.groovy} (54%) create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtilsTest.groovy delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.java delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParserTest.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/Book.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoService.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoServiceImpl.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockH2StreamChannel.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockHttp2OutputMessage.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestProtocol.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerBuilder.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestServerTransportListener.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter diff --git a/.artifacts b/.artifacts index 41748ff3bf2..904334d12e1 100644 --- a/.artifacts +++ b/.artifacts @@ -116,6 +116,5 @@ dubbo-tracing dubbo-xds dubbo-plugin-loom dubbo-rest-jaxrs -dubbo-rest-servlet dubbo-rest-spring dubbo-triple-servlet diff --git a/.editorconfig b/.editorconfig index 5de62e0893b..5afaa5666b3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -42,26 +42,40 @@ ij_java_keep_simple_lambdas_in_one_line = true ij_java_keep_simple_methods_in_one_line = true ij_java_keep_blank_lines_in_code = 1 ij_java_keep_blank_lines_in_declarations = 1 +ij_java_blank_lines_after_class_header = 1 ij_java_class_count_to_use_import_on_demand = 999 ij_java_names_count_to_use_import_on_demand = 999 ij_java_imports_layout = org.apache.dubbo.**, |, javax.**, |, java.**, |, *, |, $* ij_java_insert_inner_class_imports = true ij_java_space_before_array_initializer_left_brace = true ij_java_method_parameters_new_line_after_left_paren = true -ij_java_wrap_comments = true -ij_java_wrap_long_lines = true +ij_java_wrap_comments = false +ij_java_wrap_long_lines = false ij_java_enum_constants_wrap = split_into_lines -ij_java_method_call_chain_wrap = split_into_lines +ij_java_method_call_chain_wrap = on_every_item ij_java_method_parameters_wrap = on_every_item -ij_java_extends_list_wrap = on_every_item +ij_java_extends_list_wrap = normal ij_java_extends_keyword_wrap = normal ij_java_binary_operation_wrap = normal ij_java_binary_operation_sign_on_next_line = true +[*.groovy] +max_line_length = 180 +ij_groovy_label_indent_size = 4 +ij_groovy_keep_blank_lines_in_code = 1 +ij_groovy_keep_blank_lines_in_declarations = 1 +ij_groovy_blank_lines_after_class_header = 1 +ij_groovy_class_count_to_use_import_on_demand = 999 +ij_groovy_names_count_to_use_import_on_demand = 999 +ij_groovy_imports_layout = org.apache.dubbo.**, |, javax.**, |, java.**, |, *, |, $* +ij_groovy_space_after_type_cast = false + [*.json] tab_width = 2 +indent_size = 2 [*.{yml,yaml}] +tab_width = 2 indent_size = 2 [*.xml] diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SystemPropertyConfigUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SystemPropertyConfigUtils.java index 857807e79e0..87f0ce3a144 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SystemPropertyConfigUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SystemPropertyConfigUtils.java @@ -37,7 +37,10 @@ public class SystemPropertyConfigUtils { for (Field field : fields) { try { assert systemProperties != null; - systemProperties.add((String) field.get(null)); + Object value = field.get(null); + if (value instanceof String) { + systemProperties.add((String) value); + } } catch (IllegalAccessException e) { throw new IllegalStateException( String.format("%s does not have field of %s", clazz.getName(), field.getName())); diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml index b2e45cf7016..b90a98746fd 100644 --- a/dubbo-dependencies-bom/pom.xml +++ b/dubbo-dependencies-bom/pom.xml @@ -109,7 +109,7 @@ 3.25.3 1.3.2 3.1.0 - 6.1.0 + 6.1.0 9.4.54.v20240208 3.1.0 1.1.0.Final @@ -152,8 +152,7 @@ 2.23.1 2.16.1 1.16.0 - - 1.4.3 + 4.0.21 1.0.11 @@ -164,7 +163,9 @@ 4.2.0 2.2 2.2.2 + 1.4.3 4.11.0 + 2.3-groovy-4.0 2.2.7 1.2.0 @@ -416,7 +417,7 @@ jakarta.servlet jakarta.servlet-api - ${servlet6_version} + ${jakarta_servlet_version} provided @@ -624,6 +625,11 @@ api ${envoy_api_version} + + org.apache.groovy + groovy + ${groovy_version} + @@ -750,6 +756,24 @@ ${spring_version} test + + org.spockframework + spock-core + ${spock_version} + test + + + org.spockframework + spock-spring + ${spock_version} + test + + + org.spockframework + spock-junit4 + ${spock_version} + test + com.google.code.gson diff --git a/dubbo-distribution/dubbo-all-shaded/pom.xml b/dubbo-distribution/dubbo-all-shaded/pom.xml index ba8b8e45849..955ea02fdba 100644 --- a/dubbo-distribution/dubbo-all-shaded/pom.xml +++ b/dubbo-distribution/dubbo-all-shaded/pom.xml @@ -119,13 +119,6 @@ compile true - - org.apache.dubbo - dubbo-rest-servlet - ${project.version} - compile - true - org.apache.dubbo dubbo-rest-spring @@ -492,7 +485,6 @@ org.apache.dubbo:dubbo-rpc-injvm org.apache.dubbo:dubbo-rpc-triple org.apache.dubbo:dubbo-rest-jaxrs - org.apache.dubbo:dubbo-rest-servlet org.apache.dubbo:dubbo-rest-spring org.apache.dubbo:dubbo-triple-servlet org.apache.dubbo:dubbo-serialization-api diff --git a/dubbo-distribution/dubbo-all/pom.xml b/dubbo-distribution/dubbo-all/pom.xml index ba8316e1357..24f4a199b98 100644 --- a/dubbo-distribution/dubbo-all/pom.xml +++ b/dubbo-distribution/dubbo-all/pom.xml @@ -276,13 +276,6 @@ compile true - - org.apache.dubbo - dubbo-rest-servlet - ${project.version} - compile - true - org.apache.dubbo dubbo-rest-spring @@ -541,7 +534,6 @@ org.apache.dubbo:dubbo-rpc-injvm org.apache.dubbo:dubbo-rpc-triple org.apache.dubbo:dubbo-rest-jaxrs - org.apache.dubbo:dubbo-rest-servlet org.apache.dubbo:dubbo-rest-spring org.apache.dubbo:dubbo-triple-servlet org.apache.dubbo:dubbo-serialization-api diff --git a/dubbo-distribution/dubbo-bom/pom.xml b/dubbo-distribution/dubbo-bom/pom.xml index 11c897b9b86..fca953d53cf 100644 --- a/dubbo-distribution/dubbo-bom/pom.xml +++ b/dubbo-distribution/dubbo-bom/pom.xml @@ -334,11 +334,6 @@ dubbo-rest-jaxrs ${project.version} - - org.apache.dubbo - dubbo-rest-servlet - ${project.version} - org.apache.dubbo dubbo-rest-spring diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdaptee.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdapter.java similarity index 97% rename from dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdaptee.java rename to dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdapter.java index 3ff3f85381f..6309415356d 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdaptee.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdapter.java @@ -33,14 +33,14 @@ import org.jboss.resteasy.spi.ResteasyAsynchronousContext; import org.jboss.resteasy.spi.ResteasyUriInfo; -public final class JaxrsHttpRequestAdaptee implements org.jboss.resteasy.spi.HttpRequest { +public final class JaxrsHttpRequestAdapter implements org.jboss.resteasy.spi.HttpRequest { private final HttpRequest request; private HttpHeaders headers; private ResteasyUriInfo uriInfo; - public JaxrsHttpRequestAdaptee(HttpRequest request) { + public JaxrsHttpRequestAdapter(HttpRequest request) { this.request = request; } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdaptee.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdapter.java similarity index 95% rename from dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdaptee.java rename to dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdapter.java index 94a4bf417af..d0e7997256f 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdaptee.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdapter.java @@ -24,13 +24,13 @@ import java.io.OutputStream; -public final class JaxrsHttpResponseAdaptee implements org.jboss.resteasy.spi.HttpResponse { +public final class JaxrsHttpResponseAdapter implements org.jboss.resteasy.spi.HttpResponse { private final HttpResponse response; private MultivaluedMap headers; - public JaxrsHttpResponseAdaptee(HttpResponse response) { + public JaxrsHttpResponseAdapter(HttpResponse response) { this.response = response; } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestContextImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestContextImpl.java index 907524f0f64..713c20120ab 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestContextImpl.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestContextImpl.java @@ -20,8 +20,8 @@ import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.Helper; -import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.JaxrsHttpRequestAdaptee; -import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.JaxrsHttpResponseAdaptee; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.JaxrsHttpRequestAdapter; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.JaxrsHttpResponseAdapter; import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.MultivaluedMapWrapper; import javax.ws.rs.container.ContainerRequestContext; @@ -107,7 +107,7 @@ public void setRequestUri(URI baseUri, URI requestUri) { public Request getRequest() { Request req = this.req; if (req == null) { - req = new RequestImpl(new JaxrsHttpRequestAdaptee(request), new JaxrsHttpResponseAdaptee(response)); + req = new RequestImpl(new JaxrsHttpRequestAdapter(request), new JaxrsHttpResponseAdapter(response)); this.req = req; } return req; diff --git a/dubbo-plugin/dubbo-rest-servlet/pom.xml b/dubbo-plugin/dubbo-rest-servlet/pom.xml deleted file mode 100644 index c4efd467daf..00000000000 --- a/dubbo-plugin/dubbo-rest-servlet/pom.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - 4.0.0 - - org.apache.dubbo - dubbo-plugin - ${revision} - ../pom.xml - - - dubbo-rest-servlet - - - - org.apache.dubbo - dubbo-rpc-triple - ${project.version} - - - javax.servlet - javax.servlet-api - - - org.apache.dubbo - dubbo-remoting-netty4 - ${project.version} - test - - - diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory b/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory deleted file mode 100644 index ac45cbe9473..00000000000 --- a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory +++ /dev/null @@ -1 +0,0 @@ -servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.ServletHttpMessageAdapterFactory diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver b/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver deleted file mode 100644 index 5f4a984c541..00000000000 --- a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver +++ /dev/null @@ -1 +0,0 @@ -servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.ServletArgumentResolver diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter b/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter deleted file mode 100644 index ec669308fb3..00000000000 --- a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter +++ /dev/null @@ -1 +0,0 @@ -servlet-filter=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.FilterAdapter diff --git a/dubbo-plugin/dubbo-rest-servlet/src/test/resources/log4j2-test.xml b/dubbo-plugin/dubbo-rest-servlet/src/test/resources/log4j2-test.xml deleted file mode 100644 index 34d1c53e88a..00000000000 --- a/dubbo-plugin/dubbo-rest-servlet/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/dubbo-plugin/dubbo-rest-spring/pom.xml b/dubbo-plugin/dubbo-rest-spring/pom.xml index 21b3d9c084c..55f7a982607 100644 --- a/dubbo-plugin/dubbo-rest-spring/pom.xml +++ b/dubbo-plugin/dubbo-rest-spring/pom.xml @@ -39,9 +39,14 @@ org.apache.dubbo - dubbo-rest-servlet + dubbo-triple-servlet ${project.version} + + javax.servlet + javax.servlet-api + provided + org.springframework spring-context diff --git a/dubbo-plugin/dubbo-triple-servlet/pom.xml b/dubbo-plugin/dubbo-triple-servlet/pom.xml index ee6d22318a2..ab55ef69429 100644 --- a/dubbo-plugin/dubbo-triple-servlet/pom.xml +++ b/dubbo-plugin/dubbo-triple-servlet/pom.xml @@ -28,7 +28,7 @@ 4.0.1 - ${project.build.directory}/generated-sources/java/org/apache/dubbo/rpc/protocol/tri/servlet/jakarta + ${project.build.directory}/generated-sources/java/org/apache/dubbo/rpc/protocol/tri @@ -56,52 +56,81 @@ - - - - org.apache.maven.plugins - maven-antrun-plugin - - - copy-sources - - run - - generate-sources - - - - - - - - - - - - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - add-sources - - add-source - - generate-sources - - - ${project.build.directory}/generated-sources/java - - - - - - - + + + jdk-version-ge-17 + + [17,) + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + generate-jakarta + + run + + generate-sources + + + + + + + + + + + + + + + + /* jakarta placeholder */ + + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-sources + + add-source + + generate-sources + + + ${project.build.directory}/generated-sources/java + + + + + + + + + diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java similarity index 100% rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java similarity index 91% rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java index 3bafcd4367b..e3b382537c2 100644 --- a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java +++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java @@ -28,6 +28,7 @@ import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletRegistration; +import javax.servlet.ServletRegistration.Dynamic; import javax.servlet.SessionCookieConfig; import javax.servlet.SessionTrackingMode; import javax.servlet.descriptor.JspConfigDescriptor; @@ -111,17 +112,14 @@ public RequestDispatcher getNamedDispatcher(String name) { return null; } - @Override public Servlet getServlet(String name) { throw new UnsupportedOperationException(); } - @Override public Enumeration getServlets() { throw new UnsupportedOperationException(); } - @Override public Enumeration getServletNames() { throw new UnsupportedOperationException(); } @@ -131,7 +129,6 @@ public void log(String msg) { LoggerFactory.getLogger(DummyServletContext.class).info(msg); } - @Override public void log(Exception exception, String msg) { LoggerFactory.getLogger(DummyServletContext.class).info(msg, exception); } @@ -211,6 +208,11 @@ public ServletRegistration.Dynamic addServlet(String servletName, Class T createServlet(Class clazz) { throw new UnsupportedOperationException(); @@ -315,4 +317,34 @@ public void declareRoles(String... roleNames) { public String getVirtualServerName() { throw new UnsupportedOperationException(); } + + @Override + public int getSessionTimeout() { + throw new UnsupportedOperationException(); + } + + @Override + public void setSessionTimeout(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public String getRequestCharacterEncoding() { + throw new UnsupportedOperationException(); + } + + @Override + public void setRequestCharacterEncoding(String encoding) { + throw new UnsupportedOperationException(); + } + + @Override + public String getResponseCharacterEncoding() { + throw new UnsupportedOperationException(); + } + + @Override + public void setResponseCharacterEncoding(String encoding) { + throw new UnsupportedOperationException(); + } } diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java similarity index 100% rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java similarity index 100% rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.java similarity index 100% rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.java rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.java diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java similarity index 100% rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java similarity index 90% rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java index 1f85d58b745..1862593203a 100644 --- a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java +++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java @@ -29,7 +29,7 @@ @Activate(order = -100, onClass = "javax.servlet.http.HttpServletRequest") public final class ServletHttpMessageAdapterFactory - implements HttpMessageAdapterFactory { + implements HttpMessageAdapterFactory { private final FrameworkModel frameworkModel; private final ServletContext servletContext; @@ -51,13 +51,13 @@ private HttpSessionFactory getHttpSessionFactory(FrameworkModel frameworkModel) } @Override - public ServletHttpRequestAdaptee adaptRequest(HttpMetadata rawRequest, HttpChannel channel) { - return new ServletHttpRequestAdaptee(rawRequest, channel, servletContext, httpSessionFactory); + public ServletHttpRequestAdapter adaptRequest(HttpMetadata rawRequest, HttpChannel channel) { + return new ServletHttpRequestAdapter(rawRequest, channel, servletContext, httpSessionFactory); } @Override - public HttpResponse adaptResponse(ServletHttpRequestAdaptee request, HttpMetadata rawRequest, Void rawResponse) { - return new ServletHttpResponseAdaptee(); + public HttpResponse adaptResponse(ServletHttpRequestAdapter request, HttpMetadata rawRequest, Void rawResponse) { + return new ServletHttpResponseAdapter(); } public FilterConfig adaptFilterConfig(String filterName) { diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdaptee.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdapter.java similarity index 98% rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdaptee.java rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdapter.java index 6099a6c8d39..46c7b9a637a 100644 --- a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdaptee.java +++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdapter.java @@ -50,7 +50,7 @@ import java.util.Locale; import java.util.Map; -public class ServletHttpRequestAdaptee extends DefaultHttpRequest implements HttpServletRequest { +public class ServletHttpRequestAdapter extends DefaultHttpRequest implements HttpServletRequest { private final ServletContext servletContext; private final HttpSessionFactory sessionFactory; @@ -58,7 +58,7 @@ public class ServletHttpRequestAdaptee extends DefaultHttpRequest implements Htt private ServletInputStream sis; private BufferedReader reader; - public ServletHttpRequestAdaptee( + public ServletHttpRequestAdapter( HttpMetadata metadata, HttpChannel channel, ServletContext servletContext, @@ -211,7 +211,6 @@ public boolean isRequestedSessionIdFromURL() { return false; } - @Override public boolean isRequestedSessionIdFromUrl() { return false; } @@ -376,7 +375,6 @@ public RequestDispatcher getRequestDispatcher(String path) { throw new UnsupportedOperationException(); } - @Override public String getRealPath(String path) { return null; } @@ -437,9 +435,11 @@ public DispatcherType getDispatcherType() { return DispatcherType.REQUEST; } + /* jakarta placeholder */ + @Override public String toString() { - return "ServletHttpRequestAdaptee{" + fieldToString() + '}'; + return "ServletHttpRequestAdapter{" + fieldToString() + '}'; } private static final class HttpInputStream extends ServletInputStream { diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdaptee.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdapter.java similarity index 95% rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdaptee.java rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdapter.java index 75068cf3992..85e3f2c9736 100644 --- a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdaptee.java +++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdapter.java @@ -34,7 +34,7 @@ import java.util.Date; import java.util.Locale; -public class ServletHttpResponseAdaptee extends DefaultHttpResponse implements HttpServletResponse { +public class ServletHttpResponseAdapter extends DefaultHttpResponse implements HttpServletResponse { private ServletOutputStream sos; private PrintWriter writer; @@ -59,16 +59,18 @@ public String encodeRedirectURL(String url) { return RequestUtils.encodeURL(url); } - @Override public String encodeUrl(String url) { return RequestUtils.encodeURL(url); } - @Override public String encodeRedirectUrl(String url) { return RequestUtils.encodeURL(url); } + public void sendRedirect(String location, int sc, boolean clearBuffer) { + sendRedirect(location); + } + @Override public void setDateHeader(String name, long date) { setHeader(name, new Date(date)); @@ -89,7 +91,6 @@ public void addIntHeader(String name, int value) { addHeader(name, String.valueOf(value)); } - @Override public void setStatus(int sc, String sm) { setStatus(sc); setBody(sm); @@ -182,7 +183,7 @@ public Locale getLocale() { @Override public String toString() { - return "ServletHttpResponseAdaptee{" + fieldToString() + '}'; + return "ServletHttpResponseAdapter{" + fieldToString() + '}'; } private static final class HttpOutputStream extends ServletOutputStream { diff --git a/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory new file mode 100644 index 00000000000..791e0a790b1 --- /dev/null +++ b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory @@ -0,0 +1,2 @@ +servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.ServletHttpMessageAdapterFactory +jakarta-servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.jakarta.ServletHttpMessageAdapterFactory diff --git a/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver new file mode 100644 index 00000000000..fd026764ba5 --- /dev/null +++ b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver @@ -0,0 +1,2 @@ +servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.ServletArgumentResolver +jakarta-servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.jakarta.ServletArgumentResolver diff --git a/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter new file mode 100644 index 00000000000..3a2169eb35f --- /dev/null +++ b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter @@ -0,0 +1,2 @@ +servlet-filter=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.FilterAdapter +jakarta-servlet-filter=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.jakarta.FilterAdapter diff --git a/dubbo-plugin/pom.xml b/dubbo-plugin/pom.xml index df9b5dccdeb..1cfb0efb141 100644 --- a/dubbo-plugin/pom.xml +++ b/dubbo-plugin/pom.xml @@ -41,7 +41,6 @@ dubbo-filter-cache dubbo-filter-validation dubbo-rest-jaxrs - dubbo-rest-servlet dubbo-rest-spring dubbo-triple-servlet 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 new file mode 100644 index 00000000000..a05e479306d --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http12.rest; + +import org.apache.dubbo.remoting.http12.HttpMethods; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Mapping { + + String[] value() default {}; + + String[] path() default {}; + + HttpMethods[] method() default {}; + + String[] params() default {}; + + String[] headers() default {}; + + String[] consumes() default {}; + + String[] produces() default {}; +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Param.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Param.java new file mode 100644 index 00000000000..de3f3382279 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Param.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http12.rest; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Param { + + String DEFAULT_NONE = "\n\t\t\n\t\t\n_DEFAULT_NONE_\n\t\t\t\t\n"; + + ParamType type() default ParamType.Param; + + String value() default ""; + + boolean required() default true; + + String defaultValue() default DEFAULT_NONE; +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/ParamType.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/ParamType.java new file mode 100644 index 00000000000..fac5b864c45 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/ParamType.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http12.rest; + +public enum ParamType { + PathVariable, + MatrixVariable, + Param, + Header, + Cookie, + Attribute, + Part, + Body +} diff --git a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml index 5f57672cedb..5b8510d6f88 100644 --- a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml +++ b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml @@ -46,11 +46,6 @@ test - - org.apache.dubbo - dubbo-common - ${project.parent.version} - org.apache.curator curator-framework diff --git a/dubbo-rpc/dubbo-rpc-triple/pom.xml b/dubbo-rpc/dubbo-rpc-triple/pom.xml index aa29ea0bfe6..a48c3e58616 100644 --- a/dubbo-rpc/dubbo-rpc-triple/pom.xml +++ b/dubbo-rpc/dubbo-rpc-triple/pom.xml @@ -52,6 +52,12 @@ ${project.parent.version} true + + org.apache.dubbo + dubbo-cluster + ${project.parent.version} + test + io.netty netty-codec-http2 @@ -100,6 +106,11 @@ reactor-core test + + org.spockframework + spock-core + test + org.apache.dubbo dubbo-native @@ -161,6 +172,11 @@ true + + + org.codehaus.gmavenplus + gmavenplus-plugin + diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java index f36b78e6ebc..0289e08f787 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java @@ -39,6 +39,5 @@ public class TripleConstant { public static final String HTTP_RESPONSE_KEY = "tri.http.response"; public static final String TRIPLE_HANDLER_TYPE_REST = "rest"; - public static final String TRIPLE_HANDLER_TYPE_HTTP = "http"; public static final String TRIPLE_HANDLER_TYPE_GRPC = "grpc"; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpRequestHandlerMapping.java deleted file mode 100644 index 2dbfea05fa8..00000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpRequestHandlerMapping.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.rpc.protocol.tri.h12; - -import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.remoting.http12.HttpRequest; -import org.apache.dubbo.remoting.http12.HttpResponse; -import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; -import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; -import org.apache.dubbo.remoting.http12.message.MediaType; -import org.apache.dubbo.remoting.http12.message.codec.CodecUtils; -import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.PathResolver; -import org.apache.dubbo.rpc.model.FrameworkModel; -import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils; -import org.apache.dubbo.rpc.protocol.tri.TripleConstant; -import org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum; -import org.apache.dubbo.rpc.protocol.tri.TripleProtocol; -import org.apache.dubbo.rpc.protocol.tri.route.RequestHandler; -import org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -@Activate(order = -1000) -public class HttpRequestHandlerMapping implements RequestHandlerMapping { - - private final FrameworkModel frameworkModel; - private final PathResolver pathResolver; - private final CodecUtils codecUtils; - - public HttpRequestHandlerMapping(FrameworkModel frameworkModel) { - this.frameworkModel = frameworkModel; - pathResolver = frameworkModel.getDefaultExtension(PathResolver.class); - codecUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CodecUtils.class); - } - - @Override - public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) { - if (!supportContentType(request.contentType())) { - return null; - } - - String uri = request.uri(); - int index = uri.indexOf('/', 1); - if (index == -1) { - return null; - } - if (uri.indexOf('/', index + 1) != -1) { - return null; - } - - String serviceName = uri.substring(1, index); - String version = request.header(TripleHeaderEnum.SERVICE_VERSION.getHeader()); - String group = request.header(TripleHeaderEnum.SERVICE_GROUP.getHeader()); - String key = URL.buildKey(serviceName, group, version); - Invoker invoker = pathResolver.resolve(key); - if (invoker == null && TripleProtocol.RESOLVE_FALLBACK_TO_DEFAULT) { - invoker = pathResolver.resolve(URL.buildKey(serviceName, group, TripleConstant.DEFAULT_VERSION)); - if (invoker == null) { - invoker = pathResolver.resolve(serviceName); - if (invoker == null) { - return null; - } - } - } - - RequestHandler handler = new RequestHandler(invoker); - handler.setHasStub(pathResolver.hasNativeStub(uri)); - handler.setMethodName(uri.substring(index + 1)); - handler.setServiceDescriptor(DescriptorUtils.findServiceDescriptor(invoker, serviceName, handler.isHasStub())); - determineHttpMessageCodec(handler, url, request); - return handler; - } - - protected boolean supportContentType(String contentType) { - return true; - } - - protected void determineHttpMessageCodec(RequestHandler handler, URL url, HttpRequest request) { - String mediaType = request.mediaType(); - if (mediaType == null) { - mediaType = MediaType.APPLICATION_JSON.getName(); - request.setContentType(mediaType); - } - - Charset charset = request.charsetOrDefault(); - HttpMessageDecoder decoder = codecUtils.determineHttpMessageDecoder(url, frameworkModel, mediaType); - HttpMessageEncoder encoder = codecUtils.determineHttpMessageEncoder(url, frameworkModel, mediaType); - if (!StandardCharsets.UTF_8.equals(charset)) { - decoder = new HttpMessageDecoderWrapper(charset, decoder); - encoder = new HttpMessageEncoderWrapper(charset, encoder); - } - handler.setHttpMessageDecoder(decoder); - handler.setHttpMessageEncoder(encoder); - } - - protected final FrameworkModel getFrameworkModel() { - return frameworkModel; - } - - @Override - public String getType() { - return TripleConstant.TRIPLE_HANDLER_TYPE_HTTP; - } -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcRequestHandlerMapping.java index 52894e0df8b..c2e8d9d3aef 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcRequestHandlerMapping.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcRequestHandlerMapping.java @@ -19,32 +19,71 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.PathResolver; import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils; import org.apache.dubbo.rpc.protocol.tri.TripleConstant; -import org.apache.dubbo.rpc.protocol.tri.h12.HttpRequestHandlerMapping; +import org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum; +import org.apache.dubbo.rpc.protocol.tri.TripleProtocol; import org.apache.dubbo.rpc.protocol.tri.route.RequestHandler; +import org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping; @Activate(order = -3000) -public final class GrpcRequestHandlerMapping extends HttpRequestHandlerMapping { +public final class GrpcRequestHandlerMapping implements RequestHandlerMapping { public static final GrpcCompositeCodecFactory CODEC_FACTORY = new GrpcCompositeCodecFactory(); + private final FrameworkModel frameworkModel; + private final PathResolver pathResolver; + public GrpcRequestHandlerMapping(FrameworkModel frameworkModel) { - super(frameworkModel); + this.frameworkModel = frameworkModel; + pathResolver = frameworkModel.getDefaultExtension(PathResolver.class); } @Override - protected boolean supportContentType(String contentType) { - return contentType != null && contentType.startsWith(MediaType.APPLICATION_GRPC.getName()); - } + public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) { + String contentType = request.contentType(); + if (contentType == null || !contentType.startsWith(MediaType.APPLICATION_GRPC.getName())) { + return null; + } - @Override - protected void determineHttpMessageCodec(RequestHandler handler, URL url, HttpRequest request) { - HttpMessageCodec codec = CODEC_FACTORY.createCodec(url, getFrameworkModel(), request.contentType()); + String uri = request.uri(); + int index = uri.indexOf('/', 1); + if (index == -1) { + return null; + } + if (uri.indexOf('/', index + 1) != -1) { + return null; + } + + String serviceName = uri.substring(1, index); + String version = request.header(TripleHeaderEnum.SERVICE_VERSION.getHeader()); + String group = request.header(TripleHeaderEnum.SERVICE_GROUP.getHeader()); + String key = URL.buildKey(serviceName, group, version); + Invoker invoker = pathResolver.resolve(key); + if (invoker == null && TripleProtocol.RESOLVE_FALLBACK_TO_DEFAULT) { + invoker = pathResolver.resolve(URL.buildKey(serviceName, group, TripleConstant.DEFAULT_VERSION)); + if (invoker == null) { + invoker = pathResolver.resolve(serviceName); + if (invoker == null) { + return null; + } + } + } + + RequestHandler handler = new RequestHandler(invoker); + handler.setHasStub(pathResolver.hasNativeStub(uri)); + handler.setMethodName(uri.substring(index + 1)); + handler.setServiceDescriptor(DescriptorUtils.findServiceDescriptor(invoker, serviceName, handler.isHasStub())); + HttpMessageCodec codec = CODEC_FACTORY.createCodec(url, frameworkModel, request.contentType()); handler.setHttpMessageDecoder(codec); handler.setHttpMessageEncoder(codec); + return handler; } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java index 7c512278bb5..85f8f5cc2dc 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java @@ -29,6 +29,7 @@ public final class RestConstants { public static final String EXTENSION_KEY = "extension"; public static final String EXTENSIONS_ATTRIBUTE_KEY = "restExtensionsAttributeKey"; + public static final int DIALECT_BASIC = 0; public static final int DIALECT_SPRING_MVC = 1; public static final int DIALECT_JAXRS = 2; 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 448a2b7db45..1451c8d2d10 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 @@ -62,6 +62,10 @@ public RestHttpMessageCodec( charset = request.charsetOrDefault(); } + public HttpMessageEncoder getMessageEncoder() { + return messageEncoder; + } + @Override public Object decode(InputStream inputStream, Class targetType, Charset charset) throws DecodeException { return decode(inputStream, new Class[] {targetType}, charset); 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 90b02b68a9b..270df2f6fa5 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 @@ -48,7 +48,7 @@ protected final Object resolve(NamedValueMeta meta, HttpRequest request, HttpRes } if (arg != null) { - return StringUtils.EMPTY_STRING.equals(arg) ? meta.defaultValue() : arg; + return StringUtils.EMPTY_STRING.equals(arg) ? emptyDefaultValue(meta) : arg; } arg = meta.defaultValue(); if (arg != null) { @@ -82,6 +82,10 @@ protected final NamedValueMeta updateNamedValueMeta(ParameterMeta parameterMeta, return meta; } + protected String emptyDefaultValue(NamedValueMeta meta) { + return meta.defaultValue(); + } + protected abstract Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response); protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java index cc13a38439d..93409b64048 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java @@ -73,6 +73,10 @@ public T addPath(PathExpression path, T value) { return null; } + public T addPath(String path, T value) { + return addPath(PathExpression.parse(PathUtils.normalize(path)), value); + } + private static Node getChild(Node current, PathSegment segment) { Node child; if (segment.getType() == Type.LITERAL) { 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 3dd19f4f2b8..7e38cdf499b 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 @@ -86,10 +86,9 @@ public RequestMapping combine(RequestMapping other) { ConsumesCondition consumes = combine(consumesCondition, other.consumesCondition); ProducesCondition produces = combine(producesCondition, other.producesCondition); ConditionWrapper custom = combine(customCondition, other.customCondition); - CorsMeta corsMeta = combine(this.cors, other.cors); + CorsMeta cors = combine(this.cors, other.cors); ResponseMeta response = ResponseMeta.combine(this.response, other.response); - return new RequestMapping( - name, paths, methods, params, headers, consumes, produces, custom, corsMeta, response); + return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, cors, response); } private > T combine(T source, T other) { @@ -327,7 +326,7 @@ public static final class Builder { private String[] consumes; private String[] produces; private Condition customCondition; - private CorsMeta corsMeta; + private CorsMeta cors; private Integer responseStatus; private String responseReason; @@ -376,8 +375,8 @@ public Builder custom(Condition customCondition) { return this; } - public Builder cors(CorsMeta corsMeta) { - this.corsMeta = corsMeta; + public Builder cors(CorsMeta cors) { + this.cors = cors; return this; } @@ -401,7 +400,7 @@ public RequestMapping build() { isEmpty(consumes) ? null : new ConsumesCondition(consumes), isEmpty(produces) ? null : new ProducesCondition(produces), customCondition, - corsMeta == null ? null : corsMeta, + cors == null ? null : cors, responseStatus == null ? null : new ResponseMeta(responseStatus, responseReason)); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java index 28594018160..6e04ed417d5 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java @@ -38,6 +38,10 @@ public static PathExpression parse(@Nonnull String path) { return new PathExpression(path, PathParser.parse(path)); } + public static boolean match(@Nonnull String path, String value) { + return value != null && parse(path).match(value) != null; + } + public String getPath() { return path; } @@ -85,6 +89,9 @@ public int compareTo(PathExpression other, String lookupPath) { public int compareTo(PathExpression other) { int size = segments.length; int otherSize = other.segments.length; + if (isDirect() && other.isDirect()) { + return other.path.length() - path.length(); + } for (int i = 0; i < size && i < otherSize; i++) { int result = segments[i].compareTo(other.segments[i]); if (result != 0) { @@ -112,13 +119,21 @@ public boolean equals(Object obj) { @Override public String toString() { + if (isDirect()) { + return path; + } StringBuilder sb = new StringBuilder(32); for (PathSegment segment : segments) { sb.append('/'); + String value = segment.getValue(); if (segment.getType() == Type.VARIABLE) { - sb.append('{').append(segment.getValue()).append('}'); + if (value.isEmpty()) { + sb.append('*'); + } else { + sb.append('{').append(value).append('}'); + } } else { - sb.append(segment.getValue()); + sb.append(value); } } return sb.toString(); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParser.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParser.java index 30e978c21ec..8add3bd99ee 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParser.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParser.java @@ -23,7 +23,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type; import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; @@ -33,7 +32,7 @@ *

* Spring uri templates *
- * Path and regular expression mappings + * Path and regular expression mappings *

*/ final class PathParser { @@ -59,9 +58,6 @@ static PathSegment[] parse(String path) { private List doParse(String path) { parseSegments(path); - if (segments.isEmpty()) { - return Collections.emptyList(); - } transformSegments(segments, path); for (PathSegment segment : segments) { try { @@ -96,11 +92,11 @@ private void parseSegments(String path) { appendSegment(Type.WILDCARD); break; case State.REGEX_VARIABLE_START: - if (path.charAt(i - 1) == '^' && path.charAt(i - 2) == '[') { - break; + if (path.charAt(i - 1) != '^' || path.charAt(i - 2) != '[') { + regexMulti = true; } - regexMulti = true; - break; + buf.append(c); + continue; case State.VARIABLE_START: case State.WILDCARD_VARIABLE_START: throw new PathParserException(Messages.MISSING_CLOSE_CAPTURE, path, i); @@ -235,8 +231,6 @@ private void parseSegments(String path) { case State.REGEX_VARIABLE_START: case State.WILDCARD_VARIABLE_START: throw new PathParserException(Messages.MISSING_CLOSE_CAPTURE, path, len - 1); - case State.END: - throw new PathParserException(Messages.NO_MORE_DATA_ALLOWED, path, len - 1); default: } } @@ -269,7 +263,9 @@ private static void transformSegments(List segments, String path) { prev = curr; break; case PATTERN_MULTI: - prev.setValue(prev.getValue() + '/'); + if (!".*".equals(prev.getValue())) { + prev.setValue(prev.getValue() + '/'); + } break; default: } @@ -338,9 +334,10 @@ private static void transformSegments(List segments, String path) { throw new PathParserException(Messages.ILLEGAL_DOUBLE_CAPTURE, path); case PATTERN: case PATTERN_MULTI: - prev.addVariable(curr.getVariable()); + String var = curr.getVariable(); + prev.addVariable(var); prev.setType(type); - prev.setValue("(?<" + prev.getVariable() + ">[^/]+)" + value); + prev.setValue("(?<" + prev.getVariable() + ">[^/]+)(?<" + var + '>' + value + ')'); iterator.remove(); continue; default: @@ -354,8 +351,13 @@ private static void transformSegments(List segments, String path) { iterator.remove(); continue; case WILDCARD_TAIL: - prev.addVariable(curr.getVariable()); - prev.setValue(pValue + "(?<" + curr.getVariable() + ">.*)"); + if (curr.getVariables() == null) { + prev.setValue(pValue + ".*"); + } else { + prev.addVariable(curr.getVariable()); + prev.setValue(pValue + "(?<" + curr.getVariable() + ">.*)"); + } + prev.setType(Type.PATTERN_MULTI); iterator.remove(); continue; case VARIABLE: @@ -368,10 +370,15 @@ private static void transformSegments(List segments, String path) { prev.setValue(pValue + "(?<" + curr.getVariable() + ">[^/]+)"); iterator.remove(); continue; - case PATTERN: case PATTERN_MULTI: - prev.addVariable(curr.getVariable()); - prev.setValue(pValue + "(?<" + curr.getVariable() + '>' + value + ')'); + prev.setType(Type.PATTERN_MULTI); + case PATTERN: + if (curr.getVariables() == null) { + prev.setValue(pValue + value); + } else { + prev.addVariable(curr.getVariable()); + prev.setValue(pValue + "(?<" + curr.getVariable() + '>' + value + ')'); + } iterator.remove(); continue; default: @@ -424,7 +431,7 @@ private static String toRegex(String wildcard) { sb.append("[^/]*"); break; case '?': - if (i > 0 && sb.charAt(i - 1) == '*') { + if (i > 0 && wildcard.charAt(i - 1) == '*') { continue; } sb.append("[^/]"); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java index 4a05a20bf16..0411f7f3b37 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java @@ -133,6 +133,9 @@ public boolean match(String path, int start, int end, Map variab private boolean matchPattern(String path, Map variableMap) { Matcher matcher = getPattern().matcher(path); if (matcher.matches()) { + if (variables == null) { + return true; + } for (int i = 0, size = variables.size(); i < size; i++) { String variable = variables.get(i); variableMap.put(variable, matcher.group(variable)); @@ -183,7 +186,7 @@ public int compareTo(PathSegment other) { } int size = variables == null ? 0 : variables.size(); int otherSize = other.variables == null ? 0 : other.variables.size(); - return size - otherSize; + return otherSize - size; } public enum Type { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java index d5545c5858a..9506313cfcf 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -105,24 +105,24 @@ public boolean isEmpty() { public CorsMeta applyDefault() { String[] allowedOriginArray = null; Pattern[] allowedOriginPatternArray = null; - if (this.allowedOrigins.length == 0) { + if (allowedOrigins.length == 0) { allowedOriginArray = new String[] {ANY_VALUE}; allowedOriginPatternArray = new Pattern[] {null}; } String[] allowedMethodArray = null; - if (this.allowedMethods.length == 0) { + if (allowedMethods.length == 0) { allowedMethodArray = new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name()}; } String[] allowedHeaderArray = null; - if (this.allowedHeaders.length == 0) { + if (allowedHeaders.length == 0) { allowedHeaderArray = new String[] {ANY_VALUE}; } Long maxAgeValue = null; - if (this.maxAge == null) { + if (maxAge == null) { maxAgeValue = 1800L; } @@ -134,10 +134,10 @@ public CorsMeta applyDefault() { } return new CorsMeta( - allowedOriginArray == null ? this.allowedOrigins : allowedOriginArray, - allowedOriginPatternArray == null ? this.allowedOriginsPatterns : allowedOriginPatternArray, - allowedMethodArray == null ? this.allowedMethods : allowedMethodArray, - allowedHeaderArray == null ? this.allowedHeaders : allowedHeaderArray, + allowedOriginArray == null ? allowedOrigins : allowedOriginArray, + allowedOriginPatternArray == null ? allowedOriginsPatterns : allowedOriginPatternArray, + allowedMethodArray == null ? allowedMethods : allowedMethodArray, + allowedHeaderArray == null ? allowedHeaders : allowedHeaderArray, exposedHeaders, allowCredentials, maxAgeValue); 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 a6094410f8f..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 @@ -45,6 +45,7 @@ public Parameter getParameter() { return parameter; } + @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/ParameterMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java index 32779b34e06..293b8e6e599 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 @@ -101,6 +101,10 @@ public final Object bind(HttpRequest request, HttpResponse response) { return getToolKit().bind(this, request, response); } + public int getIndex() { + return -1; + } + public String getDescription() { return name; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/Annotations.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/Annotations.java new file mode 100644 index 00000000000..33e0e46289d --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/Annotations.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.basic; + +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationEnum; + +import java.lang.annotation.Annotation; + +public enum Annotations implements AnnotationEnum { + Mapping(org.apache.dubbo.remoting.http12.rest.Mapping.class), + Param(org.apache.dubbo.remoting.http12.rest.Param.class), + Nonnull(javax.annotation.Nonnull.class); + + private final Class type; + + @SuppressWarnings("unchecked") + Annotations(Class type) { + this.type = (Class) type; + } + + public String className() { + return type.getName(); + } + + @Override + public Class type() { + return type; + } +} 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 new file mode 100644 index 00000000000..ad6490ca358 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.basic; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.utils.StringUtils; +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; +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; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.DefaultRestToolKit; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; + +@Activate +public class BasicRequestMappingResolver implements RequestMappingResolver { + + private final FrameworkModel frameworkModel; + private final RestToolKit toolKit; + private CorsMeta globalCorsMeta; + + public BasicRequestMappingResolver(FrameworkModel frameworkModel) { + this.frameworkModel = frameworkModel; + toolKit = new DefaultRestToolKit(frameworkModel); + } + + @Override + public RestToolKit getRestToolKit() { + return toolKit; + } + + @Override + public RequestMapping resolve(ServiceMeta serviceMeta) { + return builder(serviceMeta.findAnnotation(Annotations.Mapping), serviceMeta.getServiceInterface()) + .name(serviceMeta.getType().getSimpleName()) + .contextPath(serviceMeta.getContextPath()) + .build(); + } + + @Override + public RequestMapping resolve(MethodMeta methodMeta) { + ServiceMeta serviceMeta = methodMeta.getServiceMeta(); + if (globalCorsMeta == null) { + globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); + } + String name = methodMeta.getMethod().getName(); + return builder(methodMeta.findAnnotation(Annotations.Mapping), name) + .name(name) + .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) + .cors(globalCorsMeta) + .build(); + } + + private Builder builder(AnnotationMeta mapping, String defaultPath) { + Builder builder = RequestMapping.builder(); + String[] paths = StringUtils.EMPTY_STRING_ARRAY; + if (mapping != null) { + builder.method(mapping.getStringArray("method")) + .param(mapping.getStringArray("params")) + .header(mapping.getStringArray("headers")) + .consume(mapping.getStringArray("consumes")) + .produce(mapping.getStringArray("produces")); + + paths = mapping.getStringArray("path"); + if (paths.length == 0) { + paths = mapping.getValueArray(); + } + } + return paths.length == 0 ? builder.path(defaultPath) : builder.path(paths); + } +} 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 new file mode 100644 index 00000000000..c2d56a322d8 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.basic; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpMethods; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpRequest.FileUpload; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.message.codec.CodecUtils; +import org.apache.dubbo.rpc.model.FrameworkModel; +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.NamedValueMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; + +import java.util.List; +import java.util.Map; + +@Activate(order = Integer.MAX_VALUE - 10000) +public class FallbackArgumentResolver extends AbstractArgumentResolver { + + private final FrameworkModel frameworkModel; + private final CodecUtils codecUtils; + + public FallbackArgumentResolver(FrameworkModel frameworkModel) { + this.frameworkModel = frameworkModel; + codecUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CodecUtils.class); + } + + @Override + public boolean accept(ParameterMeta param) { + return param.getToolKit().getDialect() == RestConstants.DIALECT_BASIC; + } + + @Override + protected NamedValueMeta createNamedValueMeta(ParameterMeta param) { + return new NamedValueMeta(param.isAnnotated(Annotations.Nonnull), null); + } + + @Override + protected String emptyDefaultValue(NamedValueMeta meta) { + return StringUtils.EMPTY_STRING; + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return doResolveValue(meta, request, true); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return doResolveValue(meta, request, false); + } + + protected Object doResolveValue(NamedValueMeta meta, HttpRequest request, boolean single) { + if (HttpMethods.supportBody(request.method())) { + Object body = RequestUtils.decodeBody(request); + if (body != null) { + if (body instanceof List) { + List list = (List) body; + int index = meta.parameterMeta().getIndex(); + if (index >= 0 && list.size() > index) { + return list.get(index); + } + } else if (body instanceof Map) { + Object value = ((Map) body).get(meta.name()); + if (value != null) { + return value; + } + } + } + if (RequestUtils.isMultiPart(request)) { + FileUpload fu = request.part(meta.name()); + if (fu != null) { + return codecUtils + .determineHttpMessageDecoder(null, frameworkModel, fu.contentType()) + .decode(fu.inputStream(), meta.type(), request.charsetOrDefault()); + } + } + } + return single ? request.parameter(meta.name()) : request.parameterValues(meta.name()); + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return resolveValue(meta, request, response); + } +} 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 new file mode 100644 index 00000000000..57d65af0aa6 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/ParamArgumentResolver.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.basic; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.io.StreamUtils; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.remoting.http12.HttpCookie; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpRequest.FileUpload; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.rest.Param; +import org.apache.dubbo.remoting.http12.rest.ParamType; +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.RestParameterException; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.AbstractAnnotationBaseArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +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; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Activate +public class ParamArgumentResolver extends AbstractAnnotationBaseArgumentResolver { + + @Override + protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta ann) { + String defaultValue = ann.getString("defaultValue"); + if (Param.DEFAULT_NONE.equals(defaultValue)) { + defaultValue = null; + } + ParamType paramType = ann.getEnum("type"); + return new ParamNamedValueMeta(ann.getValue(), ann.getBoolean("required"), defaultValue, paramType); + } + + @Override + public Class accept() { + return Annotations.Param.type(); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + switch (((ParamNamedValueMeta) meta).paramType) { + case PathVariable: + return resolvePathVariable(meta, request); + case MatrixVariable: + return CollectionUtils.first(resolveMatrixVariable(meta, request)); + case Param: + return request.parameter(meta.name()); + case Header: + return request.header(meta.name()); + case Cookie: + return request.cookie(meta.name()); + case Attribute: + return request.attribute(meta.name()); + case Part: + return request.part(meta.name()); + case Body: + if (RequestUtils.isFormOrMultiPart(request)) { + return request.formParameter(meta.name()); + } + return RequestUtils.decodeBody(request, meta.type()); + } + return null; + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + switch (((ParamNamedValueMeta) meta).paramType) { + case PathVariable: + String value = resolvePathVariable(meta, request); + return value == null ? Collections.emptyList() : Collections.singletonList(value); + case MatrixVariable: + return resolveMatrixVariable(meta, request); + case Param: + return request.parameterValues(meta.name()); + case Header: + return request.headerValues(meta.name()); + case Cookie: + Collection cookies = request.cookies(); + if (cookies.isEmpty()) { + return Collections.emptyList(); + } + String name = meta.name(); + List result = new ArrayList<>(cookies.size()); + for (HttpCookie cookie : cookies) { + if (name.equals(cookie.name())) { + result.add(cookie); + } + } + return result; + case Attribute: + return request.attribute(meta.name()); + case Part: + return request.parts(); + case Body: + Class type = meta.type(); + if (type == byte[].class) { + try { + return StreamUtils.readBytes(request.inputStream()); + } catch (IOException e) { + throw new RestException(e); + } + } + if (RequestUtils.isFormOrMultiPart(request)) { + return request.formParameterValues(meta.name()); + } + return RequestUtils.decodeBody(request, meta.type()); + } + return null; + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + switch (((ParamNamedValueMeta) meta).paramType) { + case PathVariable: + String value = resolvePathVariable(meta, request); + return value == null ? Collections.emptyMap() : Collections.singletonMap(meta.name(), value); + case MatrixVariable: + return CollectionUtils.first(resolveMatrixVariable(meta, request)); + case Param: + return RequestUtils.getParametersMap(request); + case Header: + return request.headers(); + case Cookie: + Collection cookies = request.cookies(); + if (cookies.isEmpty()) { + return Collections.emptyMap(); + } + Map> mapValue = CollectionUtils.newLinkedHashMap(cookies.size()); + for (HttpCookie cookie : cookies) { + mapValue.computeIfAbsent(cookie.name(), k -> new ArrayList<>()) + .add(cookie); + } + return mapValue; + case Attribute: + return request.attributes(); + case Part: + Collection parts = request.parts(); + if (parts.isEmpty()) { + return Collections.emptyMap(); + } + Map result = new LinkedHashMap<>(parts.size()); + for (FileUpload part : parts) { + result.put(part.name(), part); + } + return result; + case Body: + if (RequestUtils.isFormOrMultiPart(request)) { + return RequestUtils.getFormParametersMap(request); + } + return RequestUtils.decodeBody(request, meta.type()); + } + return null; + } + + private static String resolvePathVariable(NamedValueMeta meta, HttpRequest request) { + Map variableMap = request.attribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + if (variableMap == null) { + if (meta.required()) { + throw new RestParameterException(Messages.ARGUMENT_VALUE_MISSING, meta.name(), meta.type()); + } + return null; + } + String value = variableMap.get(meta.name()); + if (value == null) { + return null; + } + int index = value.indexOf(';'); + return RequestUtils.decodeURL(index == -1 ? value : value.substring(0, index)); + } + + private static List resolveMatrixVariable(NamedValueMeta meta, HttpRequest request) { + Map variableMap = request.attribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + return RequestUtils.parseMatrixVariableValues(variableMap, meta.name()); + } + + private static class ParamNamedValueMeta extends NamedValueMeta { + + private final ParamType paramType; + + public 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/DefaultRestToolKit.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/DefaultRestToolKit.java index 0d5a2a03f79..bf654e32073 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/DefaultRestToolKit.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/DefaultRestToolKit.java @@ -21,6 +21,7 @@ import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; 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; @@ -43,7 +44,7 @@ public DefaultRestToolKit(FrameworkModel frameworkModel) { @Override public int getDialect() { - return 0; + return RestConstants.DIALECT_BASIC; } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtils.java index e6a6bf51c09..609446f826c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtils.java @@ -22,6 +22,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.Messages; import org.apache.dubbo.rpc.protocol.tri.rest.PathParserException; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; import javax.annotation.Nonnull; @@ -88,6 +89,12 @@ public static String combine(@Nonnull String path1, @Nonnull String path2) { return path2; } } else if (path1.indexOf('{') == -1) { + if (path1.indexOf('*') != -1) { + if (PathExpression.parse(path1).match(path2) != null) { + return path2; + } + } + int starDotPos1 = path1.lastIndexOf("*."); if (starDotPos1 > -1) { String ext1 = path1.substring(starDotPos1 + 1); @@ -100,7 +107,7 @@ public static String combine(@Nonnull String path1, @Nonnull String path2) { file2 = path2.substring(0, dotPos2); ext2 = path2.substring(dotPos2); } - boolean ext1All = ext1.equals(".*"); + boolean ext1All = ext1.equals(".*") || ext1.isEmpty(); boolean ext2All = ext2.equals(".*") || ext2.isEmpty(); if (!ext1All && !ext2All) { throw new PathParserException(Messages.CANNOT_COMBINE_PATHS, path1, path2); @@ -240,8 +247,24 @@ public static String normalize(@Nonnull String path) { } end = i; } + switch (state) { + case State.DOT: + end--; + break; + case State.DOT_DOT: + if (buf == null) { + buf = new StringBuilder(len); + } + if (end > 2) { + buf.append(path, start, end - 2); + buf.setLength(buf.lastIndexOf(RestConstants.SLASH) + 1); + start = -1; + } + break; + default: + } if (buf == null) { - return path; + return start == -1 ? path : path.substring(start, end + 1); } if (start != -1) { buf.append(path, start, end + 1); 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 a91fbb3289f..e4d58be8c4d 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 @@ -41,6 +41,11 @@ public final class RequestUtils { private RequestUtils() {} + public static boolean isMultiPart(HttpRequest request) { + String contentType = request.contentType(); + return contentType != null && contentType.startsWith(MediaType.MULTIPART_FORM_DATA.getName()); + } + public static boolean isFormOrMultiPart(HttpRequest request) { String contentType = request.contentType(); if (contentType == null) { @@ -176,4 +181,26 @@ public static Object decodeBody(HttpRequest request, Class type) { } return value; } + + public static Object decodeBody(HttpRequest request) { + HttpMessageDecoder decoder = request.attribute(RestConstants.BODY_DECODER_ATTRIBUTE); + if (decoder == null) { + return null; + } + Object value = request.attribute(RestConstants.BODY_ATTRIBUTE); + if (value == null) { + Class type; + MediaType mediaType = decoder.mediaType(); + if (mediaType == MediaType.APPLICATION_JSON || mediaType == MediaType.APPLICATION_YAML) { + type = Object.class; + } else if (mediaType.isPureText()) { + type = String.class; + } else { + return null; + } + value = decoder.decode(request.inputStream(), type, request.charsetOrDefault()); + request.setAttribute(RestConstants.BODY_ATTRIBUTE, value); + } + return value; + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver index 3d2d4e50736..0486f00884a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver @@ -1 +1,3 @@ misc=org.apache.dubbo.rpc.protocol.tri.rest.argument.MiscArgumentResolver +basic-param=org.apache.dubbo.rpc.protocol.tri.rest.support.basic.ParamArgumentResolver +basic-fallback=org.apache.dubbo.rpc.protocol.tri.rest.support.basic.FallbackArgumentResolver diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver new file mode 100644 index 00000000000..edb8a4f70f8 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver @@ -0,0 +1 @@ +basic=org.apache.dubbo.rpc.protocol.tri.rest.support.basic.BasicRequestMappingResolver diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping index 3f44b63ea22..af94fe10716 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping @@ -1,3 +1,2 @@ -http=org.apache.dubbo.rpc.protocol.tri.h12.HttpRequestHandlerMapping grpc=org.apache.dubbo.rpc.protocol.tri.h12.grpc.GrpcRequestHandlerMapping -rest=org.apache.dubbo.rpc.protocol.tri.rest.mapping.RestRequestHandlerMapping \ No newline at end of file +rest=org.apache.dubbo.rpc.protocol.tri.rest.mapping.RestRequestHandlerMapping diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy new file mode 100644 index 00000000000..1701464f4e5 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.rpc.protocol.tri.rest + +import org.apache.dubbo.rpc.protocol.tri.rest.service.Book +import org.apache.dubbo.rpc.protocol.tri.rest.service.DemoServiceImpl +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()) + } + + def "Hello world"() { + given: + def request = new TestRequest(path: path) + expect: + runner.run(request, String.class) == output + where: + path | output + '/hello?name=world' | 'hello world' + } + + def "post test"() { + given: + def request = new TestRequest(path: path).post(body) + expect: + runner.run(request, String.class) == output + where: + path | body | output + '/postTest' | '["Sam","8"]' | 'Sam is 8 years old' + '/postTest' | '{"name": "Sam", "age": 8}' | 'Sam is 8 years old' + '/postTest?name=Sam' | '{"age": 8}' | 'Sam is 8 years old' + '/postTest?name=Sam' | '{"age": 8}' | 'Sam is 8 years old' + } + + def "bean test"() { + given: + def request = new TestRequest(path: path).post(body) + expect: + runner.run(request, Book.class).name == output + where: + path | body | output + '/buy' | [new Book(name: "Dubbo", price: 80, publishDate: new Date())] | 'Dubbo' + '/buy' | ['book': new Book(name: "Dubbo", price: 80, publishDate: new Date())] | 'Dubbo' + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.groovy new file mode 100644 index 00000000000..35bbe550317 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.groovy @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the 'License'); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.rpc.protocol.tri.rest.mapping + +import spock.lang.Specification + +class RadixTreeTest extends Specification { + + def "Match"() { + given: + def tree = new RadixTree() + tree.addPath('/a/*', 'abc') + tree.addPath('/a/{x}/d/e', 'bcd') + tree.addPath('/a/{v:.*}/e', 'bcd') + when: + def match = tree.match('/a/b/d/e') + then: + !match.empty + } + + def "Clear"() { + given: + def tree = new RadixTree() + tree.addPath('/a/*', 'abc') + tree.addPath('/a/{x}/d/e', 'bcd') + tree.addPath('/a/{v:.*}/e', 'bcd') + when: + tree.remove(s -> s in ['abc', 'bcd']) + then: + tree.empty + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpressionTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpressionTest.groovy new file mode 100644 index 00000000000..cbcec0922e1 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpressionTest.groovy @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition + +import org.apache.dubbo.rpc.protocol.tri.rest.Messages +import org.apache.dubbo.rpc.protocol.tri.rest.PathParserException + +import spock.lang.Specification + +import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression.parse +import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.LITERAL +import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.PATTERN +import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.PATTERN_MULTI +import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.VARIABLE +import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.WILDCARD +import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.WILDCARD_TAIL + +class PathExpressionTest extends Specification { + + def "Parse"() { + given: + def pss = PathParser.parse(path) + expect: + assertPath(pss, rs) + where: + path | rs + null | [[LITERAL, '/']] + '' | [[LITERAL, '/']] + '/' | [[LITERAL, '/']] + '/a?*b' | [[PATTERN, 'a[^/]*b']] + '/a*?b' | [[PATTERN, 'a[^/]*b']] + '/a**b' | [[PATTERN, 'a[^/]*b']] + '/{param:o?e}' | [[PATTERN, '(?o?e)']] + '/{param: \\d+ }/name' | [[PATTERN, '(?\\d+)', 'param'], [LITERAL, 'name']] + '/{param:\\S\\W}' | [[PATTERN_MULTI, '(?\\S\\W)']] + '/{param:a{2}}' | [[PATTERN, '(?a{2})']] + '/{param:[^/]+}' | [[PATTERN, '(?[^/]+)', 'param']] + '/{param:[/]+}' | [[PATTERN_MULTI, '(?[/]+)', 'param']] + '/{one}{two:\\d+}' | [[PATTERN, '(?[^/]+)(?\\d+)', 'one', 'two']] + '/{one:\\d+}{}' | [[PATTERN, '(?\\d+)[^/]+', 'one']] + '/{one:\\d+}**' | [[PATTERN_MULTI, '(?\\d+).*', 'one']] + '/{one:\\d+}{*two}' | [[PATTERN_MULTI, '(?\\d+)(?.*)', 'one', 'two']] + '/{first}-{last}/b/{vv}/v' | [[PATTERN, '(?[^/]+)-(?[^/]+)', 'first', 'last'], 'b', [VARIABLE, 'vv'], 'v'] + } + + def "Parse failed"() { + when: + PathParser.parse(path) + then: + def e = thrown(PathParserException) + e.errorCode == code.name() + where: + path | code + '/{param:[a}' | Messages.REGEX_PATTERN_INVALID + '/{param:}' | Messages.MISSING_REGEX_CONSTRAINT + '/{one}/{*/three' | Messages.MISSING_CLOSE_CAPTURE + '/{one}/{two/three' | Messages.MISSING_CLOSE_CAPTURE + '/{o{ne}' | Messages.ILLEGAL_NESTED_CAPTURE + '/{one}a}' | Messages.MISSING_OPEN_CAPTURE + '/one/{*postfix}t' | Messages.NO_MORE_DATA_ALLOWED + '/{one}{two}' | Messages.ILLEGAL_DOUBLE_CAPTURE + '/{one}-{one}' | Messages.DUPLICATE_CAPTURE_VARIABLE + '/{one}/{one' | Messages.MISSING_CLOSE_CAPTURE + } + + /** + * resteasy @Path + */ + def "ParseJaxrs"() { + given: + def pss = PathParser.parse(path) + expect: + assertPath(pss, rs) + where: + path | rs + '/library/books' | ['/library/books'] + '/library/{isbn}' | ['library', [VARIABLE, 'isbn']] + '/library/{isbn}/{type}' | ['library', [VARIABLE, 'isbn'], [VARIABLE, 'type']] + '{var:\\d+}/stuff' | [[PATTERN, '(?\\d+)', 'var'], [LITERAL, 'stuff']] + '{var:.*}/stuff' | [[PATTERN_MULTI, '(?.*)/stuff', 'var']] + '/aaa{param}bbb' | [[PATTERN, 'aaa(?[^/]+)bbb', 'param']] + '/foo{name}-{zip}bar' | [[PATTERN, 'foo(?[^/]+)-(?[^/]+)bar', 'name', 'zip']] + '/aaa{param:b+}/{many:.*}/stuff' | [[PATTERN, 'aaa(?b+)', 'param'], [PATTERN_MULTI, '(?.*)/stuff', 'many']] + } + + /** + * Spring uri templates + */ + def "ParseSpring"() { + given: + def pss = PathParser.parse(path) + expect: + assertPath(pss, rs) + where: + path | rs + '/resources/ima?e.png' | ['resources', [PATTERN, 'ima[^/]e\\.png']] + '/resources/*.png' | ['resources', [PATTERN, '[^/]*\\.png']] + '/resources/*/test.png' | ['resources', [VARIABLE, ''], 'test.png'] + '/resources/**/test.png' | ['resources', [PATTERN_MULTI, '.*\\Qtest.png\\E']] + '/resources/**/*.png' | ['resources', [PATTERN_MULTI, '.*[^/]*\\.png']] + '/resources/**' | ['resources', [WILDCARD_TAIL, '']] + '/resources/{**}' | ['resources', [WILDCARD_TAIL, '*']] + '/resources/{*path}' | ['resources', [WILDCARD_TAIL, 'path', 'path']] + '/resources/*' | ['resources', [VARIABLE, '']] + '/projects/{project}/versions' | ['projects', [VARIABLE, 'project'], 'versions'] + '/projects/{project:[a-z]+}/versions' | ['projects', [PATTERN, '(?[a-z]+)', 'project'], 'versions'] + /{name:[a-z-]+}-{version:\d\.\d\.\d}{ext:\.[a-z]+}/ | [[PATTERN, /(?[a-z-]+)-(?\d\.\d\.\d)(?\.[a-z]+)/]] + } + + def assertPath(PathSegment[] pss, List rs) { + rs.eachWithIndex { s, i -> + assert pss.length > i + def ps = pss[i] + if (s instanceof String) { + assert ps.value == s + } else if (s instanceof List) { + assert ps.type == s[0] + assert ps.value == s[1] + for (j in 2.. parse(other) == result + where: + path | other | result + '/one' | '/one' | 0 + '/one' | '/two' | 0 + '/one/{two}/three' | '/one/{t}/three' | 0 + '/one/{two}/three' | '/one/*/three' | -1 + '/one/*/three' | '/one/**/three' | -1 + '/one/two' | '/one' | -1 + '/one/{two}' | '/{one}/two' | -1 + '/one/{two}' | '/one/{two:\\d+}' | -1 + '/one/*' | '/one/{two:\\d+}' | -1 + '/one/{two:\\d+}/three' | '/one/**/three' | -1 + } + + def "CompareTo with lookup path"() { + expect: + parse(path).compareTo(parse(other), lookupPath) == result + where: + path | other | lookupPath | result + '/one' | '/two' | '/one' | -1 + '/one' | '/two' | '/two' | 1 + '/one' | '/two' | '/three' | 0 + } + + def "Equals"() { + expect: + Objects.equals(path, other) == result + if (result) { + path.hashCode() == other.hashCode() + } + where: + path | other | result + parse('/one') | parse('/one') | true + parse('/one') | parse('/two') | false + parse('/two') | null | false + parse('/two') | '/two' | false + } + + @SuppressWarnings('ChangeToOperator') + def "Misc"() { + given: + def path = '/library/{isbn}/{type}' + def expr = parse(path) + def seg = expr.segments[1] + expect: + PathExpression.match(path, "/library/1-84356-028-3/math") + expr.path == path + expr.equals(expr) + expr.segments.length == 3 + expr.toString() == path + seg.hashCode() + seg.equals(seg) + !seg.equals(null) + !seg.equals(expr.segments[2]) + !new PathSegment(WILDCARD, '').match('', 0, 0, null) + seg.toString() == '{type=VARIABLE, value=isbn, variables=[isbn]}' + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/PathUtilsTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/test/BaseServiceTest.groovy similarity index 54% rename from dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/PathUtilsTest.java rename to dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/test/BaseServiceTest.groovy index db2ec6804e5..cf2df41cd60 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/PathUtilsTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/test/BaseServiceTest.groovy @@ -14,30 +14,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.rpc.protocol.tri.rest.mapping; -import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.constants.CommonConstants; -import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils; -import org.apache.dubbo.rpc.protocol.tri.support.IGreeter; +package org.apache.dubbo.rpc.protocol.tri.rest.test -import org.junit.jupiter.api.Test; +import org.apache.dubbo.rpc.protocol.tri.test.TestRunner +import org.apache.dubbo.rpc.protocol.tri.test.TestRunnerBuilder -class PathUtilsTest { +import spock.lang.Shared +import spock.lang.Specification - @Test - void getContextPath() { - URL url = URL.valueOf("tri://127.0.0.1/test/"); - url = url.addParameter(CommonConstants.INTERFACE_KEY, IGreeter.class.getName()); - System.out.println(PathUtils.getContextPath(url)); +abstract class BaseServiceTest extends Specification { + + @Shared + TestRunner runner + + void setupSpec() { + TestRunnerBuilder build = TestRunnerBuilder.builder() + setupService(build) + runner = build.build() } - @Test - void isDirectPath() {} + void cleanupSpec() { + runner.destroy() + } - @Test - void combine() {} + void setup() { + } - @Test - void normalize() {} + void cleanup() { + } + + void setupService(TestRunnerBuilder builder) { + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtilsTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtilsTest.groovy new file mode 100644 index 00000000000..6e4047d0e9e --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtilsTest.groovy @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.rpc.protocol.tri.rest.util + +import org.apache.dubbo.common.URL +import org.apache.dubbo.rpc.protocol.tri.rest.PathParserException + +import spock.lang.Specification + +class PathUtilsTest extends Specification { + + def "GetContextPath"() { + expect: + PathUtils.getContextPath(URL.valueOf(url)) == result + where: + result | url + '' | 'tri://127.0.0.1' + '' | 'tri://127.0.0.1/' + '' | 'tri://127.0.0.1/one' + '' | 'tri://127.0.0.1/test.Demo?interface=test.DeMo' + 'one' | 'tri://127.0.0.1/one/test.Demo?interface=test.Demo' + 'one' | 'tri://127.0.0.1/one/?interface=test.Demo' + } + + def "IsDirectPath"() { + expect: + PathUtils.isDirectPath(path) == result + where: + result | path + true | '/one' + true | '/one/{ab/c' + false | '/one/*' + false | '/one/**' + false | '/one/a?c' + false | '/one/{ab}/c' + } + + def "Combine"() { + expect: + PathUtils.combine(path1, path2) == result + where: + path1 | path2 | result + '/hotels' | '' | '/hotels' + '' | '/hotels' | '/hotels' + '/hotels' | '/bookings' | '/hotels/bookings' + '/hotels' | 'bookings' | '/hotels/bookings' + '/hotels/' | '/bookings' | '/hotels/bookings' + '/hotels/' | '/bookings/' | '/hotels/bookings/' + '/hotels/*' | '/bookings' | '/hotels/bookings' + '/hotels/**' | '/bookings' | '/hotels/**/bookings' + '/hotels' | '{hotel}' | '/hotels/{hotel}' + '/{hotels}' | '/hotel' | '/{hotels}/hotel' + '/hotels/*' | '{hotel}' | '/hotels/{hotel}' + '/hotels/**' | '{hotel}' | '/hotels/**/{hotel}' + '/*.html' | '/hotels.html' | '/hotels.html' + '/hotels.*' | '/hotels.html' | '/hotels.html' + '/*.html' | '/hotels' | '/hotels.html' + '/' | '/bookings' | '/bookings' + '/hotels' | '/' | '/hotels/' + } + + def "Combine failed"() { + when: + PathUtils.combine(path1, path2) + then: + thrown(PathParserException) + where: + path1 | path2 + '/*.html' | '/*.txt' + '/*.html' | '/hotel.txt' + } + + def "Normalize"() { + expect: + PathUtils.normalize(path) == result + where: + path | result + '' | '/' + '/' | '/' + '//' | '/' + '/.' | '/' + '/./' | '/' + '/./.' | '/' + '..' | '/..' + '/..' | '/..' + '../' | '/../' + '/../' | '/../' + 'one' | '/one' + '.one' | '/.one' + '/one.two' | '/one.two' + ' /one ' | '/one' + '\t /one \t\n \r' | '/one' + '/one#two' | '/one' + '/one?two' | '/one' + '/one' | '/one' + '/one/' | '/one/' + '/one//' | '/one/' + '/one/.' | '/one/' + 'a/..' | '/' + '/one/..' | '/' + '/one/.../' | '/one/.../' + '/one/../' | '/' + '/one/../../' | '/../' + '/one/../../../' | '/../../' + '/./one/../../../' | '/../../' + '/one/../two' | '/two' + '/one/./two' | '/one/two' + '/one/./two/..' | '/one/' + '/one/./two/../' | '/one/' + '/one/./two/../three' | '/one/three' + '/one/./two/../three/' | '/one/three/' + '/one/./two/../three/.' | '/one/three/' + '/one/./two/../three/..' | '/one/' + '/one/./two/../three/../' | '/one/' + } + + def "Normalize with context path"() { + expect: + PathUtils.normalize(contextPath, path) == result + where: + contextPath | path | result + '' | '' | '/' + '' | '/' | '/' + '/' | '' | '/' + '' | '/one' | '/one' + '/one' | '' | '/one' + '/one' | 'two' | '/one/two' + 'one' | 'two' | '/one/two' + '/one' | '/two' | '/one/two' + '/one//' | '/two' | '/one/two' + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.java deleted file mode 100644 index bd3142c0efe..00000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.rpc.protocol.tri.rest.mapping; - -import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; -import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils; - -import java.util.List; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class RadixTreeTest { - - @Test - void match() { - RadixTree tree = new RadixTree<>(); - tree.addPath(PathExpression.parse("/a/*"), "abc"); - tree.addPath(PathExpression.parse("/a/{x}/d/e"), "acd"); - tree.addPath(PathExpression.parse("/a/{v:.*}/e"), "acd"); - List> match = tree.match("/a/b/d/e"); - Assertions.assertFalse(match.isEmpty()); - } - - @Test - void match1() { - RadixTree tree = new RadixTree<>(); - tree.addPath(PathExpression.parse(PathUtils.normalize("")), "abc"); - List> match = tree.match(PathUtils.normalize("")); - Assertions.assertFalse(match.isEmpty()); - } - - @Test - void clear() { - RadixTree tree = new RadixTree<>(); - tree.addPath(PathExpression.parse("/a/*"), "abc"); - tree.addPath(PathExpression.parse("/a/{x}/d/e"), "acd"); - tree.addPath(PathExpression.parse("/a/{v:.*}/e"), "acd"); - tree.remove(s -> "abc".equals(s) || "acd".equals(s)); - Assertions.assertTrue(tree.isEmpty()); - } -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParserTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParserTest.java deleted file mode 100644 index cb8616fc83b..00000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParserTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; - -import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class PathParserTest { - - @Test - void parse1() { - PathSegment[] parse = PathParser.parse("/{first}-{last}/b/{vv}/v"); - Assertions.assertEquals(parse.length, 4); - } - - @Test - void parse2() { - System.out.println(PathUtils.normalize("/../a/b")); - System.out.println(PathUtils.normalize("../a/b")); - System.out.println(PathUtils.normalize("./a/b")); - System.out.println(PathUtils.normalize("/a/b")); - System.out.println(PathUtils.normalize("a/b")); - System.out.println(PathUtils.normalize("a/../b")); - System.out.println(PathUtils.normalize("a/../../b")); - System.out.println(PathUtils.normalize("a/../../../b")); - System.out.println(PathUtils.normalize(" a/../../../b \r")); - } - - @Test - void parse3() { - System.out.println(PathUtils.normalize("/a/b/c")); - } -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/Book.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/Book.java new file mode 100644 index 00000000000..fcfcadc5bbb --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/Book.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.service; + +import java.util.Date; +import java.util.Objects; + +public class Book { + + private String name; + private int price; + private Date publishDate; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getPrice() { + return price; + } + + public void setPrice(int price) { + this.price = price; + } + + public Date getPublishDate() { + return publishDate; + } + + public void setPublishDate(Date publishDate) { + this.publishDate = publishDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof Book) { + Book book = (Book) o; + return price == book.price + && Objects.equals(name, book.name) + && Objects.equals(publishDate, book.publishDate); + } + return false; + } +} 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 new file mode 100644 index 00000000000..4842edb95bf --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoService.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.service; + +import org.apache.dubbo.remoting.http12.rest.Mapping; + +@Mapping("/") +public interface DemoService { + + @Mapping("/hello") + String hello(String name); + + @Mapping("/postTest") + String postTest(String name, int age); + + @Mapping("/buy") + Book buy(Book book); +} 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 new file mode 100644 index 00000000000..dfb50418422 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoServiceImpl.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.service; + +public class DemoServiceImpl implements DemoService { + + @Override + public String hello(String name) { + return "hello " + name; + } + + @Override + public String postTest(String name, int age) { + return name + " is " + age + " years old"; + } + + @Override + public Book buy(Book book) { + return book; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockH2StreamChannel.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockH2StreamChannel.java new file mode 100644 index 00000000000..d6a31e6e413 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockH2StreamChannel.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.test; + +import org.apache.dubbo.remoting.http12.HttpMetadata; +import org.apache.dubbo.remoting.http12.HttpOutputMessage; +import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; +import org.apache.dubbo.remoting.http12.h2.Http2OutputMessage; + +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class MockH2StreamChannel implements H2StreamChannel { + + private HttpMetadata httpMetadata; + private final List bodies = new ArrayList<>(); + + @Override + public CompletableFuture writeHeader(HttpMetadata httpMetadata) { + this.httpMetadata = httpMetadata; + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture writeMessage(HttpOutputMessage httpOutputMessage) { + bodies.add(httpOutputMessage.getBody()); + return CompletableFuture.completedFuture(null); + } + + @Override + public SocketAddress remoteAddress() { + return InetSocketAddress.createUnresolved(TestProtocol.HOST, TestProtocol.PORT + 1); + } + + @Override + public SocketAddress localAddress() { + return InetSocketAddress.createUnresolved(TestProtocol.HOST, TestProtocol.PORT); + } + + @Override + public void flush() {} + + @Override + public CompletableFuture writeResetFrame(long errorCode) { + return CompletableFuture.completedFuture(null); + } + + @Override + public Http2OutputMessage newOutputMessage(boolean endStream) { + return new MockHttp2OutputMessage(endStream); + } + + public HttpMetadata getHttpMetadata() { + return httpMetadata; + } + + public List getBodies() { + return bodies; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockHttp2OutputMessage.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockHttp2OutputMessage.java new file mode 100644 index 00000000000..18fd78158a2 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockHttp2OutputMessage.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.test; + +import org.apache.dubbo.remoting.http12.h2.Http2OutputMessage; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; + +public class MockHttp2OutputMessage implements Http2OutputMessage { + + private final OutputStream outputStream; + private final boolean endStream; + + public MockHttp2OutputMessage(boolean endStream) { + outputStream = new ByteArrayOutputStream(); + this.endStream = endStream; + } + + @Override + public OutputStream getBody() { + return outputStream; + } + + @Override + public boolean isEndStream() { + return endStream; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestProtocol.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestProtocol.java new file mode 100644 index 00000000000..e7c0d07225a --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestProtocol.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.test; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Exporter; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.AbstractExporter; +import org.apache.dubbo.rpc.protocol.AbstractProtocol; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.DefaultRequestMappingRegistry; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingRegistry; + +public class TestProtocol extends AbstractProtocol { + + public static final String NAME = "test"; + public static final String HOST = "127.0.0.1"; + public static final int PORT = 8081; + + private final RequestMappingRegistry mappingRegistry; + + public TestProtocol(FrameworkModel frameworkModel) { + this.frameworkModel = frameworkModel; + mappingRegistry = frameworkModel.getBeanFactory().getOrRegisterBean(DefaultRequestMappingRegistry.class); + } + + @Override + public int getDefaultPort() { + return PORT; + } + + @Override + public Exporter export(Invoker invoker) throws RpcException { + AbstractExporter exporter = new AbstractExporter(invoker) { + @Override + public void afterUnExport() { + mappingRegistry.unregister(invoker); + } + }; + + invokers.add(invoker); + mappingRegistry.register(invoker); + return exporter; + } + + @Override + protected Invoker protocolBindingRefer(Class type, URL url) throws RpcException { + throw new UnsupportedOperationException(); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java new file mode 100644 index 00000000000..b4e5d24adba --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.test; + +import org.apache.dubbo.remoting.http12.HttpHeaders; +import org.apache.dubbo.remoting.http12.HttpMethods; +import org.apache.dubbo.remoting.http12.h2.Http2Header; +import org.apache.dubbo.remoting.http12.h2.Http2Headers; +import org.apache.dubbo.remoting.http12.h2.Http2MetadataFrame; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@SuppressWarnings("UnusedReturnValue") +public class TestRequest { + + private final HttpHeaders headers = new HttpHeaders(); + private final Map cookies = new LinkedHashMap<>(); + private final Map params = new LinkedHashMap<>(); + private final Map providerParams = new LinkedHashMap<>(); + private List bodies; + + public TestRequest(String path, HttpMethods method) { + setPath(path); + setMethod(method); + } + + public TestRequest(String path) { + setPath(path); + } + + public TestRequest() {} + + public String getMethod() { + return headers.getFirst(Http2Headers.METHOD.getName()); + } + + public TestRequest setMethod(HttpMethods method) { + headers.set(Http2Headers.METHOD.getName(), method.name()); + return this; + } + + public TestRequest setMethod(String method) { + headers.set(Http2Headers.METHOD.getName(), method); + return this; + } + + public String getPath() { + return headers.getFirst(Http2Headers.PATH.getName()); + } + + public TestRequest setPath(String path) { + headers.set(Http2Headers.PATH.getName(), path); + return this; + } + + public TestRequest setHeader(String name, Object value) { + if (value != null) { + headers.set(name, value.toString()); + } + return this; + } + + @SuppressWarnings("unchecked") + public TestRequest setHeaders(Map headers) { + for (Map.Entry entry : headers.entrySet()) { + Object value = entry.getValue(); + if (value instanceof List) { + List items = new ArrayList<>(); + for (Object obj : (List) value) { + if (obj != null) { + items.add(obj.toString()); + } + } + this.headers.put(entry.getKey(), items); + } else if (value instanceof Object[]) { + List items = new ArrayList<>(); + for (Object obj : (Object[]) value) { + if (obj != null) { + items.add(obj.toString()); + } + } + this.headers.put(entry.getKey(), items); + } else if (value != null) { + this.headers.set(entry.getKey(), value.toString()); + } + } + return this; + } + + public TestRequest setCookie(String name, String value) { + cookies.put(name, value); + return this; + } + + public TestRequest setCookies(Map cookies) { + this.cookies.putAll(cookies); + return this; + } + + public Map getCookies() { + return cookies; + } + + public TestRequest setParam(String name, Object value) { + params.put(name, value); + return this; + } + + public TestRequest setParams(Map params) { + this.params.putAll(params); + return this; + } + + public Map getParams() { + return params; + } + + public TestRequest setProviderParam(String name, String value) { + providerParams.put(name, value); + return this; + } + + public TestRequest setProviderParams(Map params) { + providerParams.putAll(params); + return this; + } + + public Map getProviderParams() { + return providerParams; + } + + public TestRequest setBody(Object body) { + List bodies = this.bodies; + if (bodies == null) { + bodies = new ArrayList<>(); + this.bodies = bodies; + } + bodies.add(body); + return this; + } + + public List getBodies() { + return bodies; + } + + public TestRequest post(Object body) { + setMethod(HttpMethods.POST); + setBody(body); + return this; + } + + public Http2Header toMetadata() { + return new Http2MetadataFrame(headers, !HttpMethods.supportBody(getMethod())); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java new file mode 100644 index 00000000000..2e5deeae8b1 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.test; + +import org.apache.dubbo.remoting.http12.HttpHeaderNames; +import org.apache.dubbo.remoting.http12.HttpHeaders; +import org.apache.dubbo.remoting.http12.h2.Http2Headers; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("unchecked") +public class TestResponse { + + private final HttpHeaders headers; + private final List oss; + private final HttpMessageDecoder decoder; + + private List bodies; + + public TestResponse(HttpHeaders headers, List oss, HttpMessageDecoder decoder) { + this.headers = headers; + this.oss = oss; + this.decoder = decoder; + } + + public int getStatus() { + return Integer.parseInt(headers.getFirst(Http2Headers.STATUS.getName())); + } + + public String getHeader(String name) { + return headers.getFirst(name); + } + + public String getContentType() { + return headers.getFirst(HttpHeaderNames.CONTENT_TYPE.getName()); + } + + public T getBody(Class type) { + List bodies = getBodies(type); + return bodies.isEmpty() ? null : bodies.get(0); + } + + public List getBodies(Class type) { + List bodies = (List) this.bodies; + if (bodies == null) { + bodies = new ArrayList<>(oss.size()); + for (OutputStream os : oss) { + ByteArrayOutputStream bos = (ByteArrayOutputStream) os; + if (bos.size() == 0) { + bodies.add(null); + } else { + bodies.add((T) decoder.decode(new ByteArrayInputStream(bos.toByteArray()), type)); + } + } + this.bodies = (List) bodies; + } + return bodies; + } + + public String getValue() { + return getBody(String.class); + } + + public List getValues() { + return getBodies(String.class); + } + + public Integer getIntValue() { + return getBody(Integer.class); + } + + public List getIntValues() { + return getBodies(Integer.class); + } + + public Long getLongValue() { + return getBody(Long.class); + } + + public List getLongValues() { + return getBodies(Long.class); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java new file mode 100644 index 00000000000..c9835931e8e --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.test; + +public interface TestRunner { + + TestResponse run(TestRequest request); + + T run(TestRequest request, Class type); + + void destroy(); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerBuilder.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerBuilder.java new file mode 100644 index 00000000000..c6b1e404bcd --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerBuilder.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +public final class TestRunnerBuilder { + + private final List> providers = new ArrayList<>(); + + public static TestRunnerBuilder builder() { + return new TestRunnerBuilder(); + } + + public static TestRunnerBuilder of(Object service) { + return builder().provider(service); + } + + public static TestRunnerBuilder of(Class type, T service) { + return builder().provider(type, service); + } + + public TestRunnerBuilder provider(Class type, T service, Map parameters) { + providers.add(new TProvider<>(requireNonNull(type), requireNonNull(service), parameters)); + return this; + } + + public TestRunnerBuilder provider(Class type, T service) { + return provider(type, service, Collections.emptyMap()); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public TestRunnerBuilder provider(Object service, Map parameters) { + requireNonNull(service); + Class[] interfaces = service.getClass().getInterfaces(); + providers.add(new TProvider(interfaces[0], service, parameters)); + return this; + } + + public TestRunnerBuilder provider(Object service) { + return provider(service, Collections.emptyMap()); + } + + public TestRunner build() { + return new TestRunnerImpl(providers); + } + + static final class TProvider { + final Class type; + final T service; + final Map parameters; + + TProvider(Class type, T service, Map parameters) { + this.type = type; + this.service = service; + this.parameters = parameters; + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java new file mode 100644 index 00000000000..fbb4c7cddba --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.test; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.utils.Assert; +import org.apache.dubbo.common.utils.ClassUtils; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.config.Constants; +import org.apache.dubbo.remoting.http12.HttpMethods; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.h2.Http2InputMessage; +import org.apache.dubbo.remoting.http12.h2.Http2InputMessageFrame; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; +import org.apache.dubbo.rpc.Protocol; +import org.apache.dubbo.rpc.ProxyFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.model.ModuleServiceRepository; +import org.apache.dubbo.rpc.model.ProviderModel; +import org.apache.dubbo.rpc.model.ServiceDescriptor; +import org.apache.dubbo.rpc.model.ServiceMetadata; +import org.apache.dubbo.rpc.protocol.tri.RpcInvocationBuildContext; +import org.apache.dubbo.rpc.protocol.tri.TripleConstant; +import org.apache.dubbo.rpc.protocol.tri.rest.RestHttpMessageCodec; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; +import org.apache.dubbo.rpc.protocol.tri.test.TestRunnerBuilder.TProvider; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +final class TestRunnerImpl implements TestRunner { + + static final Http2InputMessage END = new Http2InputMessageFrame(new ByteArrayInputStream(new byte[0]), true); + + private final FrameworkModel frameworkModel; + private final ApplicationModel applicationModel; + + TestRunnerImpl(List> providers) { + frameworkModel = FrameworkModel.defaultModel(); + applicationModel = frameworkModel.newApplication(); + Protocol protocol = applicationModel.getExtensionLoader(Protocol.class).getExtension(TestProtocol.NAME); + ProxyFactory proxy = + applicationModel.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); + for (TProvider provider : providers) { + registerProvider(provider, protocol, proxy); + } + } + + private void registerProvider(TProvider provider, Protocol protocol, ProxyFactory proxy) { + Class type = provider.type; + String typeName = type.getName(); + Map parameters = new LinkedHashMap<>(provider.parameters); + parameters.put(CommonConstants.INTERFACE_KEY, typeName); + String contextPath = parameters.get(Constants.CONTEXTPATH_KEY); + String path = contextPath == null ? typeName : contextPath + '/' + typeName; + URL providerUrl = new URL(TestProtocol.NAME, TestProtocol.HOST, TestProtocol.PORT, path, parameters); + ModuleServiceRepository serviceRepository = + applicationModel.getDefaultModule().getServiceRepository(); + ServiceDescriptor serviceDescriptor = serviceRepository.registerService(type); + ProviderModel providerModel = new ProviderModel( + providerUrl.getServiceKey(), + provider.service, + serviceDescriptor, + new ServiceMetadata(), + ClassUtils.getClassLoader(type)); + serviceRepository.registerProvider(providerModel); + providerUrl = providerUrl.setServiceModel(providerModel); + protocol.export(proxy.getInvoker(provider.service, type, providerUrl)); + } + + @Override + @SuppressWarnings("unchecked") + public TestResponse run(TestRequest request) { + MockH2StreamChannel channel = new MockH2StreamChannel(); + URL url = new URL(TestProtocol.NAME, TestProtocol.HOST, TestProtocol.PORT, request.getProviderParams()); + TestServerTransportListener listener = new TestServerTransportListener(channel, url, frameworkModel); + + if (request.getMethod() == null) { + request.setMethod(HttpMethods.GET.name()); + } + + String path = request.getPath(); + Assert.notNull(path, "path is required"); + + if (!request.getParams().isEmpty()) { + StringBuilder sb = new StringBuilder(path); + boolean hasQuery = path.indexOf('?') != -1; + for (Map.Entry entry : request.getParams().entrySet()) { + String key = RequestUtils.encodeURL(entry.getKey()); + Object value = entry.getValue(); + if (value instanceof List) { + for (Object obj : (List) value) { + if (obj != null) { + if (hasQuery) { + sb.append('&'); + } else { + hasQuery = true; + sb.append('?'); + } + sb.append(key).append('=').append(RequestUtils.encodeURL(obj.toString())); + } + } + } else if (value instanceof Object[]) { + for (Object obj : (Object[]) value) { + if (obj != null) { + if (hasQuery) { + sb.append('&'); + } else { + hasQuery = true; + sb.append('?'); + } + sb.append(key).append('=').append(RequestUtils.encodeURL(obj.toString())); + } + } + } else { + if (hasQuery) { + sb.append('&'); + } else { + hasQuery = true; + sb.append('?'); + } + sb.append(key); + if (value != null) { + sb.append('=').append(RequestUtils.encodeURL(value.toString())); + } + } + } + request.setPath(sb.toString()); + } + + if (!request.getCookies().isEmpty()) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : request.getCookies().entrySet()) { + sb.append(entry.getKey()) + .append('=') + .append(RequestUtils.encodeURL(entry.getValue())) + .append(';'); + } + request.setHeader("cookie", sb.toString()); + } + + listener.onMetadata(request.toMetadata()); + RpcInvocationBuildContext context = listener.getContext(); + HttpMessageEncoder encoder = context.getHttpMessageEncoder(); + HttpMessageDecoder decoder = context.getHttpMessageDecoder(); + HttpRequest hRequest = (HttpRequest) context.getAttributes().get(TripleConstant.HTTP_REQUEST_KEY); + if (CollectionUtils.isEmpty(request.getBodies())) { + if (HttpMethods.supportBody(hRequest.method())) { + listener.onData(END); + } + } else { + for (Object body : request.getBodies()) { + byte[] bytes; + if (body instanceof String) { + bytes = ((String) body).getBytes(StandardCharsets.UTF_8); + } else { + ByteArrayOutputStream bos = new ByteArrayOutputStream(256); + encoder.encode(bos, body); + bytes = bos.toByteArray(); + } + listener.onData(new Http2InputMessageFrame(new ByteArrayInputStream(bytes))); + } + listener.onData(END); + } + + if (encoder instanceof RestHttpMessageCodec) { + encoder = ((RestHttpMessageCodec) encoder).getMessageEncoder(); + } + if (encoder instanceof HttpMessageDecoder) { + decoder = (HttpMessageDecoder) encoder; + } + return new TestResponse(channel.getHttpMetadata().headers(), channel.getBodies(), decoder); + } + + @Override + public T run(TestRequest request, Class type) { + return run(request).getBody(type); + } + + @Override + public void destroy() { + applicationModel.destroy(); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestServerTransportListener.java new file mode 100644 index 00000000000..7e061016477 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestServerTransportListener.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.test; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; +import org.apache.dubbo.remoting.http12.h2.Http2Header; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.h12.http2.GenericHttp2ServerTransportListener; + +import java.util.concurrent.Executor; + +public class TestServerTransportListener extends GenericHttp2ServerTransportListener { + + public TestServerTransportListener(H2StreamChannel h2StreamChannel, URL url, FrameworkModel frameworkModel) { + super(h2StreamChannel, url, frameworkModel); + } + + @Override + protected Executor initializeExecutor(Http2Header metadata) { + return Runnable::run; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol b/dubbo-rpc/dubbo-rpc-triple/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol new file mode 100644 index 00000000000..a092b92b9e4 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol @@ -0,0 +1 @@ +test=org.apache.dubbo.rpc.protocol.tri.test.TestProtocol diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension b/dubbo-rpc/dubbo-rpc-triple/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter b/dubbo-rpc/dubbo-rpc-triple/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/pom.xml b/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/pom.xml index bfaf876099d..a31465cc622 100644 --- a/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/pom.xml +++ b/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/pom.xml @@ -66,4 +66,49 @@ true + + + + + src/main/resources + + ** + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + true + + + + + + + + jdk-version-ge-17 + + [17,) + + + + + src/main/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + + false + + + + + + diff --git a/dubbo-spring-boot/dubbo-spring-boot-starter/pom.xml b/dubbo-spring-boot/dubbo-spring-boot-starter/pom.xml index b21cb786f88..a52cd9e2c01 100644 --- a/dubbo-spring-boot/dubbo-spring-boot-starter/pom.xml +++ b/dubbo-spring-boot/dubbo-spring-boot-starter/pom.xml @@ -46,6 +46,5 @@ dubbo-spring-boot-3-autoconfigure ${project.version} - diff --git a/dubbo-test/dubbo-dependencies-all/pom.xml b/dubbo-test/dubbo-dependencies-all/pom.xml index 529a5233bf3..2b719a687fd 100644 --- a/dubbo-test/dubbo-dependencies-all/pom.xml +++ b/dubbo-test/dubbo-dependencies-all/pom.xml @@ -251,11 +251,6 @@ dubbo-rest-jaxrs ${project.version} - - org.apache.dubbo - dubbo-rest-servlet - ${project.version} - org.apache.dubbo dubbo-rest-spring diff --git a/pom.xml b/pom.xml index 438bf40857b..912c7fa1a2f 100644 --- a/pom.xml +++ b/pom.xml @@ -359,6 +359,18 @@ + + org.codehaus.gmavenplus + gmavenplus-plugin + 3.0.2 + + + + compileTests + + + + org.apache.maven.plugins maven-deploy-plugin From e5f93f2d9b78a994c8e12190a4404684821afc4e Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Tue, 18 Jun 2024 00:24:28 +0800 Subject: [PATCH 02/12] Rest config refine --- .../config/nested/AggregationConfig.java | 2 + .../dubbo/config/nested/BaggageConfig.java | 2 + .../dubbo/config/{ => nested}/CorsConfig.java | 5 +- .../dubbo/config/nested/ExporterConfig.java | 2 + .../dubbo/config/nested/HistogramConfig.java | 2 + .../dubbo/config/nested/Http3Config.java | 250 ++++++++++++++++ .../dubbo/config/nested/PrometheusConfig.java | 2 + .../config/nested/PropagationConfig.java | 2 + .../dubbo/config/{ => nested}/RestConfig.java | 74 ++--- .../dubbo/config/nested/SamplingConfig.java | 2 + .../dubbo/config/nested/ServletConfig.java | 81 ++++++ .../dubbo/config/nested/TripleConfig.java | 274 +++--------------- .../src/main/resources/application.yml | 3 +- .../protocol/tri/servlet/TripleFilter.java | 3 +- .../remoting/transport/netty4/Helper.java | 55 ++-- .../java/org/apache/dubbo/rpc/Constants.java | 4 +- .../rpc/protocol/tri/rest/RestConstants.java | 4 +- .../tri/rest/cors/CorsHeaderFilter.java | 20 +- .../tri/rest/mapping/ContentNegotiator.java | 4 + .../DefaultRequestMappingRegistry.java | 159 +++++++--- .../tri/rest/support/basic/Annotations.java | 23 +- .../protocol/tri/rest/RestProtocolTest.groovy | 32 +- .../rpc/protocol/tri/test/TestRequest.java | 59 +++- .../rpc/protocol/tri/test/TestResponse.java | 8 +- .../rpc/protocol/tri/test/TestRunnerImpl.java | 52 ++-- .../DubboTriple3AutoConfiguration.java | 4 + .../DubboTripleAutoConfiguration.java | 10 +- .../DubboConfigurationProperties.java | 50 ---- 28 files changed, 699 insertions(+), 489 deletions(-) rename dubbo-common/src/main/java/org/apache/dubbo/config/{ => nested}/CorsConfig.java (97%) create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/config/nested/Http3Config.java rename dubbo-common/src/main/java/org/apache/dubbo/config/{ => nested}/RestConfig.java (63%) create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/config/nested/ServletConfig.java diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/AggregationConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/AggregationConfig.java index 553d8343548..bcbdf052a80 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/AggregationConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/AggregationConfig.java @@ -23,6 +23,8 @@ */ public class AggregationConfig implements Serializable { + private static final long serialVersionUID = 4878693820314125085L; + /** * Enable aggregation or not. */ diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/BaggageConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/BaggageConfig.java index 45bea909de3..6ff8e9df9f4 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/BaggageConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/BaggageConfig.java @@ -27,6 +27,8 @@ */ public class BaggageConfig implements Serializable { + private static final long serialVersionUID = -4750259290735346439L; + /** * Whether baggage is enabled or not. */ diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/CorsConfig.java similarity index 97% rename from dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java rename to dubbo-common/src/main/java/org/apache/dubbo/config/nested/CorsConfig.java index 657f20d039c..1b604927d6f 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/CorsConfig.java @@ -14,12 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.config; +package org.apache.dubbo.config.nested; import java.io.Serializable; public class CorsConfig implements Serializable { - private static final long serialVersionUID = 1L; + + private static final long serialVersionUID = -7106481576053641726L; /** * A list of origins for which cross-origin requests are allowed. Values may be a specific domain, e.g. diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ExporterConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ExporterConfig.java index 6edfc532040..fd17a99c535 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ExporterConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ExporterConfig.java @@ -28,6 +28,8 @@ */ public class ExporterConfig implements Serializable { + private static final long serialVersionUID = -559392305178067845L; + /** * Configuration for the Zipkin. */ diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/HistogramConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/HistogramConfig.java index 504faf03c80..66dbf466e07 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/HistogramConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/HistogramConfig.java @@ -23,6 +23,8 @@ */ public class HistogramConfig implements Serializable { + private static final long serialVersionUID = 8152538916051803031L; + /** * Whether histograms are enabled or not. Default is not enabled (false). */ diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/Http3Config.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/Http3Config.java new file mode 100644 index 00000000000..757516196fb --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/Http3Config.java @@ -0,0 +1,250 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.config.nested; + +import java.io.Serializable; + +public class Http3Config implements Serializable { + + private static final long serialVersionUID = -4443828713331129834L; + + /** + * Enable http3 support + *

The default value is false. + */ + private Boolean enable; + + /** + * See set_initial_max_data. + *

The default value is 8MiB. + */ + private Integer initialMaxData; + + /** + * If configured this will enable Datagram support. + */ + private Integer recvQueueLen; + + /** + * If configured this will enable Datagram support. + */ + private Integer sendQueueLen; + + /** + * See + * set_initial_max_stream_data_bidi_local. + *

The default value is 1MiB. + */ + private Integer initialMaxStreamDataBidiLocal; + + /** + * See + * set_initial_max_stream_data_bidi_remote. + *

The default value is 1MiB. + */ + private Integer initialMaxStreamDataBidiRemote; + + /** + * See + * set_initial_max_stream_data_uni. + *

The default value is 0. + */ + private Integer initialMaxStreamDataUni; + + /** + * See + * set_initial_max_streams_bidi. + *

The default value is 1B(2^30). + */ + private Long initialMaxStreamsBidi; + + /** + * See + * set_initial_max_streams_uni. + *

+ *

The default value is 1B(2^30). + */ + private Long initialMaxStreamsUni; + + /** + * See + * set_ack_delay_exponent. + *

The default value is 3. + */ + private Integer maxAckDelayExponent; + + /** + * See + * set_max_ack_delay. + *

The default value is 25 milliseconds. + */ + private Integer maxAckDelay; + + /** + * See + * set_disable_active_migration. + *

The default value is {@code false}. + */ + private Boolean disableActiveMigration; + + /** + * See + * enable_hystart. + *

The default value is {@code true}. + */ + private Boolean enableHystart; + + /** + * Sets the congestion control algorithm to use. + *

Supported algorithms are {@code "RENO"} or {@code "CUBIC"} or {@code "BBR"}. + *

The default value is {@code "CUBIC"}. + */ + private String ccAlgorithm; + + public Boolean getEnable() { + return enable; + } + + public void setEnable(Boolean enable) { + this.enable = enable; + } + + public Integer getInitialMaxData() { + return initialMaxData; + } + + public void setInitialMaxData(Integer initialMaxData) { + this.initialMaxData = initialMaxData; + } + + public Integer getRecvQueueLen() { + return recvQueueLen; + } + + public void setRecvQueueLen(Integer recvQueueLen) { + this.recvQueueLen = recvQueueLen; + } + + public Integer getSendQueueLen() { + return sendQueueLen; + } + + public void setSendQueueLen(Integer sendQueueLen) { + this.sendQueueLen = sendQueueLen; + } + + public Integer getInitialMaxStreamDataBidiLocal() { + return initialMaxStreamDataBidiLocal; + } + + public void setInitialMaxStreamDataBidiLocal(Integer initialMaxStreamDataBidiLocal) { + this.initialMaxStreamDataBidiLocal = initialMaxStreamDataBidiLocal; + } + + public Integer getInitialMaxStreamDataBidiRemote() { + return initialMaxStreamDataBidiRemote; + } + + public void setInitialMaxStreamDataBidiRemote(Integer initialMaxStreamDataBidiRemote) { + this.initialMaxStreamDataBidiRemote = initialMaxStreamDataBidiRemote; + } + + public Integer getInitialMaxStreamDataUni() { + return initialMaxStreamDataUni; + } + + public void setInitialMaxStreamDataUni(Integer initialMaxStreamDataUni) { + this.initialMaxStreamDataUni = initialMaxStreamDataUni; + } + + public Long getInitialMaxStreamsBidi() { + return initialMaxStreamsBidi; + } + + public void setInitialMaxStreamsBidi(Long initialMaxStreamsBidi) { + this.initialMaxStreamsBidi = initialMaxStreamsBidi; + } + + public Long getInitialMaxStreamsUni() { + return initialMaxStreamsUni; + } + + public void setInitialMaxStreamsUni(Long initialMaxStreamsUni) { + this.initialMaxStreamsUni = initialMaxStreamsUni; + } + + public Integer getMaxAckDelayExponent() { + return maxAckDelayExponent; + } + + public void setMaxAckDelayExponent(Integer maxAckDelayExponent) { + this.maxAckDelayExponent = maxAckDelayExponent; + } + + public Integer getMaxAckDelay() { + return maxAckDelay; + } + + public void setMaxAckDelay(Integer maxAckDelay) { + this.maxAckDelay = maxAckDelay; + } + + public Boolean getDisableActiveMigration() { + return disableActiveMigration; + } + + public void setDisableActiveMigration(Boolean disableActiveMigration) { + this.disableActiveMigration = disableActiveMigration; + } + + public Boolean getEnableHystart() { + return enableHystart; + } + + public void setEnableHystart(Boolean enableHystart) { + this.enableHystart = enableHystart; + } + + public String getCcAlgorithm() { + return ccAlgorithm; + } + + public void setCcAlgorithm(String ccAlgorithm) { + this.ccAlgorithm = ccAlgorithm; + } + + public void checkDefault() { + if (initialMaxData == null) { + initialMaxData = 1 << 23; + } + if (initialMaxStreamDataBidiLocal == null) { + initialMaxStreamDataBidiLocal = 1 << 20; + } + if (initialMaxStreamDataBidiRemote == null) { + initialMaxStreamDataBidiRemote = 1 << 20; + } + if (initialMaxStreamDataUni == null) { + initialMaxStreamDataUni = 1 << 20; + } + if (initialMaxStreamsBidi == null) { + initialMaxStreamsBidi = (long) 1 << 30; + } + if (initialMaxStreamsUni == null) { + initialMaxStreamsUni = (long) 1 << 30; + } + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PrometheusConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PrometheusConfig.java index a9b8ded5339..cc066678c9a 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PrometheusConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PrometheusConfig.java @@ -25,6 +25,8 @@ */ public class PrometheusConfig implements Serializable { + private static final long serialVersionUID = 2238807632335823129L; + /** * Prometheus exporter configuration */ diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java index c7ee0ba7dde..d2e650d2734 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java @@ -23,6 +23,8 @@ */ public class PropagationConfig implements Serializable { + private static final long serialVersionUID = -2570106396211532046L; + public static final String B3 = "B3"; public static final String W3C = "W3C"; diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/RestConfig.java similarity index 63% rename from dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java rename to dubbo-common/src/main/java/org/apache/dubbo/config/nested/RestConfig.java index dc7e9889286..518d3d3b6d6 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/RestConfig.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.config; +package org.apache.dubbo.config.nested; import org.apache.dubbo.config.support.Nested; @@ -25,35 +25,14 @@ */ public class RestConfig implements Serializable { - private static final long serialVersionUID = 1L; - - /** - * Maximum allowed size for request bodies. - * Limits the size of request to prevent excessively large request. - *

The default value is 8MiB. - */ - private Integer maxBodySize; - - /** - * Maximum allowed size for response bodies. - * Limits the size of responses to prevent excessively large response. - *

The default value is 8MiB. - */ - private Integer maxResponseBodySize; + private static final long serialVersionUID = -8068568976367034755L; /** * Whether path matching should be match paths with a trailing slash. * If enabled, a method mapped to "/users" also matches to "/users/". *

The default value is {@code true}. */ - private Boolean trailingSlashMatch; - - /** - * Whether path matching should be case-sensitive. - * If enabled, a method mapped to "/users" won't match to "/Users/". - *

The default value is {@code false}. - */ - private Boolean caseSensitiveMatch; + private boolean trailingSlashMatch; /** * Whether path matching uses suffix pattern matching (".*"). @@ -62,7 +41,14 @@ public class RestConfig implements Serializable { * inferred from the URL suffix, e.g., ".json" for "application/json". *

The default value is {@code true}. */ - private Boolean suffixPatternMatch; + private boolean suffixPatternMatch; + + /** + * Whether path matching should be case-sensitive. + * If enabled, a method mapped to "/users" won't match to "/Users/". + *

The default value is {@code true}. + */ + private boolean caseSensitiveMatch; /** * The parameter name that can be used to specify the response format. @@ -71,49 +57,33 @@ public class RestConfig implements Serializable { private String formatParameterName; /** - * The config is used to set the Global CORS configuration properties. + * The cors configuration. */ @Nested private CorsConfig cors; - public Integer getMaxBodySize() { - return maxBodySize; - } - - public void setMaxBodySize(Integer maxBodySize) { - this.maxBodySize = maxBodySize; - } - - public Integer getMaxResponseBodySize() { - return maxResponseBodySize; - } - - public void setMaxResponseBodySize(Integer maxResponseBodySize) { - this.maxResponseBodySize = maxResponseBodySize; - } - - public Boolean getTrailingSlashMatch() { + public boolean isTrailingSlashMatch() { return trailingSlashMatch; } - public void setTrailingSlashMatch(Boolean trailingSlashMatch) { + public void setTrailingSlashMatch(boolean trailingSlashMatch) { this.trailingSlashMatch = trailingSlashMatch; } - public Boolean getCaseSensitiveMatch() { - return caseSensitiveMatch; + public boolean isSuffixPatternMatch() { + return suffixPatternMatch; } - public void setCaseSensitiveMatch(Boolean caseSensitiveMatch) { - this.caseSensitiveMatch = caseSensitiveMatch; + public void setSuffixPatternMatch(boolean suffixPatternMatch) { + this.suffixPatternMatch = suffixPatternMatch; } - public Boolean getSuffixPatternMatch() { - return suffixPatternMatch; + public boolean isCaseSensitiveMatch() { + return caseSensitiveMatch; } - public void setSuffixPatternMatch(Boolean suffixPatternMatch) { - this.suffixPatternMatch = suffixPatternMatch; + public void setCaseSensitiveMatch(boolean caseSensitiveMatch) { + this.caseSensitiveMatch = caseSensitiveMatch; } public String getFormatParameterName() { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/SamplingConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/SamplingConfig.java index 26dc0c2ea26..aa2ed072ed6 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/SamplingConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/SamplingConfig.java @@ -23,6 +23,8 @@ */ public class SamplingConfig implements Serializable { + private static final long serialVersionUID = -7456034528275916549L; + /** * Probability in the range from 0.0 to 1.0 that a trace will be sampled. */ diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ServletConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ServletConfig.java new file mode 100644 index 00000000000..30b67ebf57a --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ServletConfig.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.config.nested; + +import java.io.Serializable; + +public class ServletConfig implements Serializable { + + private static final long serialVersionUID = 1091478303358670173L; + + /** + * Enable servlet support, requests are transport through the servlet container, + * which only supports unary calls due to protocol limitations + *

The default value is false. + */ + private Boolean enable; + + /** + * The timeout in milliseconds for the servlet async timeout. + *

The default value is 180_000. + */ + private Integer timeout; + + /** + * The URL patterns that the servlet filter will be registered for. + *

The default value is '/*'. + */ + private String[] filterUrlPatterns; + + /** + * The order of the servlet filter. + *

The default value is -1000000. + */ + private Integer filterOrder; + + public Boolean getEnable() { + return enable; + } + + public void setEnable(Boolean enable) { + this.enable = enable; + } + + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + + public String[] getFilterUrlPatterns() { + return filterUrlPatterns; + } + + public void setFilterUrlPatterns(String[] filterUrlPatterns) { + this.filterUrlPatterns = filterUrlPatterns; + } + + public Integer getFilterOrder() { + return filterOrder; + } + + public void setFilterOrder(Integer filterOrder) { + this.filterOrder = filterOrder; + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java index 96b2141b625..e0dca0664a6 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java @@ -16,6 +16,8 @@ */ package org.apache.dubbo.config.nested; +import org.apache.dubbo.config.support.Nested; + import java.io.Serializable; /** @@ -26,14 +28,14 @@ public class TripleConfig implements Serializable { private static final long serialVersionUID = -3682252713701362155L; /** - * Maximum allowed size for HTTP1 request bodies. + * Maximum allowed size for HTTP request bodies. * Limits the size of request to prevent excessively large request. *

The default value is 8MiB. */ private Integer maxBodySize; /** - * Maximum allowed size for HTTP1 response bodies. + * Maximum allowed size for HTTP response bodies. * Limits the size of responses to prevent excessively large response. *

The default value is 8MiB. */ @@ -41,10 +43,11 @@ public class TripleConfig implements Serializable { /** * Set the maximum chunk size. - * HTTP requests and responses can be quite large, in which case it's better to process the data as a stream of - * chunks. + * HTTP requests and responses can be quite large, + * in which case it's better to process the data as a stream of chunks. * This sets the limit, in bytes, at which Netty will send a chunk down the pipeline. *

The default value is 8MiB. + *

For HTTP/1 */ private Integer maxChunkSize; @@ -53,6 +56,7 @@ public class TripleConfig implements Serializable { * This limits how much memory Netty will use when parsing HTTP header key-value pairs. * You would typically set this to the same value as {@link #setMaxInitialLineLength(Integer)}. *

The default value is 8KiB. + *

For HTTP/1 */ private Integer maxHeaderSize; @@ -61,156 +65,62 @@ public class TripleConfig implements Serializable { * This limits how much memory Netty will use when parsed the initial HTTP header line. * You would typically set this to the same value as {@link #setMaxHeaderSize(Integer)}. *

The default value is 4096. + *

For HTTP/1 */ private Integer maxInitialLineLength; /** * Set the initial size of the temporary buffer used when parsing the lines of the HTTP headers. *

The default value is 16384 octets. + *

For HTTP/1 */ private Integer initialBufferSize; /** * The header table size. + *

For HTTP/1 */ private Integer headerTableSize; /** - * Whether to enable push, default is false. + * Whether to enable push + *

The default value is false. + *

For HTTP/2 */ private Boolean enablePush; /** * Maximum concurrent streams. + *

For HTTP/2 */ private Integer maxConcurrentStreams; /** * Initial window size. + *

For HTTP/2 */ private Integer initialWindowSize; /** * Maximum frame size. + *

For HTTP/2 */ private Integer maxFrameSize; /** * Maximum header list size. + *

For HTTP/2 */ private Integer maxHeaderListSize; - /** - * Enable http3 support - *

The default value is false. - */ - private Boolean enableHttp3; - - /** - * See set_initial_max_data. - *

The default value is 8MiB. - */ - private Integer http3InitialMaxData; - - /** - * If configured this will enable Datagram support. - */ - private Integer http3RecvQueueLen; - - /** - * If configured this will enable Datagram support. - */ - private Integer http3SendQueueLen; - - /** - * See - * set_initial_max_stream_data_bidi_local. - *

The default value is 1MiB. - */ - private Integer http3InitialMaxStreamDataBidiLocal; - - /** - * See - * set_initial_max_stream_data_bidi_remote. - *

The default value is 1MiB. - */ - private Integer http3InitialMaxStreamDataBidiRemote; - - /** - * See - * set_initial_max_stream_data_uni. - *

The default value is 0. - */ - private Integer http3InitialMaxStreamDataUni; + @Nested + private RestConfig rest; - /** - * See - * set_initial_max_streams_bidi. - *

The default value is 1B(2^30). - */ - private Long http3InitialMaxStreamsBidi; + @Nested + private Http3Config http3; - /** - * See - * set_initial_max_streams_uni. - *

- *

The default value is 1B(2^30). - */ - private Long http3InitialMaxStreamsUni; - - /** - * See - * set_ack_delay_exponent. - *

The default value is 3. - */ - private Integer http3MaxAckDelayExponent; - - /** - * See - * set_max_ack_delay. - *

The default value is 25 milliseconds. - */ - private Integer http3MaxAckDelay; - - /** - * See - * set_disable_active_migration. - *

The default value is {@code false}. - */ - private Boolean http3DisableActiveMigration; - - /** - * See - * enable_hystart. - *

The default value is {@code true}. - */ - private Boolean http3EnableHystart; - - /** - * Sets the congestion control algorithm to use. - *

Supported algorithms are {@code "RENO"} or {@code "CUBIC"} or {@code "BBR"}. - *

The default value is {@code "CUBIC"}. - */ - private String http3CcAlgorithm; - - /** - * Enable servlet support, requests are transport through the servlet container, - * which only supports unary calls due to protocol limitations - *

The default value is false. - */ - private Boolean enableServlet; - - /** - * The URL patterns that the servlet filter will be registered for. - *

The default value is '/*'. - */ - private String[] servletFilterUrlPatterns; - - /** - * The order of the servlet filter. - *

The default value is -1000000. - */ - private Integer servletFilterOrder; + @Nested + private ServletConfig servlet; public Integer getMaxBodySize() { return maxBodySize; @@ -308,140 +218,28 @@ public void setMaxHeaderListSize(Integer maxHeaderListSize) { this.maxHeaderListSize = maxHeaderListSize; } - public Boolean getEnableHttp3() { - return enableHttp3; - } - - public void setEnableHttp3(Boolean enableHttp3) { - this.enableHttp3 = enableHttp3; - } - - public Integer getHttp3InitialMaxData() { - return http3InitialMaxData; - } - - public void setHttp3InitialMaxData(Integer http3InitialMaxData) { - this.http3InitialMaxData = http3InitialMaxData; - } - - public Integer getHttp3RecvQueueLen() { - return http3RecvQueueLen; - } - - public void setHttp3RecvQueueLen(Integer http3RecvQueueLen) { - this.http3RecvQueueLen = http3RecvQueueLen; - } - - public Integer getHttp3SendQueueLen() { - return http3SendQueueLen; - } - - public void setHttp3SendQueueLen(Integer http3SendQueueLen) { - this.http3SendQueueLen = http3SendQueueLen; - } - - public Integer getHttp3InitialMaxStreamDataBidiLocal() { - return http3InitialMaxStreamDataBidiLocal; - } - - public void setHttp3InitialMaxStreamDataBidiLocal(Integer http3InitialMaxStreamDataBidiLocal) { - this.http3InitialMaxStreamDataBidiLocal = http3InitialMaxStreamDataBidiLocal; - } - - public Integer getHttp3InitialMaxStreamDataBidiRemote() { - return http3InitialMaxStreamDataBidiRemote; - } - - public void setHttp3InitialMaxStreamDataBidiRemote(Integer http3InitialMaxStreamDataBidiRemote) { - this.http3InitialMaxStreamDataBidiRemote = http3InitialMaxStreamDataBidiRemote; - } - - public Integer getHttp3InitialMaxStreamDataUni() { - return http3InitialMaxStreamDataUni; - } - - public void setHttp3InitialMaxStreamDataUni(Integer http3InitialMaxStreamDataUni) { - this.http3InitialMaxStreamDataUni = http3InitialMaxStreamDataUni; - } - - public Long getHttp3InitialMaxStreamsBidi() { - return http3InitialMaxStreamsBidi; - } - - public void setHttp3InitialMaxStreamsBidi(Long http3InitialMaxStreamsBidi) { - this.http3InitialMaxStreamsBidi = http3InitialMaxStreamsBidi; - } - - public Long getHttp3InitialMaxStreamsUni() { - return http3InitialMaxStreamsUni; - } - - public void setHttp3InitialMaxStreamsUni(Long http3InitialMaxStreamsUni) { - this.http3InitialMaxStreamsUni = http3InitialMaxStreamsUni; - } - - public Integer getHttp3MaxAckDelayExponent() { - return http3MaxAckDelayExponent; - } - - public void setHttp3MaxAckDelayExponent(Integer http3MaxAckDelayExponent) { - this.http3MaxAckDelayExponent = http3MaxAckDelayExponent; - } - - public Integer getHttp3MaxAckDelay() { - return http3MaxAckDelay; - } - - public void setHttp3MaxAckDelay(Integer http3MaxAckDelay) { - this.http3MaxAckDelay = http3MaxAckDelay; - } - - public Boolean getHttp3DisableActiveMigration() { - return http3DisableActiveMigration; - } - - public void setHttp3DisableActiveMigration(Boolean http3DisableActiveMigration) { - this.http3DisableActiveMigration = http3DisableActiveMigration; - } - - public Boolean getHttp3EnableHystart() { - return http3EnableHystart; - } - - public void setHttp3EnableHystart(Boolean http3EnableHystart) { - this.http3EnableHystart = http3EnableHystart; - } - - public String getHttp3CcAlgorithm() { - return http3CcAlgorithm; - } - - public void setHttp3CcAlgorithm(String http3CcAlgorithm) { - this.http3CcAlgorithm = http3CcAlgorithm; - } - - public Boolean getEnableServlet() { - return enableServlet; + public RestConfig getRest() { + return rest; } - public void setEnableServlet(Boolean enableServlet) { - this.enableServlet = enableServlet; + public void setRest(RestConfig rest) { + this.rest = rest; } - public String[] getServletFilterUrlPatterns() { - return servletFilterUrlPatterns; + public Http3Config getHttp3() { + return http3; } - public void setServletFilterUrlPatterns(String[] servletFilterUrlPatterns) { - this.servletFilterUrlPatterns = servletFilterUrlPatterns; + public void setHttp3(Http3Config http3) { + this.http3 = http3; } - public Integer getServletFilterOrder() { - return servletFilterOrder; + public ServletConfig getServlet() { + return servlet; } - public void setServletFilterOrder(Integer servletFilterOrder) { - this.servletFilterOrder = servletFilterOrder; + public void setServlet(ServletConfig servlet) { + this.servlet = servlet; } public void checkDefault() { diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml index b75b8578e99..ec0ba148c1c 100644 --- a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml +++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml @@ -26,7 +26,8 @@ dubbo: name: tri port: ${server.port} triple: - enable-servlet: true + servlet: + enable: true registry: id: zk-registry address: zookeeper://127.0.0.1:2181 diff --git a/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/TripleFilter.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/TripleFilter.java index 17f1d107a64..b009588853c 100644 --- a/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/TripleFilter.java +++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/TripleFilter.java @@ -20,6 +20,7 @@ import org.apache.dubbo.common.io.StreamUtils; import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; import org.apache.dubbo.remoting.http12.h2.Http2InputMessageFrame; import org.apache.dubbo.remoting.http12.h2.Http2ServerTransportListenerFactory; @@ -70,7 +71,7 @@ public void init(FilterConfig config) { pathResolver = frameworkModel.getDefaultExtension(PathResolver.class); mappingRegistry = frameworkModel.getBeanFactory().getOrRegisterBean(DefaultRequestMappingRegistry.class); String timeoutString = config.getInitParameter("timeout"); - defaultTimeout = timeoutString == null ? 180_000 : Integer.parseInt(timeoutString); + defaultTimeout = StringUtils.isEmpty(timeoutString) ? 180_000 : Integer.parseInt(timeoutString); } @Override diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java index b1d88ff901a..6df29c4f06f 100644 --- a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java @@ -18,6 +18,7 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.config.context.ConfigManager; +import org.apache.dubbo.config.nested.Http3Config; import org.apache.dubbo.config.nested.TripleConfig; import io.netty.incubator.codec.quic.QuicCodecBuilder; @@ -30,43 +31,37 @@ final class Helper { @SuppressWarnings("unchecked") static > T configCodec(QuicCodecBuilder builder, URL url) { TripleConfig tripleConfig = ConfigManager.getProtocol(url).getTriple(); - if (tripleConfig.getHttp3InitialMaxData() != null) { - builder.initialMaxData(tripleConfig.getHttp3InitialMaxData()); + Http3Config config = tripleConfig.getHttp3(); + if (config == null) { + config = new Http3Config(); } - if (tripleConfig.getHttp3RecvQueueLen() != null && tripleConfig.getHttp3SendQueueLen() != null) { - builder.datagram(tripleConfig.getHttp3RecvQueueLen(), tripleConfig.getHttp3SendQueueLen()); - } - if (tripleConfig.getHttp3InitialMaxStreamDataBidiLocal() != null) { - builder.initialMaxStreamDataBidirectionalLocal(tripleConfig.getHttp3InitialMaxStreamDataBidiLocal()); - } - if (tripleConfig.getHttp3InitialMaxStreamDataBidiRemote() != null) { - builder.initialMaxStreamDataBidirectionalRemote(tripleConfig.getHttp3InitialMaxStreamDataBidiRemote()); - } - if (tripleConfig.getHttp3InitialMaxStreamDataUni() != null) { - builder.initialMaxStreamDataUnidirectional(tripleConfig.getHttp3InitialMaxStreamDataUni()); - } - if (tripleConfig.getHttp3InitialMaxStreamsBidi() != null) { - builder.initialMaxStreamsBidirectional(tripleConfig.getHttp3InitialMaxStreamsBidi()); - } - if (tripleConfig.getHttp3InitialMaxStreamsUni() != null) { - builder.initialMaxStreamsUnidirectional(tripleConfig.getHttp3InitialMaxStreamsUni()); + config.checkDefault(); + builder.initialMaxData(config.getInitialMaxData()) + .initialMaxStreamDataBidirectionalLocal(config.getInitialMaxStreamDataBidiLocal()) + .initialMaxStreamDataBidirectionalRemote(config.getInitialMaxStreamDataBidiRemote()) + .initialMaxStreamDataUnidirectional(config.getInitialMaxStreamDataUni()) + .initialMaxStreamsBidirectional(config.getInitialMaxStreamsBidi()) + .initialMaxStreamsUnidirectional(config.getInitialMaxStreamsUni()); + + if (config.getRecvQueueLen() != null && config.getSendQueueLen() != null) { + builder.datagram(config.getRecvQueueLen(), config.getSendQueueLen()); } - if (tripleConfig.getHttp3MaxAckDelayExponent() != null) { - builder.ackDelayExponent(tripleConfig.getHttp3MaxAckDelayExponent()); + if (config.getMaxAckDelayExponent() != null) { + builder.ackDelayExponent(config.getMaxAckDelayExponent()); } - if (tripleConfig.getHttp3MaxAckDelay() != null) { - builder.maxAckDelay(tripleConfig.getHttp3MaxAckDelay(), MILLISECONDS); + if (config.getMaxAckDelay() != null) { + builder.maxAckDelay(config.getMaxAckDelay(), MILLISECONDS); } - if (tripleConfig.getHttp3DisableActiveMigration() != null) { - builder.activeMigration(tripleConfig.getHttp3DisableActiveMigration()); + if (config.getDisableActiveMigration() != null) { + builder.activeMigration(config.getDisableActiveMigration()); } - if (tripleConfig.getHttp3EnableHystart() != null) { - builder.hystart(tripleConfig.getHttp3EnableHystart()); + if (config.getEnableHystart() != null) { + builder.hystart(config.getEnableHystart()); } - if (tripleConfig.getHttp3CcAlgorithm() != null) { - if ("RENO".equalsIgnoreCase(tripleConfig.getHttp3CcAlgorithm())) { + if (config.getCcAlgorithm() != null) { + if ("RENO".equalsIgnoreCase(config.getCcAlgorithm())) { builder.congestionControlAlgorithm(QuicCongestionControlAlgorithm.RENO); - } else if ("BBR".equalsIgnoreCase(tripleConfig.getHttp3CcAlgorithm())) { + } else if ("BBR".equalsIgnoreCase(config.getCcAlgorithm())) { builder.congestionControlAlgorithm(QuicCongestionControlAlgorithm.BBR); } } diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java index 0e6100827d2..9327ad6a7b9 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java @@ -105,8 +105,8 @@ public interface Constants { String H2_SETTINGS_BUILTIN_SERVICE_INIT = "dubbo.tri.builtin.service.init"; String H2_SETTINGS_PASS_THROUGH_STANDARD_HTTP_HEADERS = "dubbo.rpc.tri.pass-through-standard-http-headers"; - String H3_SETTINGS_HTTP3_ENABLE = "dubbo.protocol.triple.enable-http3"; - String H3_SETTINGS_SERVLET_ENABLE = "dubbo.protocol.triple.enable-servlet"; + String H3_SETTINGS_HTTP3_ENABLE = "dubbo.protocol.triple.http3.enable"; + String H3_SETTINGS_SERVLET_ENABLE = "dubbo.protocol.triple.servlet.enable"; String ADAPTIVE_LOADBALANCE_ATTACHMENT_KEY = "lb_adaptive"; String ADAPTIVE_LOADBALANCE_START_TIME = "adaptive_startTime"; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java index 85f8f5cc2dc..c8d8bdf131e 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java @@ -50,9 +50,7 @@ public final class RestConstants { "org.springframework.web.servlet.HandlerMapping.producibleMediaTypes"; /* Configuration Key */ - public static final String CONFIG_PREFIX = "dubbo.rpc.rest."; - public static final String MAX_BODY_SIZE_KEY = CONFIG_PREFIX + "max-body-size"; - public static final String MAX_RESPONSE_BODY_SIZE_KEY = CONFIG_PREFIX + "max-response-body-size"; + public static final String CONFIG_PREFIX = "dubbo.protocol.triple.rest."; public static final String SUFFIX_PATTERN_MATCH_KEY = CONFIG_PREFIX + "suffix-pattern-match"; public static final String TRAILING_SLASH_MATCH_KEY = CONFIG_PREFIX + "trailing-slash-match"; public static final String CASE_SENSITIVE_MATCH_KEY = CONFIG_PREFIX + "case-sensitive-match"; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java index f3dbe3eb622..8b71b3eff9b 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java @@ -52,16 +52,16 @@ @Activate(group = CommonConstants.PROVIDER, order = 1000) public class CorsHeaderFilter extends RestHeaderFilterAdapter { - public static final String VARY = "Vary"; - public static final String ORIGIN = "Origin"; - public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; - public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; - public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; - public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; - public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; - public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; - public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; - public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + public static final String VARY = "vary"; + public static final String ORIGIN = "origin"; + public static final String ACCESS_CONTROL_REQUEST_METHOD = "access-control-request-method"; + public static final String ACCESS_CONTROL_REQUEST_HEADERS = "access-control-request-headers"; + public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "access-control-allow-credentials"; + public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "access-control-expose-headers"; + public static final String ACCESS_CONTROL_MAX_AGE = "access-control-max-age"; + public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin"; + public static final String ACCESS_CONTROL_ALLOW_METHODS = "access-control-allow-methods"; + public static final String ACCESS_CONTROL_ALLOW_HEADERS = "access-control-allow-headers"; public static final String SEP = ", "; @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/ContentNegotiator.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/ContentNegotiator.java index d31858ce725..e92b0bd5644 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/ContentNegotiator.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/ContentNegotiator.java @@ -87,6 +87,10 @@ public String negotiate(HttpRequest request) { return null; } + public boolean supportExtension(String extension) { + return getMediaTypeByExtension(extension) != null; + } + private String getSuitableMediaType(String name) { int index = name.indexOf('/'); if (index == -1 || index == name.length() - 1) { 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 ffb35027787..ac3dd6880f2 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 @@ -16,7 +16,9 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping; -import org.apache.dubbo.common.utils.Assert; +import org.apache.dubbo.common.config.Configuration; +import org.apache.dubbo.common.config.ConfigurationUtils; +import org.apache.dubbo.config.nested.RestConfig; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.message.MethodMetadata; import org.apache.dubbo.rpc.Invoker; @@ -44,22 +46,31 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import static org.apache.dubbo.rpc.protocol.tri.rest.Messages.AMBIGUOUS_MAPPING; -import static org.apache.dubbo.rpc.protocol.tri.rest.Messages.DUPLICATE_MAPPING; public final class DefaultRequestMappingRegistry implements RequestMappingRegistry { + private final FrameworkModel frameworkModel; private final List resolvers; + private final ContentNegotiator contentNegotiator; private final RadixTree tree = new RadixTree<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private RestConfig restConfig; + public DefaultRequestMappingRegistry(FrameworkModel frameworkModel) { + this.frameworkModel = frameworkModel; resolvers = frameworkModel.getActivateExtensions(RequestMappingResolver.class); + contentNegotiator = frameworkModel.getBeanFactory().getOrRegisterBean(ContentNegotiator.class); } @Override public void register(Invoker invoker) { Object service = invoker.getUrl().getServiceModel().getProxyObject(); + ServiceDescriptor serviceDescriptor = DescriptorUtils.getReflectionServiceDescriptor(invoker.getUrl()); + if (serviceDescriptor == null) { + return; + } new MethodWalker().walk(service.getClass(), (classes, consumer) -> { for (int i = 0, size = resolvers.size(); i < size; i++) { RequestMappingResolver resolver = resolvers.get(i); @@ -70,6 +81,12 @@ public void register(Invoker invoker) { } RequestMapping classMapping = resolver.resolve(serviceMeta); consumer.accept((methods) -> { + Method method = methods.get(0); + MethodDescriptor methodDescriptor = + serviceDescriptor.getMethod(method.getName(), method.getParameterTypes()); + if (methodDescriptor == null) { + return; + } MethodMeta methodMeta = new MethodMeta(methods, serviceMeta); RequestMapping methodMapping = resolver.resolve(methodMeta); if (methodMapping == null) { @@ -78,7 +95,14 @@ public void register(Invoker invoker) { if (classMapping != null) { methodMapping = classMapping.combine(methodMapping); } - register0(methodMapping, buildHandlerMeta(invoker, methodMeta)); + register0( + methodMapping, + new HandlerMeta( + invoker, + methodMeta, + MethodMetadata.fromMethodDescriptor(methodDescriptor), + methodDescriptor, + serviceDescriptor)); }); } }); @@ -91,31 +115,13 @@ private void register0(RequestMapping mapping, HandlerMeta handler) { registration.mapping = mapping; registration.meta = handler; for (PathExpression path : mapping.getPathCondition().getExpressions()) { - Registration exists = tree.addPath(path, registration); - if (exists != null) { - throw new RestInitializeException(DUPLICATE_MAPPING, path.getPath(), mapping, exists.mapping); - } + tree.addPath(path, registration); } } finally { lock.writeLock().unlock(); } } - private HandlerMeta buildHandlerMeta(Invoker invoker, MethodMeta methodMeta) { - ServiceDescriptor serviceDescriptor = DescriptorUtils.getReflectionServiceDescriptor(invoker.getUrl()); - String serviceInterface = invoker.getUrl().getServiceInterface(); - Assert.notNull(serviceDescriptor, "ServiceDescriptor for [%s] can't be null", serviceInterface); - Method method = methodMeta.getMethod(); - MethodDescriptor methodDescriptor = serviceDescriptor.getMethod(method.getName(), method.getParameterTypes()); - Assert.notNull(methodDescriptor, "MethodDescriptor for [%s] can't be null", method); - return new HandlerMeta( - invoker, - methodMeta, - MethodMetadata.fromMethodDescriptor(methodDescriptor), - methodDescriptor, - serviceDescriptor); - } - @Override public void unregister(Invoker invoker) { lock.writeLock().lock(); @@ -139,33 +145,29 @@ public void destroy() { public HandlerMeta lookup(HttpRequest request) { String path = PathUtils.normalize(request.rawPath()); request.setAttribute(RestConstants.PATH_ATTRIBUTE, path); - List> matches = new ArrayList<>(); - lock.readLock().lock(); - try { - tree.match(path, matches); - } finally { - lock.readLock().unlock(); - } + List candidates = new ArrayList<>(); - int size = matches.size(); - if (size == 0) { - return null; - } - List candidates = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - Match match = matches.get(i); - RequestMapping mapping = match.getValue().mapping.match(request, match.getExpression()); - if (mapping != null) { - Candidate candidate = new Candidate(); - candidate.mapping = mapping; - candidate.meta = match.getValue().meta; - candidate.expression = match.getExpression(); - candidate.variableMap = match.getVariableMap(); - candidates.add(candidate); + tryMatch(request, path, candidates); + + if (candidates.isEmpty()) { + RestConfig restConfig = getRestConfig(); + if (restConfig.isTrailingSlashMatch()) { + int end = path.length() - 1; + if (end > 0 && path.charAt(end) == '/') { + tryMatch(request, path.substring(0, end), candidates); + } + } + if (candidates.isEmpty()) { + if (restConfig.isSuffixPatternMatch()) { + int index = path.lastIndexOf('.'); + if (index > -1 && contentNegotiator.supportExtension(path.substring(index + 1))) { + tryMatch(request, path.substring(0, index), candidates); + } + } } } - size = candidates.size(); + int size = candidates.size(); if (size == 0) { return null; } @@ -206,8 +208,73 @@ public HandlerMeta lookup(HttpRequest request) { return handler; } + private RestConfig getRestConfig() { + RestConfig restConfig = this.restConfig; + if (restConfig == null) { + Configuration conf = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); + restConfig = new RestConfig(); + restConfig.setSuffixPatternMatch(conf.getBoolean(RestConstants.SUFFIX_PATTERN_MATCH_KEY, true)); + restConfig.setTrailingSlashMatch(conf.getBoolean(RestConstants.TRAILING_SLASH_MATCH_KEY, true)); + restConfig.setCaseSensitiveMatch(conf.getBoolean(RestConstants.CASE_SENSITIVE_MATCH_KEY, true)); + this.restConfig = restConfig; + } + return restConfig; + } + + private void tryMatch(HttpRequest request, String path, List candidates) { + List> matches = new ArrayList<>(); + + lock.readLock().lock(); + try { + tree.match(path, matches); + } finally { + lock.readLock().unlock(); + } + + int size = matches.size(); + if (size == 0) { + return; + } + for (int i = 0; i < size; i++) { + Match match = matches.get(i); + RequestMapping mapping = match.getValue().mapping.match(request, match.getExpression()); + if (mapping != null) { + Candidate candidate = new Candidate(); + candidate.mapping = mapping; + candidate.meta = match.getValue().meta; + candidate.expression = match.getExpression(); + candidate.variableMap = match.getVariableMap(); + candidates.add(candidate); + } + } + } + @Override public boolean exists(String path, String method) { + if (tryExists(path, method)) { + return true; + } + + RestConfig restConfig = getRestConfig(); + if (restConfig.isTrailingSlashMatch()) { + int end = path.length() - 1; + if (end > 0 && path.charAt(end) == '/') { + if (tryExists(path.substring(0, end), method)) { + return true; + } + } + } + + if (restConfig.isSuffixPatternMatch()) { + int index = path.lastIndexOf('.'); + if (index > -1 && contentNegotiator.supportExtension(path.substring(index + 1))) { + return tryExists(path.substring(0, index), method); + } + } + return false; + } + + private boolean tryExists(String path, String method) { List> matches = new ArrayList<>(); lock.readLock().lock(); try { @@ -224,6 +291,7 @@ public boolean exists(String path, String method) { } private static final class Registration { + RequestMapping mapping; HandlerMeta meta; @@ -245,6 +313,7 @@ public int hashCode() { } private static final class Candidate { + RequestMapping mapping; HandlerMeta meta; PathExpression expression; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/Annotations.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/Annotations.java index 33e0e46289d..5a1ef216aa0 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/Annotations.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/Annotations.java @@ -21,23 +21,30 @@ import java.lang.annotation.Annotation; public enum Annotations implements AnnotationEnum { - Mapping(org.apache.dubbo.remoting.http12.rest.Mapping.class), - Param(org.apache.dubbo.remoting.http12.rest.Param.class), - Nonnull(javax.annotation.Nonnull.class); + Mapping, + Param, + Nonnull("javax.annotation.Nonnull"); - private final Class type; + private final String className; + private Class type; - @SuppressWarnings("unchecked") - Annotations(Class type) { - this.type = (Class) type; + Annotations(String className) { + this.className = className; + } + + Annotations() { + className = "org.apache.dubbo.remoting.http12.rest." + name(); } public String className() { - return type.getName(); + return className; } @Override public Class type() { + if (type == null) { + type = loadType(); + } return type; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy index 1701464f4e5..f89cffdc500 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy @@ -17,6 +17,8 @@ package org.apache.dubbo.rpc.protocol.tri.rest +import org.apache.dubbo.remoting.http12.HttpMethods +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.service.DemoServiceImpl import org.apache.dubbo.rpc.protocol.tri.rest.test.BaseServiceTest @@ -32,17 +34,19 @@ class RestProtocolTest extends BaseServiceTest { def "Hello world"() { given: - def request = new TestRequest(path: path) + def request = new TestRequest(path) expect: runner.run(request, String.class) == output where: - path | output - '/hello?name=world' | 'hello world' + path | output + '/hello?name=world' | 'hello world' + '/hello/?name=world' | 'hello world' + '/hello.yml?name=world' | 'hello world' } def "post test"() { given: - def request = new TestRequest(path: path).post(body) + def request = new TestRequest(path).post(body) expect: runner.run(request, String.class) == output where: @@ -55,7 +59,7 @@ class RestProtocolTest extends BaseServiceTest { def "bean test"() { given: - def request = new TestRequest(path: path).post(body) + def request = new TestRequest(path).post(body) expect: runner.run(request, Book.class).name == output where: @@ -63,4 +67,22 @@ class RestProtocolTest extends BaseServiceTest { '/buy' | [new Book(name: "Dubbo", price: 80, publishDate: new Date())] | 'Dubbo' '/buy' | ['book': new Book(name: "Dubbo", price: 80, publishDate: new Date())] | 'Dubbo' } + + def "urlEncodeForm test"() { + given: + def request = new TestRequest( + method: HttpMethods.POST, + path: path, + contentType: MediaType.APPLICATION_FROM_URLENCODED, + params: [ + 'name': 'Sam', + 'age' : 8 + ] + ) + expect: + runner.run(request, String.class) == output + where: + path | body | output + '/postTest' | '["Sam","8"]' | 'Sam is 8 years old' + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java index b4e5d24adba..d87830b0f0c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java @@ -16,12 +16,15 @@ */ package org.apache.dubbo.rpc.protocol.tri.test; +import org.apache.dubbo.remoting.http12.HttpHeaderNames; import org.apache.dubbo.remoting.http12.HttpHeaders; import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.h2.Http2Header; import org.apache.dubbo.remoting.http12.h2.Http2Headers; import org.apache.dubbo.remoting.http12.h2.Http2MetadataFrame; +import org.apache.dubbo.remoting.http12.message.MediaType; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -36,9 +39,9 @@ public class TestRequest { private final Map providerParams = new LinkedHashMap<>(); private List bodies; - public TestRequest(String path, HttpMethods method) { - setPath(path); + public TestRequest(HttpMethods method, String path) { setMethod(method); + setPath(path); } public TestRequest(String path) { @@ -47,29 +50,54 @@ public TestRequest(String path) { public TestRequest() {} - public String getMethod() { - return headers.getFirst(Http2Headers.METHOD.getName()); + public String getPath() { + return headers.getFirst(Http2Headers.PATH.getName()); } - public TestRequest setMethod(HttpMethods method) { - headers.set(Http2Headers.METHOD.getName(), method.name()); + public TestRequest setPath(String path) { + headers.set(Http2Headers.PATH.getName(), path); return this; } + public String getMethod() { + return headers.getFirst(Http2Headers.METHOD.getName()); + } + public TestRequest setMethod(String method) { headers.set(Http2Headers.METHOD.getName(), method); return this; } - public String getPath() { - return headers.getFirst(Http2Headers.PATH.getName()); + public TestRequest setMethod(HttpMethods method) { + return setMethod(method.name()); } - public TestRequest setPath(String path) { - headers.set(Http2Headers.PATH.getName(), path); + public TestRequest setContentType(String contentType) { + headers.set(HttpHeaderNames.CONTENT_TYPE.getName(), contentType); + return this; + } + + public TestRequest setContentType(MediaType mediaType) { + return setContentType(mediaType.getName()); + } + + public TestRequest setContentType(MediaType mediaType, String charset) { + return setContentType(mediaType.getName() + "; charset=" + charset); + } + + public TestRequest setContentType(MediaType mediaType, Charset charset) { + return setContentType(mediaType.getName() + "; charset=" + charset.name()); + } + + public TestRequest setAccept(String accept) { + headers.set(HttpHeaderNames.ACCEPT.getName(), accept); return this; } + public TestRequest setAccept(MediaType mediaType) { + return setAccept(mediaType.getName()); + } + public TestRequest setHeader(String name, Object value) { if (value != null) { headers.set(name, value.toString()); @@ -78,8 +106,8 @@ public TestRequest setHeader(String name, Object value) { } @SuppressWarnings("unchecked") - public TestRequest setHeaders(Map headers) { - for (Map.Entry entry : headers.entrySet()) { + public TestRequest setHeaders(Map headers) { + for (Map.Entry entry : headers.entrySet()) { Object value = entry.getValue(); if (value instanceof List) { List items = new ArrayList<>(); @@ -123,7 +151,7 @@ public TestRequest setParam(String name, Object value) { return this; } - public TestRequest setParams(Map params) { + public TestRequest setParams(Map params) { this.params.putAll(params); return this; } @@ -166,6 +194,11 @@ public TestRequest post(Object body) { return this; } + public TestRequest post() { + setMethod(HttpMethods.POST); + return this; + } + public Http2Header toMetadata() { return new Http2MetadataFrame(headers, !HttpMethods.supportBody(getMethod())); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java index 2e5deeae8b1..eaac8efaeff 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java @@ -42,14 +42,18 @@ public TestResponse(HttpHeaders headers, List oss, HttpMessageDeco this.decoder = decoder; } - public int getStatus() { - return Integer.parseInt(headers.getFirst(Http2Headers.STATUS.getName())); + public HttpHeaders getHeaders() { + return headers; } public String getHeader(String name) { return headers.getFirst(name); } + public int getStatus() { + return Integer.parseInt(headers.getFirst(Http2Headers.STATUS.getName())); + } + public String getContentType() { return headers.getFirst(HttpHeaderNames.CONTENT_TYPE.getName()); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java index fbb4c7cddba..57668559d67 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java @@ -28,6 +28,7 @@ import org.apache.dubbo.remoting.http12.h2.Http2InputMessageFrame; import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; +import org.apache.dubbo.remoting.http12.message.codec.JsonCodec; import org.apache.dubbo.rpc.Protocol; import org.apache.dubbo.rpc.ProxyFactory; import org.apache.dubbo.rpc.model.ApplicationModel; @@ -162,33 +163,36 @@ public TestResponse run(TestRequest request) { listener.onMetadata(request.toMetadata()); RpcInvocationBuildContext context = listener.getContext(); - HttpMessageEncoder encoder = context.getHttpMessageEncoder(); - HttpMessageDecoder decoder = context.getHttpMessageDecoder(); - HttpRequest hRequest = (HttpRequest) context.getAttributes().get(TripleConstant.HTTP_REQUEST_KEY); - if (CollectionUtils.isEmpty(request.getBodies())) { - if (HttpMethods.supportBody(hRequest.method())) { - listener.onData(END); - } - } else { - for (Object body : request.getBodies()) { - byte[] bytes; - if (body instanceof String) { - bytes = ((String) body).getBytes(StandardCharsets.UTF_8); - } else { - ByteArrayOutputStream bos = new ByteArrayOutputStream(256); - encoder.encode(bos, body); - bytes = bos.toByteArray(); + HttpMessageDecoder decoder = JsonCodec.INSTANCE; + if (context != null) { + HttpMessageEncoder encoder = context.getHttpMessageEncoder(); + decoder = context.getHttpMessageDecoder(); + HttpRequest hRequest = (HttpRequest) context.getAttributes().get(TripleConstant.HTTP_REQUEST_KEY); + if (CollectionUtils.isEmpty(request.getBodies())) { + if (HttpMethods.supportBody(hRequest.method())) { + listener.onData(END); + } + } else { + for (Object body : request.getBodies()) { + byte[] bytes; + if (body instanceof String) { + bytes = ((String) body).getBytes(StandardCharsets.UTF_8); + } else { + ByteArrayOutputStream bos = new ByteArrayOutputStream(256); + encoder.encode(bos, body); + bytes = bos.toByteArray(); + } + listener.onData(new Http2InputMessageFrame(new ByteArrayInputStream(bytes))); } - listener.onData(new Http2InputMessageFrame(new ByteArrayInputStream(bytes))); + listener.onData(END); } - listener.onData(END); - } - if (encoder instanceof RestHttpMessageCodec) { - encoder = ((RestHttpMessageCodec) encoder).getMessageEncoder(); - } - if (encoder instanceof HttpMessageDecoder) { - decoder = (HttpMessageDecoder) encoder; + if (encoder instanceof RestHttpMessageCodec) { + encoder = ((RestHttpMessageCodec) encoder).getMessageEncoder(); + } + if (encoder instanceof HttpMessageDecoder) { + decoder = (HttpMessageDecoder) encoder; + } } return new TestResponse(channel.getHttpMetadata().headers(), channel.getBodies(), decoder); } diff --git a/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTriple3AutoConfiguration.java b/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTriple3AutoConfiguration.java index 80fd90ef39f..246bfdce936 100644 --- a/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTriple3AutoConfiguration.java +++ b/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTriple3AutoConfiguration.java @@ -19,6 +19,8 @@ import org.apache.dubbo.rpc.protocol.tri.ServletExchanger; import org.apache.dubbo.rpc.protocol.tri.servlet.jakarta.TripleFilter; +import java.util.Collections; + import jakarta.servlet.Filter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -44,11 +46,13 @@ public static class TripleServletConfiguration { @Bean public FilterRegistrationBean tripleProtocolFilter( + @Value("${" + PREFIX + ".timeout:}") String timeout, @Value("${" + PREFIX + ".servlet-filter-url-patterns:/*}") String[] urlPatterns, @Value("${" + PREFIX + ".servlet-filter-order:-1000000}") int order, @Value("${server.port:8080}") int serverPort) { ServletExchanger.bindServerPort(serverPort); FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setInitParameters(Collections.singletonMap("timeout", timeout)); registrationBean.setFilter(new TripleFilter()); registrationBean.addUrlPatterns(urlPatterns); registrationBean.setOrder(order); diff --git a/dubbo-spring-boot/dubbo-spring-boot-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTripleAutoConfiguration.java b/dubbo-spring-boot/dubbo-spring-boot-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTripleAutoConfiguration.java index 114ab7cadf6..41dfc63e43c 100644 --- a/dubbo-spring-boot/dubbo-spring-boot-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTripleAutoConfiguration.java +++ b/dubbo-spring-boot/dubbo-spring-boot-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTripleAutoConfiguration.java @@ -21,6 +21,8 @@ import javax.servlet.Filter; +import java.util.Collections; + import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -35,7 +37,7 @@ @Conditional(SpringBoot12Condition.class) public class DubboTripleAutoConfiguration { - public static final String PREFIX = "dubbo.protocol.triple"; + public static final String PREFIX = "dubbo.protocol.triple.servlet"; @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Filter.class) @@ -45,11 +47,13 @@ public static class TripleServletConfiguration { @Bean public FilterRegistrationBean tripleProtocolFilter( - @Value("${" + PREFIX + ".servlet-filter-url-patterns:/*}") String[] urlPatterns, - @Value("${" + PREFIX + ".servlet-filter-order:-1000000}") int order, + @Value("${" + PREFIX + ".timeout:}") String timeout, + @Value("${" + PREFIX + ".filter-url-patterns:/*}") String[] urlPatterns, + @Value("${" + PREFIX + ".filter-order:-1000000}") int order, @Value("${server.port:8080}") int serverPort) { ServletExchanger.bindServerPort(serverPort); FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setInitParameters(Collections.singletonMap("timeout", timeout)); registrationBean.setFilter(new TripleFilter()); registrationBean.addUrlPatterns(urlPatterns); registrationBean.setOrder(order); diff --git a/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboConfigurationProperties.java b/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboConfigurationProperties.java index 3f52e12802d..50ad92c7a24 100644 --- a/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboConfigurationProperties.java +++ b/dubbo-spring-boot/dubbo-spring-boot-compatible/autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboConfigurationProperties.java @@ -27,10 +27,8 @@ import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.ProviderConfig; import org.apache.dubbo.config.RegistryConfig; -import org.apache.dubbo.config.RestConfig; import org.apache.dubbo.config.SslConfig; import org.apache.dubbo.config.TracingConfig; -import org.apache.dubbo.config.nested.TripleConfig; import java.util.LinkedHashMap; import java.util.Map; @@ -121,12 +119,6 @@ public class DubboConfigurationProperties { @NestedConfigurationProperty private SslConfig ssl = new SslConfig(); - /** - * Configuration properties for rpc. - */ - @NestedConfigurationProperty - private RpcConfig rpc = new RpcConfig(); - // Multiple Config Bindings /** @@ -275,14 +267,6 @@ public void setSsl(SslConfig ssl) { this.ssl = ssl; } - public RpcConfig getRpc() { - return rpc; - } - - public void setRpc(RpcConfig rpc) { - this.rpc = rpc; - } - public Map getModules() { return modules; } @@ -362,38 +346,4 @@ public Map getTracings() { public void setTracings(Map tracings) { this.tracings = tracings; } - - /** - * Configuration for rpc. - */ - public static class RpcConfig { - - /** - * The triple config. - */ - @NestedConfigurationProperty - private TripleConfig tri; - - /** - * The rest config. - */ - @NestedConfigurationProperty - private RestConfig rest; - - public TripleConfig getTri() { - return tri; - } - - public void setTri(TripleConfig tri) { - this.tri = tri; - } - - public RestConfig getRest() { - return rest; - } - - public void setRest(RestConfig rest) { - this.rest = rest; - } - } } From 7e5394fc3d0a81f3990b02e3b95890c4a1da38f0 Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Tue, 18 Jun 2024 12:47:40 +0800 Subject: [PATCH 03/12] Rest override support --- .github/workflows/build-and-test-pr.yml | 2 +- .../src/main/resources/application.yml | 3 +- .../filter/AbstractObservationFilterTest.java | 84 ------------ .../filter/ObservationReceiverFilterTest.java | 128 ------------------ .../filter/ObservationSenderFilterTest.java | 76 ----------- .../tri/rest/support/spring/Annotations.java | 1 + .../SpringMvcRequestMappingResolver.java | 36 +++-- .../support/spring/SpringRestToolKit.java | 7 + .../tri/servlet/HttpMetadataAdapter.java | 6 +- .../dubbo/remoting/http12/HttpRequest.java | 2 + .../http12/message/DefaultHttpRequest.java | 9 ++ .../dubbo/rpc/protocol/tri/rest/Messages.java | 2 +- .../rpc/protocol/tri/rest/RestConstants.java | 1 + .../DefaultRequestMappingRegistry.java | 126 +++++++++++------ .../protocol/tri/rest/mapping/RadixTree.java | 97 ++++--------- .../tri/rest/mapping/RequestMapping.java | 88 +++++++++--- .../mapping/condition/PathExpression.java | 3 +- .../rest/mapping/condition/PathSegment.java | 12 +- .../tri/rest/mapping/meta/CorsMeta.java | 8 +- .../basic/BasicRequestMappingResolver.java | 63 ++++++--- .../rpc/protocol/tri/rest/util/KeyString.java | 118 ++++++++++++++++ .../rpc/protocol/tri/rest/util/TypeUtils.java | 10 ++ .../protocol/tri/rest/RestProtocolTest.groovy | 27 +++- .../tri/rest/service/DemoService.java | 4 + .../tri/rest/service/DemoServiceImpl.java | 10 ++ .../rpc/protocol/tri/test/TestRequest.java | 2 +- .../DubboTriple3AutoConfiguration.java | 4 +- .../DubboTripleAutoConfiguration.java | 2 +- 28 files changed, 466 insertions(+), 465 deletions(-) delete mode 100644 dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/AbstractObservationFilterTest.java delete mode 100644 dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationReceiverFilterTest.java delete mode 100644 dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationSenderFilterTest.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/KeyString.java diff --git a/.github/workflows/build-and-test-pr.yml b/.github/workflows/build-and-test-pr.yml index f179ec54957..7a40dbca157 100644 --- a/.github/workflows/build-and-test-pr.yml +++ b/.github/workflows/build-and-test-pr.yml @@ -326,7 +326,7 @@ jobs: cd test && bash ./build-test-image.sh - name: "Run tests" run: cd test && bash ./run-tests.sh - - name: "merge jacoco resule" + - name: "merge jacoco result" run: | cd test/dubbo-test-jacoco-merger && mvn clean compile exec:java -Dexec.mainClass="org.apache.dubbo.test.JacocoMerge" -Dexec.args="${{github.workspace}}" - name: "Upload jacoco" diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml index ec0ba148c1c..e2642b66125 100644 --- a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml +++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml @@ -24,7 +24,8 @@ dubbo: qos-enable: false protocol: name: tri - port: ${server.port} + port: -1 + #port: ${server.port} triple: servlet: enable: true diff --git a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/AbstractObservationFilterTest.java b/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/AbstractObservationFilterTest.java deleted file mode 100644 index ce56ae3662a..00000000000 --- a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/AbstractObservationFilterTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.tracing.filter; - -import org.apache.dubbo.config.ApplicationConfig; -import org.apache.dubbo.config.TracingConfig; -import org.apache.dubbo.rpc.AppResponse; -import org.apache.dubbo.rpc.BaseFilter; -import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.RpcInvocation; -import org.apache.dubbo.rpc.model.ApplicationModel; -import org.apache.dubbo.tracing.MockInvocation; - -import io.micrometer.tracing.test.SampleTestRunner; -import org.junit.jupiter.api.AfterEach; - -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -abstract class AbstractObservationFilterTest extends SampleTestRunner { - - ApplicationModel applicationModel; - RpcInvocation invocation; - - BaseFilter filter; - - Invoker invoker = mock(Invoker.class); - - static final String INTERFACE_NAME = "org.apache.dubbo.MockInterface"; - static final String METHOD_NAME = "mockMethod"; - static final String GROUP = "mockGroup"; - static final String VERSION = "1.0.0"; - - @AfterEach - public void teardown() { - if (applicationModel != null) { - applicationModel.destroy(); - } - } - - abstract BaseFilter createFilter(ApplicationModel applicationModel); - - void setupConfig() { - ApplicationConfig config = new ApplicationConfig(); - config.setName("MockObservations"); - - applicationModel = ApplicationModel.defaultModel(); - applicationModel.getApplicationConfigManager().setApplication(config); - - invocation = new RpcInvocation(new MockInvocation()); - invocation.addInvokedInvoker(invoker); - - applicationModel.getBeanFactory().registerBean(getObservationRegistry()); - TracingConfig tracingConfig = new TracingConfig(); - tracingConfig.setEnabled(true); - applicationModel.getApplicationConfigManager().setTracing(tracingConfig); - - filter = createFilter(applicationModel); - - given(invoker.invoke(invocation)).willReturn(new AppResponse("success")); - - initParam(); - } - - private void initParam() { - invocation.setTargetServiceUniqueName(GROUP + "/" + INTERFACE_NAME + ":" + VERSION); - invocation.setMethodName(METHOD_NAME); - invocation.setParameterTypes(new Class[] {String.class}); - } -} diff --git a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationReceiverFilterTest.java b/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationReceiverFilterTest.java deleted file mode 100644 index 76eac81ad99..00000000000 --- a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationReceiverFilterTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.tracing.filter; - -import org.apache.dubbo.common.URL; -import org.apache.dubbo.rpc.AppResponse; -import org.apache.dubbo.rpc.Filter; -import org.apache.dubbo.rpc.Invocation; -import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.Result; -import org.apache.dubbo.rpc.RpcContext; -import org.apache.dubbo.rpc.RpcException; -import org.apache.dubbo.rpc.model.ApplicationModel; - -import io.micrometer.common.KeyValues; -import io.micrometer.core.tck.MeterRegistryAssert; -import io.micrometer.tracing.Span; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.test.simple.SpansAssert; -import org.assertj.core.api.BDDAssertions; - -class ObservationReceiverFilterTest extends AbstractObservationFilterTest { - - @Override - public SampleTestRunnerConsumer yourCode() { - return (buildingBlocks, meterRegistry) -> { - setupConfig(); - setupAttachments(buildingBlocks.getTracer()); - invoker = new AssertingInvoker(buildingBlocks.getTracer()); - - ObservationReceiverFilter senderFilter = (ObservationReceiverFilter) filter; - senderFilter.invoke(invoker, invocation); - senderFilter.onResponse(null, invoker, invocation); - - MeterRegistryAssert.then(meterRegistry) - .hasMeterWithNameAndTags( - "rpc.server.duration", - KeyValues.of( - "rpc.method", - "mockMethod", - "rpc.service", - "DemoService", - "rpc.system", - "apache_dubbo")); - SpansAssert.then(buildingBlocks.getFinishedSpans()) - .hasASpanWithNameIgnoreCase("DemoService/mockMethod", spanAssert -> spanAssert - .hasTag("rpc.method", "mockMethod") - .hasTag("rpc.service", "DemoService") - .hasTag("rpc.system", "apache_dubbo")); - }; - } - - void setupAttachments(Tracer tracer) { - RpcContext.getServerAttachment() - .setUrl(URL.valueOf("test://test:11/test?accesslog=true&group=dubbo&version=1.1&side=consumer")); - RpcContext.getServerAttachment().setMethodName("foo"); - RpcContext.getServerAttachment().setRemoteAddress("foo.bar.com", 8080); - RpcContext.getServerAttachment() - .setAttachment("X-B3-TraceId", tracer.currentSpan().context().traceId()); - RpcContext.getServerAttachment() - .setAttachment("X-B3-SpanId", tracer.currentSpan().context().spanId()); - RpcContext.getServerAttachment().setAttachment("X-B3-Sampled", "1"); - } - - @Override - Filter createFilter(ApplicationModel applicationModel) { - return new ObservationReceiverFilter(applicationModel); - } - - static class AssertingInvoker implements Invoker { - - private final String expectedTraceId; - - private final String parentSpanId; - - private final Tracer tracer; - - AssertingInvoker(Tracer tracer) { - this.tracer = tracer; - this.expectedTraceId = tracer.currentSpan().context().traceId(); - this.parentSpanId = tracer.currentSpan().context().spanId(); - } - - @Override - public URL getUrl() { - return null; - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public void destroy() {} - - @Override - public Class getInterface() { - return AssertingInvoker.class; - } - - @Override - public Result invoke(Invocation invocation) throws RpcException { - Span span = this.tracer.currentSpan(); - BDDAssertions.then(span.context().traceId()) - .as("Should propagate the trace id from the attributes") - .isEqualTo(this.expectedTraceId); - BDDAssertions.then(span.context().spanId()) - .as("A child span must be created") - .isNotEqualTo(this.parentSpanId); - return new AppResponse("OK"); - } - } -} diff --git a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationSenderFilterTest.java b/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationSenderFilterTest.java deleted file mode 100644 index 8239c4128ee..00000000000 --- a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationSenderFilterTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.tracing.filter; - -import org.apache.dubbo.common.URL; -import org.apache.dubbo.rpc.RpcContext; -import org.apache.dubbo.rpc.cluster.filter.ClusterFilter; -import org.apache.dubbo.rpc.model.ApplicationModel; - -import io.micrometer.common.KeyValues; -import io.micrometer.core.tck.MeterRegistryAssert; -import io.micrometer.tracing.test.simple.SpansAssert; -import org.assertj.core.api.BDDAssertions; - -class ObservationSenderFilterTest extends AbstractObservationFilterTest { - - @Override - public SampleTestRunnerConsumer yourCode() { - return (buildingBlocks, meterRegistry) -> { - setupConfig(); - setupAttachments(); - - ObservationSenderFilter senderFilter = (ObservationSenderFilter) filter; - senderFilter.invoke(invoker, invocation); - senderFilter.onResponse(null, invoker, invocation); - - BDDAssertions.then(invocation.getObjectAttachment("X-B3-TraceId")).isNotNull(); - MeterRegistryAssert.then(meterRegistry) - .hasMeterWithNameAndTags( - "rpc.client.duration", - KeyValues.of( - "net.peer.name", - "foo.bar.com", - "net.peer.port", - "8080", - "rpc.method", - "mockMethod", - "rpc.service", - "DemoService", - "rpc.system", - "apache_dubbo")); - SpansAssert.then(buildingBlocks.getFinishedSpans()) - .hasASpanWithNameIgnoreCase("DemoService/mockMethod", spanAssert -> spanAssert - .hasTag("net.peer.name", "foo.bar.com") - .hasTag("net.peer.port", "8080") - .hasTag("rpc.method", "mockMethod") - .hasTag("rpc.service", "DemoService") - .hasTag("rpc.system", "apache_dubbo")); - }; - } - - void setupAttachments() { - RpcContext.getClientAttachment() - .setUrl(URL.valueOf("test://test:11/test?accesslog=true&group=dubbo&version=1.1&side=consumer")); - RpcContext.getClientAttachment().setRemoteAddress("foo.bar.com", 8080); - } - - @Override - ClusterFilter createFilter(ApplicationModel applicationModel) { - return new ObservationSenderFilter(applicationModel); - } -} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java index be8b1c58652..974a56dea2d 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java @@ -34,6 +34,7 @@ public enum Annotations implements AnnotationEnum { ResponseStatus, CrossOrigin, ExceptionHandler, + HttpExchange("org.springframework.web.service.annotation.HttpExchange"), Nonnull("javax.annotation.Nonnull"); private final String className; 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 e6de4f568bd..a43bb50a741 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 @@ -62,44 +62,49 @@ public RestToolKit getRestToolKit() { @Override public RequestMapping resolve(ServiceMeta serviceMeta) { AnnotationMeta requestMapping = serviceMeta.findMergedAnnotation(Annotations.RequestMapping); - if (requestMapping == null) { + AnnotationMeta httpExchange = serviceMeta.findMergedAnnotation(Annotations.HttpExchange); + if (requestMapping == null && httpExchange == null) { return null; } - AnnotationMeta responseStatus = serviceMeta.findMergedAnnotation(Annotations.ResponseStatus); - AnnotationMeta crossOrigin = serviceMeta.findMergedAnnotation(Annotations.CrossOrigin); - String[] methods = requestMapping.getStringArray("method"); - return builder(requestMapping, responseStatus) + + String[] methods = requestMapping == null + ? httpExchange.getStringArray("method") + : requestMapping.getStringArray("method"); + return builder(requestMapping, httpExchange, serviceMeta.findMergedAnnotation(Annotations.ResponseStatus)) .method(methods) .name(serviceMeta.getType().getSimpleName()) .contextPath(serviceMeta.getContextPath()) - .cors(buildCorsMeta(crossOrigin, methods)) + .cors(buildCorsMeta(serviceMeta.findMergedAnnotation(Annotations.CrossOrigin), methods)) .build(); } @Override public RequestMapping resolve(MethodMeta methodMeta) { AnnotationMeta requestMapping = methodMeta.findMergedAnnotation(Annotations.RequestMapping); - if (requestMapping == null) { + AnnotationMeta httpExchange = methodMeta.findMergedAnnotation(Annotations.HttpExchange); + if (requestMapping == null && httpExchange == null) { AnnotationMeta exceptionHandler = methodMeta.getAnnotation(Annotations.ExceptionHandler); if (exceptionHandler != null) { methodMeta.getServiceMeta().addExceptionHandler(methodMeta); } return null; } + ServiceMeta serviceMeta = methodMeta.getServiceMeta(); - AnnotationMeta responseStatus = methodMeta.findMergedAnnotation(Annotations.ResponseStatus); - AnnotationMeta crossOrigin = methodMeta.findMergedAnnotation(Annotations.CrossOrigin); - String[] methods = requestMapping.getStringArray("method"); - return builder(requestMapping, responseStatus) + String[] methods = requestMapping == null + ? httpExchange.getStringArray("method") + : requestMapping.getStringArray("method"); + return builder(requestMapping, httpExchange, methodMeta.findMergedAnnotation(Annotations.ResponseStatus)) .method(methods) .name(methodMeta.getMethod().getName()) .contextPath(serviceMeta.getContextPath()) .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) - .cors(buildCorsMeta(crossOrigin, methods)) + .cors(buildCorsMeta(methodMeta.findMergedAnnotation(Annotations.CrossOrigin), methods)) .build(); } - private Builder builder(AnnotationMeta requestMapping, AnnotationMeta responseStatus) { + private Builder builder( + AnnotationMeta requestMapping, AnnotationMeta httpExchange, AnnotationMeta responseStatus) { Builder builder = RequestMapping.builder(); if (responseStatus != null) { HttpStatus value = responseStatus.getEnum("value"); @@ -109,6 +114,11 @@ private Builder builder(AnnotationMeta requestMapping, AnnotationMeta resp builder.responseReason(reason); } } + if (requestMapping == null) { + return builder.path(httpExchange.getValueArray()) + .consume(httpExchange.getStringArray("contentType")) + .produce(httpExchange.getStringArray("accept")); + } return builder.path(requestMapping.getValueArray()) .param(requestMapping.getStringArray("params")) .header(requestMapping.getStringArray("headers")) 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 9e5011b683c..a2638c7748d 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 @@ -32,6 +32,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Map; import org.springframework.beans.factory.config.ConfigurableBeanFactory; @@ -97,6 +98,12 @@ public String resolvePlaceholders(String text) { @Override public Object convert(Object value, ParameterMeta parameter) { + if (value instanceof Collection) { + Object target = typeConverter.convert(value, parameter.getGenericType()); + if (target != null) { + return target; + } + } TypeDescriptor targetType = (TypeDescriptor) parameter.getTypeDescriptor(); if (targetType == null) { MethodParameterMeta meta = (MethodParameterMeta) parameter; diff --git a/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/HttpMetadataAdapter.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/HttpMetadataAdapter.java index 15af2654c7e..83e29af618f 100644 --- a/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/HttpMetadataAdapter.java +++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/HttpMetadataAdapter.java @@ -25,7 +25,7 @@ import java.util.Enumeration; import java.util.List; -final class HttpMetadataAdapter implements Http2Header { +public final class HttpMetadataAdapter implements Http2Header { private final HttpServletRequest request; @@ -35,6 +35,10 @@ final class HttpMetadataAdapter implements Http2Header { this.request = request; } + public HttpServletRequest getRequest() { + return request; + } + @Override public HttpHeaders headers() { HttpHeaders headers = this.headers; diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java index 50fcc001d73..4aa03ff4a50 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java @@ -116,6 +116,8 @@ public interface HttpRequest extends RequestMetadata { Collection queryParameterNames(); + Map> queryParameters(); + String formParameter(String name); List formParameterValues(String name); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java index 41dea99f99f..cc67e9544be 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java @@ -81,6 +81,10 @@ public DefaultHttpRequest(HttpMetadata metadata, HttpChannel channel) { } } + public HttpMetadata getMetadata() { + return metadata; + } + @Override public boolean isHttp2() { return metadata instanceof Http2Header; @@ -436,6 +440,11 @@ public Collection queryParameterNames() { return getDecoder().parameters().keySet(); } + @Override + public Map> queryParameters() { + return getDecoder().parameters(); + } + @Override public String formParameter(String name) { HttpPostRequestDecoder postDecoder = getPostDecoder(); 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 e1493f428d5..2abcff12207 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 @@ -31,7 +31,7 @@ public enum Messages { NO_MORE_DATA_ALLOWED("No more data allowed after '{*...}' or '**' pattern segment for path ''{0}'' at index {1}"), CANNOT_COMBINE_PATHS("Cannot combine paths: ''{0}'' vs ''{1}''"), DUPLICATE_MAPPING("Duplicate mapping for ''{0}'': current={1}, exists={2}"), - AMBIGUOUS_MAPPING("Ambiguous mapping for ''{0}'': {{1}, {2}}"), + 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."), diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java index c8d8bdf131e..d267d581443 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java @@ -41,6 +41,7 @@ public final class RestConstants { /* Request Attribute */ public static final String BODY_ATTRIBUTE = HttpRequest.class.getName() + ".body"; public static final String BODY_DECODER_ATTRIBUTE = HttpMessageDecoder.class.getName() + ".body"; + public static final String SIG_ATTRIBUTE = RequestMapping.class.getName() + ".sig"; public static final String MAPPING_ATTRIBUTE = RequestMapping.class.getName(); public static final String HANDLER_ATTRIBUTE = HandlerMeta.class.getName(); public static final String PATH_ATTRIBUTE = "org.springframework.web.util.UrlPathHelper.PATH"; 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 ac3dd6880f2..e7291ce7fda 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 @@ -34,6 +34,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.KeyString; import org.apache.dubbo.rpc.protocol.tri.rest.util.MethodWalker; import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils; import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; @@ -42,6 +43,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -50,22 +52,43 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegistry { private final FrameworkModel frameworkModel; - private final List resolvers; private final ContentNegotiator contentNegotiator; - - private final RadixTree tree = new RadixTree<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final AtomicBoolean initialized = new AtomicBoolean(); private RestConfig restConfig; + private List resolvers; + private RadixTree tree; public DefaultRequestMappingRegistry(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; - resolvers = frameworkModel.getActivateExtensions(RequestMappingResolver.class); contentNegotiator = frameworkModel.getBeanFactory().getOrRegisterBean(ContentNegotiator.class); } + private void init() { + Configuration conf = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); + restConfig = new RestConfig(); + restConfig.setSuffixPatternMatch(conf.getBoolean(RestConstants.SUFFIX_PATTERN_MATCH_KEY, true)); + restConfig.setTrailingSlashMatch(conf.getBoolean(RestConstants.TRAILING_SLASH_MATCH_KEY, true)); + restConfig.setCaseSensitiveMatch(conf.getBoolean(RestConstants.CASE_SENSITIVE_MATCH_KEY, true)); + + resolvers = frameworkModel.getActivateExtensions(RequestMappingResolver.class); + tree = new RadixTree<>(restConfig.isCaseSensitiveMatch()); + } + @Override public void register(Invoker invoker) { + if (tree == null) { + lock.writeLock().lock(); + try { + if (initialized.compareAndSet(false, true)) { + init(); + } + } finally { + lock.writeLock().unlock(); + } + } + Object service = invoker.getUrl().getServiceModel().getProxyObject(); ServiceDescriptor serviceDescriptor = DescriptorUtils.getReflectionServiceDescriptor(invoker.getUrl()); if (serviceDescriptor == null) { @@ -81,9 +104,8 @@ public void register(Invoker invoker) { } RequestMapping classMapping = resolver.resolve(serviceMeta); consumer.accept((methods) -> { - Method method = methods.get(0); - MethodDescriptor methodDescriptor = - serviceDescriptor.getMethod(method.getName(), method.getParameterTypes()); + Method m = methods.get(0); + MethodDescriptor methodDescriptor = serviceDescriptor.getMethod(m.getName(), m.getParameterTypes()); if (methodDescriptor == null) { return; } @@ -145,23 +167,42 @@ public void destroy() { public HandlerMeta lookup(HttpRequest request) { String path = PathUtils.normalize(request.rawPath()); request.setAttribute(RestConstants.PATH_ATTRIBUTE, path); - List candidates = new ArrayList<>(); - tryMatch(request, path, candidates); + List candidates = new ArrayList<>(); + boolean cs = restConfig.isCaseSensitiveMatch(); + tryMatch(request, new KeyString(path, cs), candidates); if (candidates.isEmpty()) { - RestConfig restConfig = getRestConfig(); + int end = path.length(); + if (restConfig.isTrailingSlashMatch()) { - int end = path.length() - 1; - if (end > 0 && path.charAt(end) == '/') { - tryMatch(request, path.substring(0, end), candidates); + if (path.charAt(end - 1) == '/') { + end--; + tryMatch(request, new KeyString(path, end, cs), candidates); } } + if (candidates.isEmpty()) { - if (restConfig.isSuffixPatternMatch()) { - int index = path.lastIndexOf('.'); - if (index > -1 && contentNegotiator.supportExtension(path.substring(index + 1))) { - tryMatch(request, path.substring(0, index), candidates); + for (int i = end - 1; i >= 0; i--) { + char ch = path.charAt(i); + if (ch == '/') { + break; + } + if (ch == '.' && restConfig.isSuffixPatternMatch()) { + if (contentNegotiator.supportExtension(path.substring(i + 1, end))) { + tryMatch(request, new KeyString(path, i, cs), candidates); + if (!candidates.isEmpty()) { + break; + } + end = i; + } + } + if (ch == '~') { + request.setAttribute(RestConstants.SIG_ATTRIBUTE, path.substring(i + 1, end)); + tryMatch(request, new KeyString(path, i, cs), candidates); + if (!candidates.isEmpty()) { + break; + } } } } @@ -186,7 +227,8 @@ public HandlerMeta lookup(HttpRequest request) { Candidate first = candidates.get(0); Candidate second = candidates.get(1); if (first.mapping.compareTo(second.mapping, request) == 0) { - throw new RestInitializeException(AMBIGUOUS_MAPPING, path, first.mapping, second.mapping); + throw new RestInitializeException( + AMBIGUOUS_MAPPING, path, first.mapping.getName(), second.mapping.getName()); } } @@ -208,20 +250,7 @@ public HandlerMeta lookup(HttpRequest request) { return handler; } - private RestConfig getRestConfig() { - RestConfig restConfig = this.restConfig; - if (restConfig == null) { - Configuration conf = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); - restConfig = new RestConfig(); - restConfig.setSuffixPatternMatch(conf.getBoolean(RestConstants.SUFFIX_PATTERN_MATCH_KEY, true)); - restConfig.setTrailingSlashMatch(conf.getBoolean(RestConstants.TRAILING_SLASH_MATCH_KEY, true)); - restConfig.setCaseSensitiveMatch(conf.getBoolean(RestConstants.CASE_SENSITIVE_MATCH_KEY, true)); - this.restConfig = restConfig; - } - return restConfig; - } - - private void tryMatch(HttpRequest request, String path, List candidates) { + private void tryMatch(HttpRequest request, KeyString path, List candidates) { List> matches = new ArrayList<>(); lock.readLock().lock(); @@ -251,30 +280,43 @@ private void tryMatch(HttpRequest request, String path, List candidat @Override public boolean exists(String path, String method) { - if (tryExists(path, method)) { + boolean cs = restConfig.isCaseSensitiveMatch(); + if (tryExists(new KeyString(path, cs), method)) { return true; } - RestConfig restConfig = getRestConfig(); + int end = path.length(); if (restConfig.isTrailingSlashMatch()) { - int end = path.length() - 1; - if (end > 0 && path.charAt(end) == '/') { - if (tryExists(path.substring(0, end), method)) { + if (path.charAt(end - 1) == '/') { + end--; + if (tryExists(new KeyString(path, end, cs), method)) { return true; } } } - if (restConfig.isSuffixPatternMatch()) { - int index = path.lastIndexOf('.'); - if (index > -1 && contentNegotiator.supportExtension(path.substring(index + 1))) { - return tryExists(path.substring(0, index), method); + for (int i = end - 1; i >= 0; i--) { + char ch = path.charAt(i); + if (ch == '/') { + break; + } + if (ch == '.' && restConfig.isSuffixPatternMatch()) { + if (contentNegotiator.supportExtension(path.substring(i + 1, end))) { + if (tryExists(new KeyString(path, i, cs), method)) { + return true; + } + end = i; + } + } + if (ch == '~') { + return tryExists(new KeyString(path, i, cs), method); } } + return false; } - private boolean tryExists(String path, String method) { + private boolean tryExists(KeyString path, String method) { List> matches = new ArrayList<>(); lock.readLock().lock(); try { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java index 93409b64048..7e45bced4ef 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java @@ -20,6 +20,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type; +import org.apache.dubbo.rpc.protocol.tri.rest.util.KeyString; import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils; import java.util.ArrayList; @@ -39,12 +40,22 @@ */ public final class RadixTree { - private final Map>> directPathMap = new HashMap<>(); + private final Map>> directPathMap = new HashMap<>(); private final Node root = new Node<>(); + private final boolean caseSensitive; + + public RadixTree(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + } + + public RadixTree() { + caseSensitive = true; + } public T addPath(PathExpression path, T value) { if (path.isDirect()) { - List> matches = directPathMap.computeIfAbsent(path.getPath(), k -> new ArrayList<>()); + KeyString key = new KeyString(path.getPath(), caseSensitive); + List> matches = directPathMap.computeIfAbsent(key, k -> new ArrayList<>()); for (int i = 0, len = matches.size(); i < len; i++) { Match match = matches.get(i); if (match.getValue().equals(value)) { @@ -77,11 +88,11 @@ public T addPath(String path, T value) { return addPath(PathExpression.parse(PathUtils.normalize(path)), value); } - private static Node getChild(Node current, PathSegment segment) { + private Node getChild(Node current, PathSegment segment) { Node child; if (segment.getType() == Type.LITERAL) { - Map> children = current.children; - Key key = new Key(segment.getValue()); + Map> children = current.children; + KeyString key = new KeyString(segment.getValue(), caseSensitive); child = children.get(key); if (child == null) { child = new Node<>(); @@ -128,7 +139,7 @@ private void removeRecursive(Node current, Predicate tester) { /** * Ensure that the path is normalized using {@link PathUtils#normalize(String)} before matching. */ - public void match(String path, List> matches) { + public void match(KeyString path, List> matches) { List> directMatches = directPathMap.get(path); if (directMatches != null) { for (int i = 0, size = directMatches.size(); i < size; i++) { @@ -140,7 +151,11 @@ public void match(String path, List> matches) { matchRecursive(root, path, 1, new HashMap<>(), matches); } - public List> match(String path) { + public void match(String path, List> matches) { + match(new KeyString(path, caseSensitive), matches); + } + + public List> match(KeyString path) { List> matches = directPathMap.get(path); if (matches != null) { return new ArrayList<>(matches); @@ -151,10 +166,14 @@ public List> match(String path) { return matches; } + public List> match(String path) { + return match(new KeyString(path, caseSensitive)); + } + private void matchRecursive( - Node current, String path, int start, Map variableMap, List> matches) { + Node current, KeyString path, int start, Map variableMap, List> matches) { int end = path.indexOf('/', start); - Node node = current.children.get(new Key(path, start, end)); + Node node = current.children.get(new KeyString(path, start, end)); if (node != null) { if (node.isLeaf()) { addMatch(node, variableMap, matches); @@ -239,67 +258,9 @@ public int compareTo(Match other) { } } - /** - * Zero-copy string key. - */ - private static final class Key implements CharSequence { - - private final String value; - private final int offset; - private final int length; - - private Key(String value, int start, int end) { - this.value = value; - offset = start; - length = (end == -1 ? value.length() : end) - start; - } - - public Key(String value) { - this.value = value; - offset = 0; - length = value.length(); - } - - @Override - public int length() { - return length; - } - - @Override - public char charAt(int index) { - return value.charAt(offset + index); - } - - @Override - public CharSequence subSequence(int start, int end) { - return value.substring(offset + start, offset + end); - } - - @Override - public int hashCode() { - int h = 0; - for (int i = 0; i < length; i++) { - h = 31 * h + value.charAt(offset + i); - } - return h; - } - - @Override - @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") - public boolean equals(Object obj) { - Key other = (Key) obj; - return value.regionMatches(offset, other.value, other.offset, length); - } - - @Override - public String toString() { - return value.substring(offset, length - offset); - } - } - private static final class Node { - private final Map> children = new HashMap<>(); + private final Map> children = new HashMap<>(); private final Map> fuzzyChildren = new HashMap<>(); private final List> values = new ArrayList<>(); 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 7e38cdf499b..bd84d96f3e4 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 @@ -16,8 +16,10 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.Condition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ConditionWrapper; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ConsumesCondition; @@ -37,6 +39,7 @@ public final class RequestMapping implements Condition { private final String name; + private final String sig; private final PathCondition pathCondition; private final MethodsCondition methodsCondition; private final ParamsCondition paramsCondition; @@ -51,6 +54,7 @@ public final class RequestMapping implements Condition> T combine(T source, T other) { - return source == null ? other : other == null ? source : source.combine(other); + private String combineName(String name, String otherName, String otherSig) { + if (name == null) { + name = otherName; + } else if (otherName != null) { + name += '#' + otherName; + } + return otherSig == null ? name : name + '~' + otherSig; } - private CorsMeta combine(CorsMeta source, CorsMeta other) { - return source == null || source.isEmpty() - ? other == null || other.isEmpty() ? null : other.applyDefault() - : source.combine(other).applyDefault(); + private > T combine(T source, T other) { + return source == null ? other : other == null ? source : source.combine(other); } public RequestMapping match(HttpRequest request, PathExpression path) { @@ -171,13 +180,25 @@ private RequestMapping doMatch(HttpRequest request, PathCondition pathCondition) } } - return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, cors, response); + if (StringUtils.isNotEmpty(sig)) { + String rSig = request.attribute(RestConstants.SIG_ATTRIBUTE); + if (rSig != null && !rSig.equals(sig)) { + return null; + } + } + + return new RequestMapping( + name, sig, paths, methods, params, headers, consumes, produces, custom, cors, response); } public String getName() { return name; } + public String getSig() { + return sig; + } + public PathCondition getPathCondition() { return pathCondition; } @@ -241,7 +262,21 @@ public int compareTo(RequestMapping other, HttpRequest request) { } if (customCondition != null) { result = customCondition.compareTo(other.customCondition, request); - return result; + if (result != 0) { + return result; + } + } + if (sig != null && other.sig != null) { + int size = request.queryParameters().size(); + int size1 = sig.length(); + int size2 = other.sig.length(); + if (size1 == size) { + if (size2 != size) { + return -1; + } + } else if (size2 == size) { + return 1; + } } return 0; } @@ -257,7 +292,8 @@ public int hashCode() { headersCondition, consumesCondition, producesCondition, - customCondition); + customCondition, + sig); this.hashCode = hashCode; } return hashCode; @@ -278,13 +314,15 @@ public boolean equals(Object obj) { && Objects.equals(headersCondition, other.headersCondition) && Objects.equals(consumesCondition, other.consumesCondition) && Objects.equals(producesCondition, other.producesCondition) - && Objects.equals(customCondition, other.customCondition); + && Objects.equals(customCondition, other.customCondition) + && Objects.equals(sig, other.sig); } @Override public String toString() { StringBuilder sb = new StringBuilder("RequestMapping{name='"); sb.append(name).append('\''); + sb.append('\''); if (pathCondition != null) { sb.append(", pathCondition=").append(pathCondition); } @@ -317,7 +355,9 @@ public String toString() { } public static final class Builder { + private String name; + private String sig; private String contextPath; private String[] paths; private String[] methods; @@ -335,6 +375,11 @@ public Builder name(String name) { return this; } + public Builder sig(String sig) { + this.sig = sig; + return this; + } + public Builder contextPath(String contextPath) { this.contextPath = contextPath; return this; @@ -393,6 +438,7 @@ public Builder responseReason(String reason) { public RequestMapping build() { return new RequestMapping( name, + sig, isEmpty(paths) ? null : new PathCondition(contextPath, paths), isEmpty(methods) ? null : new MethodsCondition(methods), isEmpty(params) ? null : new ParamsCondition(params), diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java index 6e04ed417d5..ead98873a01 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java @@ -17,6 +17,7 @@ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type; +import org.apache.dubbo.rpc.protocol.tri.rest.util.KeyString; import javax.annotation.Nonnull; @@ -64,7 +65,7 @@ public Map match(@Nonnull String path) { if (end != -1) { start = end + 1; end = path.indexOf('/', start); - if (segment.match(path, start, end, variableMap)) { + if (segment.match(new KeyString(path), start, end, variableMap)) { continue; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java index 0411f7f3b37..e31ccaebf1e 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java @@ -16,9 +16,9 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; -import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.protocol.tri.rest.Messages; import org.apache.dubbo.rpc.protocol.tri.rest.PathParserException; +import org.apache.dubbo.rpc.protocol.tri.rest.util.KeyString; import java.util.ArrayList; import java.util.List; @@ -106,7 +106,7 @@ public boolean isTailMatching() { return type == Type.WILDCARD_TAIL || type == Type.PATTERN_MULTI; } - public boolean match(String path, int start, int end, Map variableMap) { + public boolean match(KeyString path, int start, int end, Map variableMap) { switch (type) { case SLASH: case LITERAL: @@ -118,11 +118,11 @@ public boolean match(String path, int start, int end, Map variab return true; case VARIABLE: if (variables != null) { - variableMap.put(getVariable(), StringUtils.substring(path, start, end)); + variableMap.put(getVariable(), path.substring(start, end)); } return true; case PATTERN: - return matchPattern(StringUtils.substring(path, start, end), variableMap); + return matchPattern(path.substring(start, end), variableMap); case PATTERN_MULTI: return matchPattern(path.substring(start), variableMap); default: @@ -130,6 +130,10 @@ public boolean match(String path, int start, int end, Map variab } } + public boolean match(String path, int start, int end, Map variableMap) { + return match(new KeyString(path), start, end, variableMap); + } + private boolean matchPattern(String path, Map variableMap) { Matcher matcher = getPattern().matcher(path); if (matcher.matches()) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java index 9506313cfcf..d16987ff85b 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java @@ -65,6 +65,12 @@ public static Builder builder() { return new Builder(); } + public static CorsMeta combine(CorsMeta source, CorsMeta other) { + return source == null || source.isEmpty() + ? other == null || other.isEmpty() ? null : other.applyDefault() + : source.combine(other).applyDefault(); + } + public String[] getAllowedOrigins() { return allowedOrigins; } @@ -158,7 +164,7 @@ public CorsMeta combine(CorsMeta other) { } /** - * Merge two arrays of CORS config values, with the other array having higher priority. + * Merge two arrays of CORS config values, the other array having higher priority. */ private static String[] combine(String[] source, String[] other) { if (other.length == 0) { 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 ad6490ca358..7929d049c3b 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 @@ -30,6 +30,9 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.DefaultRestToolKit; import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; +import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; + +import java.lang.reflect.Method; @Activate public class BasicRequestMappingResolver implements RequestMappingResolver { @@ -50,41 +53,65 @@ public RestToolKit getRestToolKit() { @Override public RequestMapping resolve(ServiceMeta serviceMeta) { - return builder(serviceMeta.findAnnotation(Annotations.Mapping), serviceMeta.getServiceInterface()) - .name(serviceMeta.getType().getSimpleName()) + AnnotationMeta mapping = serviceMeta.findAnnotation(Annotations.Mapping); + Builder builder = builder(mapping); + + String[] paths = getPaths(mapping); + if (paths.length == 0) { + builder.path(serviceMeta.getServiceInterface()); + } else { + builder.path(paths); + } + + return builder.name(serviceMeta.getType().getSimpleName()) .contextPath(serviceMeta.getContextPath()) .build(); } @Override public RequestMapping resolve(MethodMeta methodMeta) { + Method method = methodMeta.getMethod(); + AnnotationMeta mapping = methodMeta.findAnnotation(Annotations.Mapping); + Builder builder = builder(mapping); + + String[] paths = getPaths(mapping); + if (paths.length == 0) { + builder.path(method.getName()).sig(TypeUtils.buildSig(method)); + } else { + builder.path(paths); + } + ServiceMeta serviceMeta = methodMeta.getServiceMeta(); if (globalCorsMeta == null) { globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel); } - String name = methodMeta.getMethod().getName(); - return builder(methodMeta.findAnnotation(Annotations.Mapping), name) - .name(name) + return builder.name(method.getName()) .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) .cors(globalCorsMeta) .build(); } - private Builder builder(AnnotationMeta mapping, String defaultPath) { + private Builder builder(AnnotationMeta mapping) { Builder builder = RequestMapping.builder(); - String[] paths = StringUtils.EMPTY_STRING_ARRAY; - if (mapping != null) { - builder.method(mapping.getStringArray("method")) - .param(mapping.getStringArray("params")) - .header(mapping.getStringArray("headers")) - .consume(mapping.getStringArray("consumes")) - .produce(mapping.getStringArray("produces")); + if (mapping == null) { + return builder; + } + builder.method(mapping.getStringArray("method")) + .param(mapping.getStringArray("params")) + .header(mapping.getStringArray("headers")) + .consume(mapping.getStringArray("consumes")) + .produce(mapping.getStringArray("produces")); + return builder; + } - paths = mapping.getStringArray("path"); - if (paths.length == 0) { - paths = mapping.getValueArray(); - } + private static String[] getPaths(AnnotationMeta mapping) { + if (mapping == null) { + return StringUtils.EMPTY_STRING_ARRAY; + } + String[] paths = mapping.getStringArray("path"); + if (paths.length > 0) { + return paths; } - return paths.length == 0 ? builder.path(defaultPath) : builder.path(paths); + return mapping.getValueArray(); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/KeyString.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/KeyString.java new file mode 100644 index 00000000000..2ddac3c70c5 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/KeyString.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.util; + +/** + * Zero-copy string as map key. + */ +public final class KeyString implements CharSequence { + + private final String value; + private final int offset; + private final int length; + private final boolean caseSensitive; + + public KeyString(String value, int start, int end, boolean caseSensitive) { + this.value = value; + offset = start; + length = (end == -1 ? value.length() : end) - start; + this.caseSensitive = caseSensitive; + } + + public KeyString(String value, int start, int end) { + this(value, start, end, true); + } + + public KeyString(String value, int end, boolean caseSensitive) { + this.value = value; + offset = 0; + length = end == -1 ? value.length() : end; + this.caseSensitive = caseSensitive; + } + + public KeyString(String value, int end) { + this(value, end, true); + } + + public KeyString(String value, boolean caseSensitive) { + this.value = value; + offset = 0; + length = value.length(); + this.caseSensitive = caseSensitive; + } + + public KeyString(String value) { + this(value, false); + } + + public KeyString(KeyString path, int start, int end) { + this(path.value, path.offset + start, path.offset + end, path.caseSensitive); + } + + @Override + public int length() { + return length; + } + + @Override + public char charAt(int index) { + return value.charAt(offset + index); + } + + @Override + public CharSequence subSequence(int start, int end) { + return value.substring(offset + start, offset + end); + } + + @Override + public int hashCode() { + int h = 0; + for (int i = 0; i < length; i++) { + h = 31 * h + (caseSensitive ? value.charAt(offset + i) : Character.toLowerCase(value.charAt(offset + i))); + } + return h; + } + + @Override + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + public boolean equals(Object obj) { + KeyString other = (KeyString) obj; + return value.regionMatches(!caseSensitive, offset, other.value, other.offset, length); + } + + @Override + public String toString() { + return value.substring(offset, length - offset); + } + + public int indexOf(char ch, int start) { + int index = value.indexOf(ch, offset + start); + return index == -1 ? -1 : index - offset; + } + + public boolean regionMatches(int start, String value, int i, int length) { + return this.value.regionMatches(!caseSensitive, offset + start, value, i, length); + } + + public String substring(int start) { + return value.substring(offset + start, offset + length); + } + + public String substring(int start, int end) { + return value.substring(offset + start, offset + (end == -1 ? length : end)); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java index 1e6181795da..04b2a618d54 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java @@ -23,6 +23,7 @@ import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -215,4 +216,13 @@ public static Object nullDefault(Class targetClass) { } return null; } + + public static String buildSig(Method method) { + StringBuilder sb = new StringBuilder(8); + for (Class type : method.getParameterTypes()) { + String name = type.getName(); + sb.append(name.charAt(name.lastIndexOf('.') + 1)); + } + return sb.toString(); + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy index f89cffdc500..c2cb041a845 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy @@ -32,7 +32,7 @@ class RestProtocolTest extends BaseServiceTest { builder.provider(new DemoServiceImpl()) } - def "Hello world"() { + def "hello world"() { given: def request = new TestRequest(path) expect: @@ -85,4 +85,29 @@ class RestProtocolTest extends BaseServiceTest { path | body | output '/postTest' | '["Sam","8"]' | 'Sam is 8 years old' } + + def "override mapping test"() { + given: + def request = new TestRequest(path: path) + expect: + runner.run(request, String.class) == output + where: + path | output + '/say?name=sam&count=2' | '2' + '/say?name=sam' | '1' + '/say~SL' | '2' + '/say~S' | '1' + '/say~S?name=sam&count=2' | '1' + '/say~S.yml?name=sam&count=2' | '1' + } + + def "ambiguous mapping test"() { + given: + def request = new TestRequest(path: path) + expect: + runner.run(request, String.class) contains "Ambiguous mapping" + where: + path | output + '/say' | '1' + } } 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 4842edb95bf..9b13f7e2810 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 @@ -29,4 +29,8 @@ public interface DemoService { @Mapping("/buy") Book buy(Book book); + + String say(String name, Long count); + + String say(String name); } 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 dfb50418422..ed30117299c 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 @@ -32,4 +32,14 @@ public String postTest(String name, int age) { public Book buy(Book book) { return book; } + + @Override + public String say(String name, Long count) { + return "2"; + } + + @Override + public String say(String name) { + return "1"; + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java index d87830b0f0c..4099cdf345c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java @@ -146,7 +146,7 @@ public Map getCookies() { return cookies; } - public TestRequest setParam(String name, Object value) { + public TestRequest param(String name, Object value) { params.put(name, value); return this; } diff --git a/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTriple3AutoConfiguration.java b/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTriple3AutoConfiguration.java index 246bfdce936..08df8da1686 100644 --- a/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTriple3AutoConfiguration.java +++ b/dubbo-spring-boot/dubbo-spring-boot-3-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTriple3AutoConfiguration.java @@ -36,12 +36,12 @@ @Conditional(SpringBoot3Condition.class) public class DubboTriple3AutoConfiguration { - public static final String PREFIX = "dubbo.protocol.triple"; + public static final String PREFIX = "dubbo.protocol.triple.servlet"; @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Filter.class) @ConditionalOnWebApplication(type = Type.SERVLET) - @ConditionalOnProperty(prefix = PREFIX, name = "enable-servlet") + @ConditionalOnProperty(prefix = PREFIX, name = "enable") public static class TripleServletConfiguration { @Bean diff --git a/dubbo-spring-boot/dubbo-spring-boot-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTripleAutoConfiguration.java b/dubbo-spring-boot/dubbo-spring-boot-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTripleAutoConfiguration.java index 41dfc63e43c..9f6139a7ba5 100644 --- a/dubbo-spring-boot/dubbo-spring-boot-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTripleAutoConfiguration.java +++ b/dubbo-spring-boot/dubbo-spring-boot-autoconfigure/src/main/java/org/apache/dubbo/spring/boot/autoconfigure/DubboTripleAutoConfiguration.java @@ -42,7 +42,7 @@ public class DubboTripleAutoConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Filter.class) @ConditionalOnWebApplication(type = Type.SERVLET) - @ConditionalOnProperty(prefix = PREFIX, name = "enable-servlet") + @ConditionalOnProperty(prefix = PREFIX, name = "enable") public static class TripleServletConfiguration { @Bean From 52b1e00cd5d1ed29608e8f9561e415e1135eeef5 Mon Sep 17 00:00:00 2001 From: oxsean Date: Wed, 19 Jun 2024 08:40:24 +0000 Subject: [PATCH 04/12] Fix jsonPb decode failed --- .../AbstractSpringArgumentResolver.java | 6 ++ .../remoting/http12/CompositeInputStream.java | 37 +++++++- .../NamedValueArgumentResolverSupport.java | 19 +++-- .../mapping/meta/MethodParameterMeta.java | 5 ++ .../tri/rest/mapping/meta/ParameterMeta.java | 4 + .../basic/FallbackArgumentResolver.java | 15 ++-- .../support/basic/ParamArgumentResolver.java | 6 ++ .../protocol/tri/rest/util/RequestUtils.java | 85 +++++++++++++++---- .../protocol/tri/rest/RestProtocolTest.groovy | 8 +- .../tri/rest/service/DemoService.java | 6 +- .../tri/rest/service/DemoServiceImpl.java | 5 ++ 11 files changed, 156 insertions(+), 40 deletions(-) diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java index fef61723e7f..f60cdb7d2f2 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java @@ -16,6 +16,7 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.support.spring; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.protocol.tri.rest.argument.AbstractAnnotationBaseArgumentResolver; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.NamedValueMeta; @@ -29,4 +30,9 @@ public abstract class AbstractSpringArgumentResolver extends AbstractAnnotationB protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta ann) { return new NamedValueMeta(ann.getValue(), Helper.isRequired(ann), Helper.defaultValue(ann)); } + + @Override + protected Object filterValue(Object value, NamedValueMeta meta) { + return StringUtils.EMPTY_STRING.equals(value) ? meta.defaultValue() : value; + } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java index 9b8b248eb0b..58c069e52e1 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java @@ -60,6 +60,42 @@ public int read() throws IOException { return -1; } + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + int total = 0; + InputStream inputStream; + while ((inputStream = inputStreams.peek()) != null) { + int available = inputStream.available(); + if (available == 0) { + releaseHeadStream(); + continue; + } + + int read = inputStream.read(b, off + total, Math.min(len - total, available)); + if (read != -1) { + total += read; + readIndex += read; + releaseIfNecessary(inputStream); + + if (total == len) { + return total; + } + } else { + releaseHeadStream(); + } + } + + return total > 0 ? total : -1; + } + @Override public int available() { return totalAvailable - readIndex; @@ -81,7 +117,6 @@ private void releaseHeadStream() throws IOException { private void releaseIfNecessary(InputStream inputStream) throws IOException { int available = inputStream.available(); if (available == 0) { - inputStream.close(); releaseHeadStream(); } } 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 270df2f6fa5..54996942b6e 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 @@ -38,18 +38,17 @@ public abstract class NamedValueArgumentResolverSupport { protected final Object resolve(NamedValueMeta meta, HttpRequest request, HttpResponse response) { Class type = meta.type(); - Object arg; if (type.isArray() || Collection.class.isAssignableFrom(type)) { - arg = resolveCollectionValue(meta, request, response); - } else if (Map.class.isAssignableFrom(type)) { - arg = resolveMapValue(meta, request, response); - } else { - arg = resolveValue(meta, request, response); + return resolveCollectionValue(meta, request, response); } - + if (Map.class.isAssignableFrom(type)) { + return resolveMapValue(meta, request, response); + } + Object arg = resolveValue(meta, request, response); if (arg != null) { - return StringUtils.EMPTY_STRING.equals(arg) ? emptyDefaultValue(meta) : arg; + return filterValue(arg, meta); } + arg = meta.defaultValue(); if (arg != null) { return arg; @@ -88,6 +87,10 @@ protected String emptyDefaultValue(NamedValueMeta meta) { protected abstract Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response); + protected Object filterValue(Object value, NamedValueMeta meta) { + return value; + } + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { return resolveValue(meta, request, response); } 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 e9b82cf4828..4e769e90439 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 @@ -45,6 +45,11 @@ public Parameter getParameter() { return parameter; } + @Override + public boolean isSingle() { + return methodMeta.getMethod().getParameterCount() == 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/ParameterMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java index 293b8e6e599..94a89fc9b6a 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 @@ -101,6 +101,10 @@ 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/FallbackArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java index c2d56a322d8..6b0bca13450 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 @@ -17,7 +17,6 @@ package org.apache.dubbo.rpc.protocol.tri.rest.support.basic; import org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpRequest.FileUpload; @@ -54,11 +53,6 @@ protected NamedValueMeta createNamedValueMeta(ParameterMeta param) { return new NamedValueMeta(param.isAnnotated(Annotations.Nonnull), null); } - @Override - protected String emptyDefaultValue(NamedValueMeta meta) { - return StringUtils.EMPTY_STRING; - } - @Override protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { return doResolveValue(meta, request, true); @@ -71,11 +65,14 @@ protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request protected Object doResolveValue(NamedValueMeta meta, HttpRequest request, boolean single) { if (HttpMethods.supportBody(request.method())) { - Object body = RequestUtils.decodeBody(request); + ParameterMeta parameterMeta = meta.parameterMeta(); + Object body = RequestUtils.decodeBody(request, parameterMeta.getType(), parameterMeta.isSingle()); if (body != null) { - if (body instanceof List) { + if (parameterMeta.getType().isInstance(body)) { + return body; + } else if (body instanceof List) { List list = (List) body; - int index = meta.parameterMeta().getIndex(); + int index = parameterMeta.getIndex(); if (index >= 0 && list.size() > index) { return list.get(index); } 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 57d65af0aa6..52456c898c0 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 @@ -19,6 +19,7 @@ import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.io.StreamUtils; import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.HttpCookie; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpRequest.FileUpload; @@ -88,6 +89,11 @@ protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResp return null; } + @Override + protected Object filterValue(Object value, NamedValueMeta meta) { + return StringUtils.EMPTY_STRING.equals(value) ? meta.defaultValue() : value; + } + @Override protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { switch (((ParamNamedValueMeta) meta).paramType) { 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 e4d58be8c4d..6db1a709237 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,6 +25,9 @@ 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.net.URLDecoder; import java.net.URLEncoder; @@ -173,34 +176,84 @@ public static Object decodeBody(HttpRequest request, Class type) { } Object value = request.attribute(RestConstants.BODY_ATTRIBUTE); if (value == null) { - if (decoder.mediaType() == MediaType.TEXT_PLAIN) { + if (decoder.mediaType().isPureText()) { type = String.class; } - value = decoder.decode(request.inputStream(), type, request.charsetOrDefault()); + 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) { + public static Object decodeBody(HttpRequest request, Class type, boolean single) { HttpMessageDecoder decoder = request.attribute(RestConstants.BODY_DECODER_ATTRIBUTE); if (decoder == null) { return null; } - Object value = request.attribute(RestConstants.BODY_ATTRIBUTE); - if (value == null) { - Class type; - MediaType mediaType = decoder.mediaType(); - if (mediaType == MediaType.APPLICATION_JSON || mediaType == MediaType.APPLICATION_YAML) { - type = Object.class; - } else if (mediaType.isPureText()) { - type = String.class; - } else { - return null; + + try { + InputStream input = request.inputStream(); + if (single) { + if (decoder.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(); + } } - value = decoder.decode(request.inputStream(), type, request.charsetOrDefault()); - request.setAttribute(RestConstants.BODY_ATTRIBUTE, value); + + Object value = request.attribute(RestConstants.BODY_ATTRIBUTE); + if (value == null) { + MediaType mediaType = decoder.mediaType(); + if (mediaType == MediaType.APPLICATION_JSON || mediaType == MediaType.APPLICATION_YAML) { + type = Object.class; + } else if (mediaType.isPureText()) { + type = String.class; + } else { + return null; + } + int available = input.available(); + if (available == 0) { + return emptyDefault(type); + } + + value = decoder.decode(input, type, request.charsetOrDefault()); + request.setAttribute(RestConstants.BODY_ATTRIBUTE, value); + } + return value; + } catch (IOException e) { + throw new DecodeException("Error reading input", e); } - return value; + } + + private static Object emptyDefault(Class type) { + if (type == String.class) { + return StringUtils.EMPTY_STRING; + } + if (type == byte[].class) { + return new byte[0]; + } + return null; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy index c2cb041a845..125e6a51526 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy @@ -63,9 +63,11 @@ class RestProtocolTest extends BaseServiceTest { expect: runner.run(request, Book.class).name == output where: - path | body | output - '/buy' | [new Book(name: "Dubbo", price: 80, publishDate: new Date())] | 'Dubbo' - '/buy' | ['book': new Book(name: "Dubbo", price: 80, publishDate: new Date())] | 'Dubbo' + path | body | output + '/buy' | new Book(name: "Dubbo") | 'Dubbo' + '/buy' | [new Book(name: "Dubbo")] | 'Dubbo' + '/buy2' | [new Book(name: "Dubbo"), 2] | 'Dubbo' + '/buy2' | [book: new Book(name: "Dubbo"), count: 2] | 'Dubbo' } def "urlEncodeForm test"() { 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 9b13f7e2810..5bbabaa7059 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 @@ -21,15 +21,15 @@ @Mapping("/") public interface DemoService { - @Mapping("/hello") String hello(String name); - @Mapping("/postTest") String postTest(String name, int age); - @Mapping("/buy") Book buy(Book book); + @Mapping("/buy2") + Book buy(Book book, int count); + String say(String name, Long count); String say(String name); 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 ed30117299c..8bf63122747 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 @@ -33,6 +33,11 @@ public Book buy(Book book) { return book; } + @Override + public Book buy(Book book, int count) { + return book; + } + @Override public String say(String name, Long count) { return "2"; From d53f9b7c6e9501dbf2dcf08a71c68f5ef9f785a2 Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Wed, 19 Jun 2024 23:25:48 +0800 Subject: [PATCH 05/12] Rest support no interface method --- .../model/ReflectionServiceDescriptor.java | 3 +- .../DefaultRequestMappingRegistry.java | 42 ++++---- .../rest/mapping/RequestMappingResolver.java | 8 +- .../tri/rest/mapping/meta/MethodMeta.java | 7 +- .../basic/BasicRequestMappingResolver.java | 6 ++ .../basic/FallbackArgumentResolver.java | 19 ---- .../protocol/tri/rest/util/RequestUtils.java | 13 +-- .../protocol/tri/rest/RestProtocolTest.groovy | 42 ++++---- .../tri/rest/service/DemoServiceImpl.java | 11 ++ .../rpc/protocol/tri/test/TestRunner.java | 40 +++++++ .../rpc/protocol/tri/test/TestRunnerImpl.java | 100 ++++++++++++++++++ 11 files changed, 219 insertions(+), 72 deletions(-) diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionServiceDescriptor.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionServiceDescriptor.java index dc041469330..9a3c0c491b4 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionServiceDescriptor.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionServiceDescriptor.java @@ -48,7 +48,8 @@ public ReflectionServiceDescriptor(String interfaceName, Class interfaceClass } public void addMethod(MethodDescriptor methodDescriptor) { - methods.put(methodDescriptor.getMethodName(), Collections.singletonList(methodDescriptor)); + methods.computeIfAbsent(methodDescriptor.getMethodName(), k -> new ArrayList<>(1)) + .add(methodDescriptor); } public ReflectionServiceDescriptor(Class interfaceClass) { 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 e7291ce7fda..73b40c52454 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 @@ -16,6 +16,7 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping; +import org.apache.dubbo.common.URL; import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.ConfigurationUtils; import org.apache.dubbo.config.nested.RestConfig; @@ -24,6 +25,8 @@ import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.model.MethodDescriptor; +import org.apache.dubbo.rpc.model.ReflectionMethodDescriptor; +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.RestConstants; @@ -37,7 +40,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.util.KeyString; import org.apache.dubbo.rpc.protocol.tri.rest.util.MethodWalker; import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils; -import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; import java.lang.reflect.Method; import java.util.ArrayList; @@ -89,42 +91,44 @@ public void register(Invoker invoker) { } } - Object service = invoker.getUrl().getServiceModel().getProxyObject(); - ServiceDescriptor serviceDescriptor = DescriptorUtils.getReflectionServiceDescriptor(invoker.getUrl()); - if (serviceDescriptor == null) { + URL url = invoker.getUrl(); + Object service = url.getServiceModel().getProxyObject(); + ServiceDescriptor sd = DescriptorUtils.getReflectionServiceDescriptor(url); + if (sd == null) { return; } new MethodWalker().walk(service.getClass(), (classes, consumer) -> { for (int i = 0, size = resolvers.size(); i < size; i++) { RequestMappingResolver resolver = resolvers.get(i); - RestToolKit toolKit = resolver.getRestToolKit(); - ServiceMeta serviceMeta = new ServiceMeta(classes, service, invoker.getUrl(), toolKit); - if (!resolver.accept(serviceMeta)) { + ServiceMeta serviceMeta = new ServiceMeta(classes, service, url, resolver.getRestToolKit()); + if (!resolver.accept(serviceMeta, sd)) { continue; } RequestMapping classMapping = resolver.resolve(serviceMeta); consumer.accept((methods) -> { - Method m = methods.get(0); - MethodDescriptor methodDescriptor = serviceDescriptor.getMethod(m.getName(), m.getParameterTypes()); - if (methodDescriptor == null) { + Method method = methods.get(0); + MethodDescriptor md = sd.getMethod(method.getName(), method.getParameterTypes()); + MethodMeta methodMeta = new MethodMeta(methods, serviceMeta); + if (!resolver.accept(methodMeta, md)) { return; } - MethodMeta methodMeta = new MethodMeta(methods, serviceMeta); RequestMapping methodMapping = resolver.resolve(methodMeta); if (methodMapping == null) { return; } + if (md == null) { + if (!(sd instanceof ReflectionServiceDescriptor)) { + return; + } + md = new ReflectionMethodDescriptor(method); + ((ReflectionServiceDescriptor) sd).addMethod(md); + } if (classMapping != null) { methodMapping = classMapping.combine(methodMapping); } - register0( - methodMapping, - new HandlerMeta( - invoker, - methodMeta, - MethodMetadata.fromMethodDescriptor(methodDescriptor), - methodDescriptor, - serviceDescriptor)); + methodMeta.initParameters(); + MethodMetadata methodMetadata = MethodMetadata.fromMethodDescriptor(md); + register0(methodMapping, new HandlerMeta(invoker, methodMeta, methodMetadata, md, sd)); }); } }); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java index 7c7b5f372e5..44a179ea004 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java @@ -18,6 +18,8 @@ import org.apache.dubbo.common.extension.ExtensionScope; import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.rpc.model.MethodDescriptor; +import org.apache.dubbo.rpc.model.ServiceDescriptor; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; @@ -27,11 +29,15 @@ public interface RequestMappingResolver { RestToolKit getRestToolKit(); - default boolean accept(ServiceMeta serviceMeta) { + default boolean accept(ServiceMeta serviceMeta, ServiceDescriptor serviceDescriptor) { return true; } RequestMapping resolve(ServiceMeta serviceMeta); + default boolean accept(MethodMeta methodMeta, MethodDescriptor methodDescriptor) { + return true; + } + RequestMapping resolve(MethodMeta methodMeta); } 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 93ea6b2faf5..4dc7429e68b 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 @@ -27,18 +27,17 @@ public final class MethodMeta extends AnnotationSupport { private final List hierarchy; private final Method method; - private final ParameterMeta[] parameters; + private ParameterMeta[] parameters; private final ServiceMeta serviceMeta; public MethodMeta(List hierarchy, ServiceMeta serviceMeta) { super(serviceMeta.getToolKit()); this.hierarchy = hierarchy; method = hierarchy.get(0); - parameters = initParameters(method, hierarchy); this.serviceMeta = serviceMeta; } - private ParameterMeta[] initParameters(Method method, List hierarchy) { + public void initParameters() { int count = method.getParameterCount(); List> parameterHierarchies = new ArrayList<>(count); for (int i = 0, len = hierarchy.size(); i < len; i++) { @@ -62,7 +61,7 @@ private ParameterMeta[] initParameters(Method method, List hierarchy) { String parameterName = parameterNames == null ? null : parameterNames[i]; parameters[i] = new MethodParameterMeta(parameterHierarchies.get(i), parameterName, i, this); } - return parameters; + this.parameters = parameters; } public List getHierarchy() { 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 7929d049c3b..c342d9b1227 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 @@ -19,6 +19,7 @@ import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.model.MethodDescriptor; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder; @@ -51,6 +52,11 @@ public RestToolKit getRestToolKit() { return toolKit; } + @Override + public boolean accept(MethodMeta methodMeta, MethodDescriptor methodDescriptor) { + return methodDescriptor != null || methodMeta.findAnnotation(Annotations.Mapping) != null; + } + @Override public RequestMapping resolve(ServiceMeta serviceMeta) { AnnotationMeta mapping = serviceMeta.findAnnotation(Annotations.Mapping); 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 6b0bca13450..338208ee139 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 @@ -19,10 +19,7 @@ import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.HttpRequest; -import org.apache.dubbo.remoting.http12.HttpRequest.FileUpload; import org.apache.dubbo.remoting.http12.HttpResponse; -import org.apache.dubbo.remoting.http12.message.codec.CodecUtils; -import org.apache.dubbo.rpc.model.FrameworkModel; 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.NamedValueMeta; @@ -35,14 +32,6 @@ @Activate(order = Integer.MAX_VALUE - 10000) public class FallbackArgumentResolver extends AbstractArgumentResolver { - private final FrameworkModel frameworkModel; - private final CodecUtils codecUtils; - - public FallbackArgumentResolver(FrameworkModel frameworkModel) { - this.frameworkModel = frameworkModel; - codecUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CodecUtils.class); - } - @Override public boolean accept(ParameterMeta param) { return param.getToolKit().getDialect() == RestConstants.DIALECT_BASIC; @@ -83,14 +72,6 @@ protected Object doResolveValue(NamedValueMeta meta, HttpRequest request, boolea } } } - if (RequestUtils.isMultiPart(request)) { - FileUpload fu = request.part(meta.name()); - if (fu != null) { - return codecUtils - .determineHttpMessageDecoder(null, frameworkModel, fu.contentType()) - .decode(fu.inputStream(), meta.type(), request.charsetOrDefault()); - } - } } return single ? request.parameter(meta.name()) : request.parameterValues(meta.name()); } 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 6db1a709237..b75561a0eeb 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 @@ -200,11 +200,15 @@ public static Object decodeBody(HttpRequest request, Class type, boolean sing if (decoder == null) { return null; } + MediaType mediaType = decoder.mediaType(); + if (mediaType == MediaType.APPLICATION_FROM_URLENCODED || mediaType == MediaType.MULTIPART_FORM_DATA) { + return null; + } try { InputStream input = request.inputStream(); if (single) { - if (decoder.mediaType().isPureText()) { + if (mediaType.isPureText()) { type = String.class; } int available = input.available(); @@ -225,7 +229,6 @@ public static Object decodeBody(HttpRequest request, Class type, boolean sing Object value = request.attribute(RestConstants.BODY_ATTRIBUTE); if (value == null) { - MediaType mediaType = decoder.mediaType(); if (mediaType == MediaType.APPLICATION_JSON || mediaType == MediaType.APPLICATION_YAML) { type = Object.class; } else if (mediaType.isPureText()) { @@ -234,11 +237,9 @@ public static Object decodeBody(HttpRequest request, Class type, boolean sing return null; } int available = input.available(); - if (available == 0) { - return emptyDefault(type); + if (available != 0) { + value = decoder.decode(input, type, request.charsetOrDefault()); } - - value = decoder.decode(input, type, request.charsetOrDefault()); request.setAttribute(RestConstants.BODY_ATTRIBUTE, value); } return value; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy index 125e6a51526..9593448b040 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy @@ -17,7 +17,7 @@ package org.apache.dubbo.rpc.protocol.tri.rest -import org.apache.dubbo.remoting.http12.HttpMethods + 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.service.DemoServiceImpl @@ -33,10 +33,8 @@ class RestProtocolTest extends BaseServiceTest { } def "hello world"() { - given: - def request = new TestRequest(path) expect: - runner.run(request, String.class) == output + runner.get(path) == output where: path | output '/hello?name=world' | 'hello world' @@ -45,10 +43,8 @@ class RestProtocolTest extends BaseServiceTest { } def "post test"() { - given: - def request = new TestRequest(path).post(body) expect: - runner.run(request, String.class) == output + runner.post(path, body) == output where: path | body | output '/postTest' | '["Sam","8"]' | 'Sam is 8 years old' @@ -58,10 +54,8 @@ class RestProtocolTest extends BaseServiceTest { } def "bean test"() { - given: - def request = new TestRequest(path).post(body) expect: - runner.run(request, Book.class).name == output + runner.post(path, body, Book.class).name == output where: path | body | output '/buy' | new Book(name: "Dubbo") | 'Dubbo' @@ -73,7 +67,6 @@ class RestProtocolTest extends BaseServiceTest { def "urlEncodeForm test"() { given: def request = new TestRequest( - method: HttpMethods.POST, path: path, contentType: MediaType.APPLICATION_FROM_URLENCODED, params: [ @@ -82,17 +75,15 @@ class RestProtocolTest extends BaseServiceTest { ] ) expect: - runner.run(request, String.class) == output + runner.post(request) == output where: - path | body | output - '/postTest' | '["Sam","8"]' | 'Sam is 8 years old' + path | output + '/postTest' | 'Sam is 8 years old' } def "override mapping test"() { - given: - def request = new TestRequest(path: path) expect: - runner.run(request, String.class) == output + runner.get(path) == output where: path | output '/say?name=sam&count=2' | '2' @@ -104,12 +95,19 @@ class RestProtocolTest extends BaseServiceTest { } def "ambiguous mapping test"() { - given: - def request = new TestRequest(path: path) expect: - runner.run(request, String.class) contains "Ambiguous mapping" + runner.get(path) contains "Ambiguous mapping" + where: + path | _ + '/say' | _ + } + + def "no interface method test"() { + expect: + runner.get(path) contains output where: - path | output - '/say' | '1' + path | output + '/noInterface' | 'ok' + '/noInterfaceAndMapping' | '404' } } 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 8bf63122747..1330e697586 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 @@ -16,6 +16,8 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.service; +import org.apache.dubbo.remoting.http12.rest.Mapping; + public class DemoServiceImpl implements DemoService { @Override @@ -47,4 +49,13 @@ public String say(String name, Long count) { public String say(String name) { return "1"; } + + @Mapping + public String noInterface() { + return "ok"; + } + + public String noInterfaceAndMapping() { + return "ok"; + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java index c9835931e8e..6678c81c04b 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java @@ -22,5 +22,45 @@ public interface TestRunner { T run(TestRequest request, Class type); + T get(TestRequest request, Class type); + + String get(TestRequest request); + + T get(String path, Class type); + + String get(String path); + + T post(TestRequest request, Class type); + + String post(TestRequest request); + + T post(String path, Object body, Class type); + + String post(String path, Object body); + + T put(TestRequest request, Class type); + + String put(TestRequest request); + + T put(String path, Object body, Class type); + + String put(String path, Object body); + + T patch(TestRequest request, Class type); + + String patch(TestRequest request); + + T patch(String path, Object body, Class type); + + String patch(String path, Object body); + + T delete(TestRequest request, Class type); + + String delete(TestRequest request); + + T delete(String path, Class type); + + String delete(String path); + void destroy(); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java index 57668559d67..4d3a424243a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java @@ -202,6 +202,106 @@ public T run(TestRequest request, Class type) { return run(request).getBody(type); } + @Override + public T get(TestRequest request, Class type) { + return run(request.setMethod(HttpMethods.GET.name()), type); + } + + @Override + public String get(TestRequest request) { + return get(request, String.class); + } + + @Override + public T get(String path, Class type) { + return get(new TestRequest(path), type); + } + + @Override + public String get(String path) { + return get(new TestRequest(path)); + } + + @Override + public T post(TestRequest request, Class type) { + return run(request.setMethod(HttpMethods.POST.name()), type); + } + + @Override + public String post(TestRequest request) { + return post(request, String.class); + } + + @Override + public T post(String path, Object body, Class type) { + return post(new TestRequest(path).setBody(body), type); + } + + @Override + public String post(String path, Object body) { + return post(new TestRequest(path).setBody(body)); + } + + @Override + public T put(TestRequest request, Class type) { + return run(request.setMethod(HttpMethods.PUT.name()), type); + } + + @Override + public String put(TestRequest request) { + return put(request, String.class); + } + + @Override + public T put(String path, Object body, Class type) { + return put(new TestRequest(path).setBody(body), type); + } + + @Override + public String put(String path, Object body) { + return post(new TestRequest(path).setBody(body)); + } + + @Override + public T patch(TestRequest request, Class type) { + return run(request.setMethod(HttpMethods.PATCH.name()), type); + } + + @Override + public String patch(TestRequest request) { + return patch(request, String.class); + } + + @Override + public T patch(String path, Object body, Class type) { + return patch(new TestRequest(path).setBody(body), type); + } + + @Override + public String patch(String path, Object body) { + return patch(new TestRequest(path).setBody(body)); + } + + @Override + public T delete(TestRequest request, Class type) { + return run(request.setMethod(HttpMethods.DELETE.name()), type); + } + + @Override + public String delete(TestRequest request) { + return delete(request, String.class); + } + + @Override + public T delete(String path, Class type) { + return patch(new TestRequest(path), type); + } + + @Override + public String delete(String path) { + return delete(new TestRequest(path)); + } + @Override public void destroy() { applicationModel.destroy(); From ec327bc2ea9adcc8a3dc992c71596b46bc16f928 Mon Sep 17 00:00:00 2001 From: oxsean Date: Thu, 20 Jun 2024 05:40:08 +0000 Subject: [PATCH 06/12] Some rest bugfix --- .../http12/message/codec/CodecUtils.java | 8 +++ .../rest/argument/GeneralTypeConverter.java | 25 ++++++-- .../DefaultRequestMappingRegistry.java | 9 +-- .../rest/mapping/RequestMappingResolver.java | 6 +- .../tri/rest/mapping/meta/MethodMeta.java | 16 ++++- .../mapping/meta/MethodParameterMeta.java | 6 +- .../tri/rest/mapping/meta/ServiceMeta.java | 14 +++- .../basic/BasicRequestMappingResolver.java | 5 +- .../protocol/tri/rest/RestProtocolTest.groovy | 64 ++++++++++++++++--- .../tri/rest/service/DemoService.java | 12 +++- .../tri/rest/service/DemoServiceImpl.java | 38 ++++++++++- .../rpc/protocol/tri/test/TestRunner.java | 10 +++ .../rpc/protocol/tri/test/TestRunnerImpl.java | 21 ++++++ 13 files changed, 204 insertions(+), 30 deletions(-) diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java index 281ee15eb99..98428bb9a85 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java @@ -50,12 +50,20 @@ public HttpMessageDecoder determineHttpMessageDecoder(URL url, FrameworkModel fr .createCodec(url, frameworkModel, mediaType); } + public HttpMessageDecoder determineHttpMessageDecoder(String mediaType) { + return determineHttpMessageDecoder(null, null, mediaType); + } + public HttpMessageEncoder determineHttpMessageEncoder(URL url, FrameworkModel frameworkModel, String mediaType) { return determineHttpMessageEncoderFactory(mediaType) .orElseThrow(() -> new UnsupportedMediaTypeException(mediaType)) .createCodec(url, frameworkModel, mediaType); } + public HttpMessageEncoder determineHttpMessageEncoder(String mediaType) { + return determineHttpMessageEncoder(null, null, mediaType); + } + public Optional determineHttpMessageDecoderFactory(String mediaType) { Assert.notNull(mediaType, "mediaType must not be null"); return decoderCache.computeIfAbsent(mediaType, k -> { 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 ee9fb152218..4752512f414 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 @@ -25,6 +25,9 @@ import org.apache.dubbo.common.utils.JsonUtils; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.HttpCookie; +import org.apache.dubbo.remoting.http12.HttpRequest.FileUpload; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.remoting.http12.message.codec.CodecUtils; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestException; import org.apache.dubbo.rpc.protocol.tri.rest.RestParameterException; @@ -93,6 +96,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.dubbo.common.utils.StringUtils.tokenizeToList; import static org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils.getActualGenericType; import static org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils.getActualType; @@ -104,13 +108,16 @@ public class GeneralTypeConverter implements TypeConverter { private static final Logger LOGGER = LoggerFactory.getLogger(GeneralTypeConverter.class); private final ConverterUtil converterUtil; + private final CodecUtils codecUtils; public GeneralTypeConverter() { converterUtil = null; + codecUtils = null; } public GeneralTypeConverter(FrameworkModel frameworkModel) { converterUtil = frameworkModel.getBeanFactory().getOrRegisterBean(ConverterUtil.class); + codecUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CodecUtils.class); } @Override @@ -255,7 +262,7 @@ private Object doConvert(Object source, Class targetClass) throws Excepti case "java.lang.Class": return TypeUtils.loadClass(str); case "[B": - return str.getBytes(StandardCharsets.UTF_8); + return str.getBytes(UTF_8); case "[C": return str.toCharArray(); case "java.util.OptionalInt": @@ -432,7 +439,7 @@ private Object doConvert(Object source, Class targetClass) throws Excepti switch (targetClass.getName()) { case "java.lang.String": - return new String(bytes, StandardCharsets.UTF_8); + return new String(bytes, UTF_8); case "java.lang.Double": case "double": return ByteBuffer.wrap(bytes).getDouble(); @@ -568,6 +575,11 @@ private Object doConvert(Object source, Class targetClass) throws Excepti return StreamUtils.readBytes(is); } } + if (source instanceof FileUpload) { + try (InputStream is = ((FileUpload) source).inputStream()) { + return StreamUtils.readBytes(is); + } + } if (source instanceof Character) { char c = (Character) source; return new byte[] {(byte) (c >> 8), (byte) c}; @@ -1078,10 +1090,15 @@ private static boolean isMaybeJSON(String str) { return false; } - private static Object jsonToObject(String value, Type targetType) { + private Object jsonToObject(String value, Type targetType) { if (isMaybeJSON(value)) { try { - return JsonUtils.toJavaObject(value, targetType); + if (codecUtils == null || !(targetType instanceof Class)) { + return JsonUtils.toJavaObject(value, targetType); + } + return codecUtils + .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); } 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 73b40c52454..8cfa7856e3e 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 @@ -100,16 +100,16 @@ 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); - ServiceMeta serviceMeta = new ServiceMeta(classes, service, url, resolver.getRestToolKit()); - if (!resolver.accept(serviceMeta, sd)) { + ServiceMeta serviceMeta = new ServiceMeta(classes, sd, service, url, resolver.getRestToolKit()); + if (!resolver.accept(serviceMeta)) { continue; } RequestMapping classMapping = resolver.resolve(serviceMeta); consumer.accept((methods) -> { Method method = methods.get(0); MethodDescriptor md = sd.getMethod(method.getName(), method.getParameterTypes()); - MethodMeta methodMeta = new MethodMeta(methods, serviceMeta); - if (!resolver.accept(methodMeta, md)) { + MethodMeta methodMeta = new MethodMeta(methods, md, serviceMeta); + if (!resolver.accept(methodMeta)) { return; } RequestMapping methodMapping = resolver.resolve(methodMeta); @@ -122,6 +122,7 @@ public void register(Invoker invoker) { } md = new ReflectionMethodDescriptor(method); ((ReflectionServiceDescriptor) sd).addMethod(md); + methodMeta.setMethodDescriptor(md); } if (classMapping != null) { methodMapping = classMapping.combine(methodMapping); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java index 44a179ea004..803792f1a4d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java @@ -18,8 +18,6 @@ import org.apache.dubbo.common.extension.ExtensionScope; import org.apache.dubbo.common.extension.SPI; -import org.apache.dubbo.rpc.model.MethodDescriptor; -import org.apache.dubbo.rpc.model.ServiceDescriptor; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; @@ -29,13 +27,13 @@ public interface RequestMappingResolver { RestToolKit getRestToolKit(); - default boolean accept(ServiceMeta serviceMeta, ServiceDescriptor serviceDescriptor) { + default boolean accept(ServiceMeta serviceMeta) { return true; } RequestMapping resolve(ServiceMeta serviceMeta); - default boolean accept(MethodMeta methodMeta, MethodDescriptor methodDescriptor) { + default boolean accept(MethodMeta methodMeta) { return true; } 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 4dc7429e68b..3b09ff120f6 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,6 +16,8 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta; +import org.apache.dubbo.rpc.model.MethodDescriptor; + import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.lang.reflect.Parameter; @@ -27,13 +29,15 @@ public final class MethodMeta extends AnnotationSupport { private final List hierarchy; private final Method method; + private MethodDescriptor methodDescriptor; private ParameterMeta[] parameters; private final ServiceMeta serviceMeta; - public MethodMeta(List hierarchy, ServiceMeta serviceMeta) { + public MethodMeta(List hierarchy, MethodDescriptor methodDescriptor, ServiceMeta serviceMeta) { super(serviceMeta.getToolKit()); this.hierarchy = hierarchy; - method = hierarchy.get(0); + method = methodDescriptor == null ? hierarchy.get(0) : methodDescriptor.getMethod(); + this.methodDescriptor = methodDescriptor; this.serviceMeta = serviceMeta; } @@ -72,6 +76,14 @@ public Method getMethod() { return method; } + public MethodDescriptor getMethodDescriptor() { + return methodDescriptor; + } + + public void setMethodDescriptor(MethodDescriptor methodDescriptor) { + this.methodDescriptor = methodDescriptor; + } + public ParameterMeta[] getParameters() { return parameters; } 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 4e769e90439..845acf7d600 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,6 +16,9 @@ */ 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; @@ -47,7 +50,8 @@ public Parameter getParameter() { @Override public boolean isSingle() { - return methodMeta.getMethod().getParameterCount() == 1; + MethodDescriptor methodDescriptor = methodMeta.getMethodDescriptor(); + return methodDescriptor.getRpcType() != RpcType.UNARY || methodDescriptor.getParameterClasses().length == 1; } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ServiceMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ServiceMeta.java index f9f68ca6f50..28d940205e6 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ServiceMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ServiceMeta.java @@ -17,6 +17,7 @@ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta; import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.model.ServiceDescriptor; import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils; import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; @@ -30,14 +31,21 @@ public final class ServiceMeta extends AnnotationSupport { private final List> hierarchy; private final Class type; private final Object service; + private final ServiceDescriptor serviceDescriptor; private final URL url; private final String contextPath; private List exceptionHandlers; - public ServiceMeta(Collection> hierarchy, Object service, URL url, RestToolKit toolKit) { + public ServiceMeta( + Collection> hierarchy, + ServiceDescriptor serviceDescriptor, + Object service, + URL url, + RestToolKit toolKit) { super(toolKit); this.hierarchy = new ArrayList<>(hierarchy); + this.serviceDescriptor = serviceDescriptor; type = this.hierarchy.get(0); this.service = service; this.url = url; @@ -52,6 +60,10 @@ public Class getType() { return type; } + public ServiceDescriptor getServiceDescriptor() { + return serviceDescriptor; + } + public Object getService() { return service; } 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 c342d9b1227..269c315f46d 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 @@ -19,7 +19,6 @@ import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.model.FrameworkModel; -import org.apache.dubbo.rpc.model.MethodDescriptor; import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder; @@ -53,8 +52,8 @@ public RestToolKit getRestToolKit() { } @Override - public boolean accept(MethodMeta methodMeta, MethodDescriptor methodDescriptor) { - return methodDescriptor != null || methodMeta.findAnnotation(Annotations.Mapping) != null; + public boolean accept(MethodMeta methodMeta) { + return methodMeta.getMethodDescriptor() != null || methodMeta.findAnnotation(Annotations.Mapping) != null; } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy index 9593448b040..e7c0879d41a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy @@ -17,7 +17,6 @@ package org.apache.dubbo.rpc.protocol.tri.rest - 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.service.DemoServiceImpl @@ -42,15 +41,37 @@ class RestProtocolTest extends BaseServiceTest { '/hello.yml?name=world' | 'hello world' } - def "post test"() { + def "argument test"() { expect: runner.post(path, body) == output where: - path | body | output - '/postTest' | '["Sam","8"]' | 'Sam is 8 years old' - '/postTest' | '{"name": "Sam", "age": 8}' | 'Sam is 8 years old' - '/postTest?name=Sam' | '{"age": 8}' | 'Sam is 8 years old' - '/postTest?name=Sam' | '{"age": 8}' | 'Sam is 8 years old' + path | body | output + '/argTest' | '["Sam","8"]' | 'Sam is 8 years old' + '/argTest' | '{"name": "Sam", "age": 8}' | 'Sam is 8 years old' + '/argTest?name=Sam' | '{"age": 8}' | 'Sam is 8 years old' + } + + def "bean argument get test"() { + expect: + runner.get(path, Book.class).price == output + where: + path | output + '/beanArgTest' | 0 + '/beanArgTest?quote=5' | 0 + '/beanArgTest?book={"price": 6}' | 6 + '/beanArgTest?book={"price": 6}"e=5' | 5 + } + + def "bean argument post test"() { + expect: + runner.post(path, body, Book.class).price == output + where: + path | body | output + '/beanArgTest' | [] | 0 + '/beanArgTest' | [quote: 5] | 0 + '/beanArgTest' | [book: new Book(price: 6)] | 6 + '/beanArgTest' | [book: new Book(price: 6), quote: 5] | 5 + '/beanArgTest' | [price: 6, quote: 5] | 0 } def "bean test"() { @@ -77,8 +98,8 @@ class RestProtocolTest extends BaseServiceTest { expect: runner.post(request) == output where: - path | output - '/postTest' | 'Sam is 8 years old' + path | output + '/argTest' | 'Sam is 8 years old' } def "override mapping test"() { @@ -110,4 +131,29 @@ class RestProtocolTest extends BaseServiceTest { '/noInterface' | 'ok' '/noInterfaceAndMapping' | '404' } + + def "use interface argument name test"() { + expect: + runner.get(path) contains output + where: + path | output + '/argNameTest?name=Sam' | 'Sam' + } + + def "pb server stream test"() { + expect: + runner.posts(path, body).size() == output + where: + path | body | output + '/pbServerStream' | '{"service": "3"}' | 3 + '/pbServerStream' | '{}' | 0 + } + + def "pb server stream get test"() { + expect: + runner.gets(path).size() == output + where: + path | output + '/pbServerStream?request={"service": "3"}' | 3 + } } 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 5bbabaa7059..a2cf6da7143 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 @@ -16,14 +16,20 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.service; +import org.apache.dubbo.common.stream.StreamObserver; import org.apache.dubbo.remoting.http12.rest.Mapping; +import io.grpc.health.v1.HealthCheckRequest; +import io.grpc.health.v1.HealthCheckResponse; + @Mapping("/") public interface DemoService { String hello(String name); - String postTest(String name, int age); + String argTest(String name, int age); + + Book beanArgTest(Book book, Integer quote); Book buy(Book book); @@ -33,4 +39,8 @@ public interface DemoService { String say(String name, Long count); String say(String name); + + String argNameTest(String name); + + void pbServerStream(HealthCheckRequest request, StreamObserver responseObserver); } 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 1330e697586..cf7f03ace2e 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 @@ -16,8 +16,16 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.service; +import org.apache.dubbo.common.stream.StreamObserver; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.rest.Mapping; +import io.grpc.health.v1.HealthCheckRequest; +import io.grpc.health.v1.HealthCheckResponse; +import io.grpc.health.v1.HealthCheckResponse.ServingStatus; + +import static io.grpc.health.v1.HealthCheckResponse.newBuilder; + public class DemoServiceImpl implements DemoService { @Override @@ -26,10 +34,20 @@ public String hello(String name) { } @Override - public String postTest(String name, int age) { + public String argTest(String name, int age) { return name + " is " + age + " years old"; } + @Override + public Book beanArgTest(Book book, Integer quote) { + if (book == null) { + book = new Book(); + } else if (quote != null) { + book.setPrice(quote); + } + return book; + } + @Override public Book buy(Book book) { return book; @@ -58,4 +76,22 @@ public String noInterface() { public String noInterfaceAndMapping() { return "ok"; } + + @Override + public String argNameTest(String name1) { + return name1; + } + + @Override + public void pbServerStream(HealthCheckRequest request, StreamObserver responseObserver) { + String service = request.getService(); + if (StringUtils.isNotEmpty(service)) { + int count = Integer.parseInt(service); + for (int i = 0; i < count; i++) { + responseObserver.onNext( + newBuilder().setStatus(ServingStatus.SERVING).build()); + } + } + responseObserver.onCompleted(); + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java index 6678c81c04b..47168026bf0 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunner.java @@ -16,6 +16,8 @@ */ package org.apache.dubbo.rpc.protocol.tri.test; +import java.util.List; + public interface TestRunner { TestResponse run(TestRequest request); @@ -28,16 +30,24 @@ public interface TestRunner { T get(String path, Class type); + List gets(String path, Class type); + String get(String path); + List gets(String path); + T post(TestRequest request, Class type); String post(TestRequest request); T post(String path, Object body, Class type); + List posts(String path, Object body, Class type); + String post(String path, Object body); + List posts(String path, Object body); + T put(TestRequest request, Class type); String put(TestRequest request); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java index 4d3a424243a..d55f948539d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java @@ -217,11 +217,21 @@ public T get(String path, Class type) { return get(new TestRequest(path), type); } + @Override + public List gets(String path, Class type) { + return run(new TestRequest(path).setMethod(HttpMethods.GET.name())).getBodies(type); + } + @Override public String get(String path) { return get(new TestRequest(path)); } + @Override + public List gets(String path) { + return gets(path, String.class); + } + @Override public T post(TestRequest request, Class type) { return run(request.setMethod(HttpMethods.POST.name()), type); @@ -237,11 +247,22 @@ public T post(String path, Object body, Class type) { return post(new TestRequest(path).setBody(body), type); } + @Override + public List posts(String path, Object body, Class type) { + return run(new TestRequest(path).setMethod(HttpMethods.POST.name()).setBody(body)) + .getBodies(type); + } + @Override public String post(String path, Object body) { return post(new TestRequest(path).setBody(body)); } + @Override + public List posts(String path, Object body) { + return posts(path, body, String.class); + } + @Override public T put(TestRequest request, Class type) { return run(request.setMethod(HttpMethods.PUT.name()), type); From 3a123e1beeb2dc6712c92c4a6b2dc32ada3bedfd Mon Sep 17 00:00:00 2001 From: oxsean Date: Thu, 20 Jun 2024 09:33:11 +0000 Subject: [PATCH 07/12] Method npe bugfix --- .../rpc/protocol/tri/rest/mapping/meta/MethodMeta.java | 10 +++++++++- .../rpc/protocol/tri/rest/RestProtocolTest.groovy | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) 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 3b09ff120f6..ce3e2350d66 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 @@ -36,11 +36,19 @@ public final class MethodMeta extends AnnotationSupport { public MethodMeta(List hierarchy, MethodDescriptor methodDescriptor, ServiceMeta serviceMeta) { super(serviceMeta.getToolKit()); this.hierarchy = hierarchy; - method = methodDescriptor == null ? hierarchy.get(0) : methodDescriptor.getMethod(); + method = initMethod(hierarchy, methodDescriptor); this.methodDescriptor = methodDescriptor; this.serviceMeta = serviceMeta; } + private Method initMethod(List hierarchy, MethodDescriptor methodDescriptor) { + Method method = null; + if (methodDescriptor != null) { + method = methodDescriptor.getMethod(); + } + return method == null ? hierarchy.get(hierarchy.size() - 1) : method; + } + public void initParameters() { int count = method.getParameterCount(); List> parameterHierarchies = new ArrayList<>(count); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy index e7c0879d41a..d42b1f8fcde 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy @@ -156,4 +156,5 @@ class RestProtocolTest extends BaseServiceTest { path | output '/pbServerStream?request={"service": "3"}' | 3 } + } From 64acfeb12e47a3f2908b32daf25ba557741488b5 Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Fri, 19 Jul 2024 21:40:27 +0800 Subject: [PATCH 08/12] bean bind --- dubbo-plugin/dubbo-rest-jaxrs/pom.xml | 47 +++ .../support/jaxrs/BeanArgumentBinder.java | 276 +++---------- .../rest/support/jaxrs/JaxrsRestToolKit.java | 14 +- .../support/jaxrs/RestProtocolTest.groovy | 71 ++++ .../jaxrs/service/JaxrsDemoService.java | 34 ++ .../jaxrs/service/JaxrsDemoServiceImpl.java | 30 ++ .../tri/rest/support/jaxrs/service/User.java | 68 +++ .../rest/support/jaxrs/service/UserForm.java | 56 +++ .../src/test/resources/log4j2-test.xml | 1 + dubbo-plugin/dubbo-rest-spring/pom.xml | 42 ++ .../AbstractSpringArgumentResolver.java | 4 +- .../tri/rest/support/spring/Annotations.java | 1 + .../support/spring/BeanArgumentBinder.java | 103 +---- .../spring/BindParamArgumentResolver.java | 57 +++ .../SpringMvcRequestMappingResolver.java | 1 + ...rotocol.tri.rest.argument.ArgumentResolver | 1 + .../support/spring/RestProtocolTest.groovy | 72 ++++ .../spring/service/SpringDemoService.java | 27 ++ .../spring/service/SpringDemoServiceImpl.java | 27 ++ .../src/test/resources/log4j2-test.xml | 1 + .../http12/message/DefaultHttpRequest.java | 6 +- .../dubbo/remoting/http12/rest/Param.java | 2 +- dubbo-rpc/dubbo-rpc-triple/pom.xml | 20 + .../argument/CompositeArgumentResolver.java | 4 + .../rest/argument/GeneralTypeConverter.java | 3 +- .../NamedValueArgumentResolverSupport.java | 4 +- .../DefaultRequestMappingRegistry.java | 3 + .../rest/mapping/meta/AnnotationSupport.java | 20 +- .../tri/rest/mapping/meta/BeanMeta.java | 387 ++++++++++++++++++ .../tri/rest/mapping/meta/ParameterMeta.java | 16 +- .../basic/BasicRequestMappingResolver.java | 3 +- .../rest/support/basic/BasicRestToolKit.java | 44 ++ .../support/basic/BeanArgumentBinder.java | 375 +++++++++++++++++ .../basic/FallbackArgumentResolver.java | 24 +- ...tToolKit.java => AbstractRestToolKit.java} | 17 +- .../rpc/protocol/tri/rest/util/RestUtils.java | 31 ++ .../rpc/protocol/tri/rest/util/TypeUtils.java | 160 +++++++- .../basic}/RestProtocolTest.groovy | 53 ++- .../tri/rest/service/DemoService.java | 4 + .../tri/rest/service/DemoServiceImpl.java | 16 +- .../rpc/protocol/tri/rest/service/User.java | 283 +++++++++++++ .../rpc/protocol/tri/test/TestRequest.java | 4 + .../rpc/protocol/tri/test/TestResponse.java | 13 + .../rpc/protocol/tri/test/TestRunnerImpl.java | 31 +- .../src/test/resources/log4j2-test.xml | 1 + pom.xml | 10 +- 46 files changed, 2068 insertions(+), 399 deletions(-) create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/RestProtocolTest.groovy create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoService.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoServiceImpl.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/User.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/UserForm.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BindParamArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RestProtocolTest.groovy create mode 100644 dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/BeanMeta.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRestToolKit.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BeanArgumentBinder.java rename dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/{DefaultRestToolKit.java => AbstractRestToolKit.java} (84%) rename dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/{ => support/basic}/RestProtocolTest.groovy (59%) create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/User.java diff --git a/dubbo-plugin/dubbo-rest-jaxrs/pom.xml b/dubbo-plugin/dubbo-rest-jaxrs/pom.xml index 6370496dfae..32b43a2d002 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/pom.xml +++ b/dubbo-plugin/dubbo-rest-jaxrs/pom.xml @@ -62,5 +62,52 @@ jaxb-runtime test + + org.yaml + snakeyaml + test + + + org.apache.dubbo + dubbo-rpc-triple + ${project.version} + test-jar + test + + + org.spockframework + spock-core + test + + + + + + org.codehaus.gmavenplus + gmavenplus-plugin + + + + compileTests + + + + + + + + + + rpc-rest-test + + + org.apache.dubbo + dubbo-rpc-rest + 3.3.0-beta.2 + test + + + + diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanArgumentBinder.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanArgumentBinder.java index 294875bd953..90f0da7955b 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanArgumentBinder.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanArgumentBinder.java @@ -27,254 +27,90 @@ import org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver; import org.apache.dubbo.rpc.protocol.tri.rest.argument.CompositeArgumentResolver; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.ConstructorMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.FieldMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.SetMethodMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; -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.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.Parameter; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; import java.util.Map; +import java.util.Set; final class BeanArgumentBinder { - private final ArgumentResolver argumentResolver; + private static final ThreadLocal>> LOCAL = new ThreadLocal<>(); private final Map, String>, BeanMeta> cache = CollectionUtils.newConcurrentHashMap(); + private final ArgumentResolver argumentResolver; BeanArgumentBinder(FrameworkModel frameworkModel) { ScopeBeanFactory beanFactory = frameworkModel.getBeanFactory(); argumentResolver = beanFactory.getOrRegisterBean(CompositeArgumentResolver.class); } - public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) { + public Object bind(ParameterMeta paramMeta, HttpRequest request, HttpResponse response) { + Set> walked = LOCAL.get(); + if (walked == null) { + LOCAL.set(new HashSet<>()); + } try { - return resolveArgument(parameter, request, response); + return resolveArgument(paramMeta, request, response); } catch (Exception e) { - throw new RestException(Messages.ARGUMENT_BIND_ERROR, parameter.getName(), parameter.getType(), e); - } - } - - private Object resolveArgument(ParameterMeta param, HttpRequest request, HttpResponse response) throws Exception { - AnnotationMeta form = param.findAnnotation(Annotations.Form); - if (form != null || param.isHierarchyAnnotated(Annotations.BeanParam)) { - String prefix = form == null ? null : form.getString("prefix"); - BeanMeta beanMeta = cache.computeIfAbsent( - Pair.of(param.getActualType(), prefix), - k -> new BeanMeta(param.getToolKit(), k.getValue(), k.getKey())); - - ConstructorMeta constructor = beanMeta.constructor; - ParameterMeta[] parameters = constructor.parameters; - int len = parameters.length; - Object[] args = new Object[len]; - for (int i = 0; i < len; i++) { - args[i] = resolveArgument(parameters[i], request, response); - } - Object bean = constructor.newInstance(args); - - for (FieldMeta fieldMeta : beanMeta.fields) { - fieldMeta.set(bean, resolveArgument(fieldMeta, request, response)); - } - - for (SetMethodMeta methodMeta : beanMeta.methods) { - methodMeta.invoke(bean, resolveArgument(methodMeta, request, response)); + throw new RestException(e, Messages.ARGUMENT_BIND_ERROR, paramMeta.getName(), paramMeta.getType()); + } finally { + if (walked == null) { + LOCAL.remove(); } - return bean; } - - return argumentResolver.resolve(param, request, response); } - private static class BeanMeta { - - private final List fields = new ArrayList<>(); - private final List methods = new ArrayList<>(); - private final ConstructorMeta constructor; - - BeanMeta(RestToolKit toolKit, String prefix, Class type) { - constructor = resolveConstructor(toolKit, prefix, type); - resolveFieldAndMethod(toolKit, prefix, type); - } - - private ConstructorMeta resolveConstructor(RestToolKit toolKit, String prefix, Class type) { - Constructor[] constructors = type.getConstructors(); - Constructor ct = null; - if (constructors.length == 1) { - ct = constructors[0]; - } else { - try { - ct = type.getDeclaredConstructor(); - } catch (NoSuchMethodException ignored) { + private Object resolveArgument(ParameterMeta paramMeta, HttpRequest request, HttpResponse response) { + if (paramMeta.isSimple()) { + return argumentResolver.resolve(paramMeta, request, response); + } + + // Prevent infinite loops + if (LOCAL.get().add(paramMeta.getActualType())) { + AnnotationMeta form = paramMeta.findAnnotation(Annotations.Form); + if (form != null || paramMeta.isHierarchyAnnotated(Annotations.BeanParam)) { + String prefix = form == null ? null : form.getString("prefix"); + BeanMeta beanMeta = cache.computeIfAbsent( + Pair.of(paramMeta.getActualType(), prefix), + k -> new BeanMeta(paramMeta.getToolKit(), k.getValue(), k.getKey())); + + ConstructorMeta constructor = beanMeta.getConstructor(); + ParameterMeta[] parameters = constructor.getParameters(); + Object bean; + int len = parameters.length; + if (len == 0) { + bean = constructor.newInstance(); + } else { + Object[] args = new Object[len]; + for (int i = 0; i < len; i++) { + args[i] = resolveArgument(parameters[i], request, response); + } + bean = constructor.newInstance(args); } - } - if (ct == null) { - throw new IllegalArgumentException("No available default constructor found in " + type); - } - return new ConstructorMeta(toolKit, prefix, ct); - } - private void resolveFieldAndMethod(RestToolKit toolKit, String prefix, Class type) { - if (type == Object.class) { - return; - } - for (Field field : type.getDeclaredFields()) { - int modifiers = field.getModifiers(); - if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) { - continue; - } - if (field.getAnnotations().length == 0) { - continue; - } - if (!field.isAccessible()) { - field.setAccessible(true); - } - fields.add(new FieldMeta(toolKit, prefix, field)); - } - for (Method method : type.getDeclaredMethods()) { - if (method.getParameterCount() != 1) { - continue; + Set resolved = new HashSet<>(); + for (FieldMeta fieldMeta : beanMeta.getFields()) { + resolved.add(fieldMeta.getName()); + fieldMeta.setValue(bean, resolveArgument(fieldMeta, request, response)); } - int modifiers = method.getModifiers(); - if ((modifiers & (Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.STATIC)) == Modifier.PUBLIC) { - Parameter parameter = method.getParameters()[0]; - String name = method.getName(); - if (name.length() > 3 && name.startsWith("set")) { - name = Character.toLowerCase(name.charAt(3)) + name.substring(4); - methods.add(new SetMethodMeta(toolKit, method, parameter, prefix, name)); + + for (SetMethodMeta methodMeta : beanMeta.getMethods()) { + if (resolved.contains(methodMeta.getName())) { + continue; } + methodMeta.setValue(bean, resolveArgument(methodMeta, request, response)); } - } - resolveFieldAndMethod(toolKit, prefix, type.getSuperclass()); - } - } - - private static final class ConstructorMeta { - private final Constructor constructor; - private final ConstructorParameterMeta[] parameters; - - ConstructorMeta(RestToolKit toolKit, String prefix, Constructor constructor) { - this.constructor = constructor; - parameters = initParameters(toolKit, prefix, constructor); - } - - private ConstructorParameterMeta[] initParameters(RestToolKit toolKit, String prefix, Constructor ct) { - Parameter[] cps = ct.getParameters(); - int len = cps.length; - ConstructorParameterMeta[] parameters = new ConstructorParameterMeta[len]; - for (int i = 0; i < len; i++) { - parameters[i] = new ConstructorParameterMeta(toolKit, cps[i], prefix); + return bean; } - return parameters; } - Object newInstance(Object[] args) throws Exception { - return constructor.newInstance(args); - } - } - - public static final class ConstructorParameterMeta extends ParameterMeta { - - private final Parameter parameter; - - ConstructorParameterMeta(RestToolKit toolKit, Parameter parameter, String prefix) { - super(toolKit, prefix, parameter.isNamePresent() ? parameter.getName() : null); - this.parameter = parameter; - } - - @Override - protected AnnotatedElement getAnnotatedElement() { - return parameter; - } - - @Override - public Class getType() { - return parameter.getType(); - } - - @Override - public Type getGenericType() { - return parameter.getParameterizedType(); - } - - @Override - public String getDescription() { - return "ConstructorParameter{" + parameter + '}'; - } - } - - private static final class FieldMeta extends ParameterMeta { - - private final Field field; - - FieldMeta(RestToolKit toolKit, String prefix, Field field) { - super(toolKit, prefix, field.getName()); - this.field = field; - } - - @Override - public Class getType() { - return field.getType(); - } - - @Override - public Type getGenericType() { - return field.getGenericType(); - } - - @Override - protected AnnotatedElement getAnnotatedElement() { - return field; - } - - public void set(Object bean, Object value) throws Exception { - field.set(bean, value); - } - - @Override - public String getDescription() { - return "FieldParameter{" + field + '}'; - } - } - - private static final class SetMethodMeta extends ParameterMeta { - - private final Method method; - private final Parameter parameter; - - SetMethodMeta(RestToolKit toolKit, Method method, Parameter parameter, String prefix, String name) { - super(toolKit, prefix, name); - this.method = method; - this.parameter = parameter; - } - - @Override - public Class getType() { - return parameter.getType(); - } - - @Override - public Type getGenericType() { - return parameter.getParameterizedType(); - } - - @Override - protected AnnotatedElement getAnnotatedElement() { - return parameter; - } - - public void invoke(Object bean, Object value) throws Exception { - method.invoke(bean, value); - } - - @Override - public String getDescription() { - return "SetMethodParameter{" + method + '}'; - } + return TypeUtils.nullDefault(paramMeta.getType()); } } 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 95ef839b87d..6fa3b3699be 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 @@ -21,12 +21,12 @@ import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; -import org.apache.dubbo.rpc.protocol.tri.rest.util.DefaultRestToolKit; +import org.apache.dubbo.rpc.protocol.tri.rest.util.AbstractRestToolKit; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; -final class JaxrsRestToolKit extends DefaultRestToolKit { +final class JaxrsRestToolKit extends AbstractRestToolKit { private final BeanArgumentBinder binder; @@ -35,6 +35,11 @@ public JaxrsRestToolKit(FrameworkModel frameworkModel) { binder = new BeanArgumentBinder(frameworkModel); } + @Override + public int getDialect() { + return RestConstants.DIALECT_JAXRS; + } + @Override public Object convert(Object value, ParameterMeta parameter) { if (MultivaluedMap.class.isAssignableFrom(parameter.getType())) { @@ -46,11 +51,6 @@ public Object convert(Object value, ParameterMeta parameter) { return super.convert(value, parameter); } - @Override - public int getDialect() { - return RestConstants.DIALECT_JAXRS; - } - @Override public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) { return binder.bind(parameter, request, response); diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/RestProtocolTest.groovy b/dubbo-plugin/dubbo-rest-jaxrs/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/RestProtocolTest.groovy new file mode 100644 index 00000000000..64c6e0e7cdf --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/RestProtocolTest.groovy @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs + +import org.apache.dubbo.remoting.http12.message.MediaType +import org.apache.dubbo.rpc.protocol.tri.rest.service.DemoServiceImpl +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service.JaxrsDemoServiceImpl +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 JaxrsDemoServiceImpl()) + } + + 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' + } + + def "form param test"() { + expect: + TestRequest request = new TestRequest( + path: path, + contentType: MediaType.APPLICATION_FROM_URLENCODED, + body: body + ) + runner.post(request) == output + where: + path | body | output + '/formTest' | ['user.first': 'sam', 'user.last': 'lee'] | '{"contentType":"application/x-www-form-urlencoded","firstName":"sam","lastName":"lee"}' + } + + def "bean param test"() { + expect: + TestRequest request = new TestRequest( + path: path, + contentType: MediaType.APPLICATION_FROM_URLENCODED, + body: body + ) + runner.post(request) == output + where: + path | body | output + '/beanTest/2?name=sam' | ['first': 'sam', 'last': 'lee'] | '{"form":{"contentType":"application/x-www-form-urlencoded","firstName":"sam","lastName":"lee"},"id":2,"name":"sam"}' + } + +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoService.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoService.java new file mode 100644 index 00000000000..7af104fb406 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoService.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service; + +import javax.ws.rs.BeanParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +import org.jboss.resteasy.annotations.Form; + +public interface JaxrsDemoService { + + @POST + @Path("/formTest") + UserForm formTest(@Form(prefix = "user") UserForm userForm); + + @POST + @Path("/beanTest/{id}") + User getTest(@BeanParam User user); +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoServiceImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoServiceImpl.java new file mode 100644 index 00000000000..a54240fd750 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoServiceImpl.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service; + +public class JaxrsDemoServiceImpl implements JaxrsDemoService { + + @Override + public UserForm formTest(UserForm userForm) { + return userForm; + } + + @Override + public User getTest(User user) { + return user; + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/User.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/User.java new file mode 100644 index 00000000000..a901d1345f9 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/User.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service; + +import javax.ws.rs.BeanParam; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; + +public class User { + + @PathParam("id") + private Long id; + + @QueryParam("name") + private String name; + + @BeanParam + private User owner; + + @BeanParam + private UserForm form; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + + public UserForm getForm() { + return form; + } + + public void setForm(UserForm form) { + this.form = form; + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/UserForm.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/UserForm.java new file mode 100644 index 00000000000..60f202f4694 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/UserForm.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service; + +import javax.ws.rs.FormParam; +import javax.ws.rs.HeaderParam; + +public class UserForm { + + @FormParam("first") + String firstName; + + @FormParam("last") + String lastName; + + @HeaderParam("Content-Type") + String contentType; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/log4j2-test.xml b/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/log4j2-test.xml index 34d1c53e88a..91337f333b1 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/log4j2-test.xml +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/log4j2-test.xml @@ -23,6 +23,7 @@ + diff --git a/dubbo-plugin/dubbo-rest-spring/pom.xml b/dubbo-plugin/dubbo-rest-spring/pom.xml index 55f7a982607..a18d8a9f232 100644 --- a/dubbo-plugin/dubbo-rest-spring/pom.xml +++ b/dubbo-plugin/dubbo-rest-spring/pom.xml @@ -76,5 +76,47 @@ jaxb-runtime test + + org.apache.dubbo + dubbo-rpc-triple + ${project.version} + test-jar + test + + + org.spockframework + spock-core + test + + + + + + org.codehaus.gmavenplus + gmavenplus-plugin + + + + compileTests + + + + + + + + + + rpc-rest-test + + + org.apache.dubbo + dubbo-rpc-rest + 3.3.0-beta.2 + test + + + + diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java index f60cdb7d2f2..5f3b89e2789 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java @@ -23,12 +23,14 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; import java.lang.annotation.Annotation; +import java.util.Optional; public abstract class AbstractSpringArgumentResolver extends AbstractAnnotationBaseArgumentResolver { @Override protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta ann) { - return new NamedValueMeta(ann.getValue(), Helper.isRequired(ann), Helper.defaultValue(ann)); + boolean required = param.getType() != Optional.class && Helper.isRequired(ann); + return new NamedValueMeta(ann.getValue(), required, Helper.defaultValue(ann)); } @Override diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java index 974a56dea2d..17936d09cee 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java @@ -31,6 +31,7 @@ public enum Annotations implements AnnotationEnum { RequestPart, RequestBody, ModelAttribute, + BindParam, ResponseStatus, CrossOrigin, ExceptionHandler, diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BeanArgumentBinder.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BeanArgumentBinder.java index 17005d5fda3..d020e5425ab 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BeanArgumentBinder.java +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BeanArgumentBinder.java @@ -26,15 +26,11 @@ import org.apache.dubbo.rpc.protocol.tri.rest.RestException; import org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver; import org.apache.dubbo.rpc.protocol.tri.rest.argument.CompositeArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.ConstructorMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; -import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; -import java.lang.reflect.Parameter; -import java.lang.reflect.Type; import java.util.Map; import org.springframework.beans.MutablePropertyValues; @@ -44,23 +40,25 @@ import org.springframework.validation.DataBinder; import org.springframework.web.bind.WebDataBinder; +import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.resolveConstructor; + final class BeanArgumentBinder { + private static final Map, ConstructorMeta> CACHE = CollectionUtils.newConcurrentHashMap(); + private final ArgumentResolver argumentResolver; private final ConversionService conversionService; - private final Map, ConstructorMeta> cache = CollectionUtils.newConcurrentHashMap(); - BeanArgumentBinder(FrameworkModel frameworkModel, ConversionService conversionService) { ScopeBeanFactory beanFactory = frameworkModel.getBeanFactory(); argumentResolver = beanFactory.getOrRegisterBean(CompositeArgumentResolver.class); this.conversionService = conversionService; } - public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) { - String name = StringUtils.defaultIf(parameter.getName(), DataBinder.DEFAULT_OBJECT_NAME); + public Object bind(ParameterMeta paramMeta, HttpRequest request, HttpResponse response) { + String name = StringUtils.defaultIf(paramMeta.getName(), DataBinder.DEFAULT_OBJECT_NAME); try { - Object bean = buildBean(parameter, request, response); + Object bean = createBean(paramMeta, request, response); WebDataBinder binder = new WebDataBinder(bean, name); binder.setConversionService(conversionService); binder.bind(new MutablePropertyValues(RequestUtils.getParametersMap(request))); @@ -70,89 +68,26 @@ public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse re } return binder.getTarget(); } catch (Exception e) { - throw new RestException(e, Messages.ARGUMENT_BIND_ERROR, name, parameter.getType()); + throw new RestException(e, Messages.ARGUMENT_BIND_ERROR, name, paramMeta.getType()); } } - private Object buildBean(ParameterMeta parameter, HttpRequest request, HttpResponse response) throws Exception { - Class type = parameter.getActualType(); + private Object createBean(ParameterMeta paramMeta, HttpRequest request, HttpResponse response) { + Class type = paramMeta.getActualType(); if (Modifier.isAbstract(type.getModifiers())) { - throw new IllegalStateException(Messages.ARGUMENT_COULD_NOT_RESOLVED.format(parameter.getDescription())); + throw new IllegalStateException(Messages.ARGUMENT_COULD_NOT_RESOLVED.format(paramMeta.getDescription())); } - ConstructorMeta ct = cache.computeIfAbsent(type, k -> resolveConstructor(parameter.getToolKit(), k)); - ParameterMeta[] parameters = ct.parameters; + ConstructorMeta ct = CACHE.computeIfAbsent(type, k -> resolveConstructor(paramMeta.getToolKit(), null, type)); + ParameterMeta[] parameters = ct.getParameters(); int len = parameters.length; + if (len == 0) { + return ct.newInstance(); + } Object[] args = new Object[len]; for (int i = 0; i < len; i++) { - args[i] = argumentResolver.resolve(parameters[i], request, response); + ParameterMeta parameter = parameters[i]; + args[i] = parameter.isSimple() ? argumentResolver.resolve(parameter, request, response) : null; } return ct.newInstance(args); } - - private ConstructorMeta resolveConstructor(RestToolKit toolKit, Class type) { - Constructor[] constructors = type.getConstructors(); - Constructor ct = null; - if (constructors.length == 1) { - ct = constructors[0]; - } else { - try { - ct = type.getDeclaredConstructor(); - } catch (NoSuchMethodException ignored) { - } - } - if (ct == null) { - throw new IllegalArgumentException("No available default constructor found in " + type); - } - return new ConstructorMeta(toolKit, ct); - } - - private static final class ConstructorMeta { - - private final Constructor constructor; - private final ConstructorParameterMeta[] parameters; - - ConstructorMeta(RestToolKit toolKit, Constructor constructor) { - this.constructor = constructor; - parameters = initParameters(toolKit, constructor); - } - - private ConstructorParameterMeta[] initParameters(RestToolKit toolKit, Constructor ct) { - Parameter[] cps = ct.getParameters(); - int len = cps.length; - ConstructorParameterMeta[] parameters = new ConstructorParameterMeta[len]; - for (int i = 0; i < len; i++) { - parameters[i] = new ConstructorParameterMeta(toolKit, cps[i]); - } - return parameters; - } - - Object newInstance(Object[] args) throws Exception { - return constructor.newInstance(args); - } - } - - public static final class ConstructorParameterMeta extends ParameterMeta { - - private final Parameter parameter; - - ConstructorParameterMeta(RestToolKit toolKit, Parameter parameter) { - super(toolKit, parameter.isNamePresent() ? parameter.getName() : null); - this.parameter = parameter; - } - - @Override - protected AnnotatedElement getAnnotatedElement() { - return parameter; - } - - @Override - public Class getType() { - return parameter.getType(); - } - - @Override - public Type getGenericType() { - return parameter.getParameterizedType(); - } - } } diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BindParamArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BindParamArgumentResolver.java new file mode 100644 index 00000000000..a4906b7aec4 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BindParamArgumentResolver.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.spring; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.AbstractAnnotationBaseArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +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; + +import java.lang.annotation.Annotation; + +@Activate(onClass = "org.springframework.web.bind.annotation.BindParam") +public final class BindParamArgumentResolver extends AbstractAnnotationBaseArgumentResolver { + + @Override + public Class accept() { + return Annotations.BindParam.type(); + } + + @Override + protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta ann) { + return new NamedValueMeta(ann.getValue(), param.isAnnotated(Annotations.Nonnull), null); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.parameter(meta.name()); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.parameterValues(meta.name()); + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return RequestUtils.getParametersMap(request); + } +} 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 a43bb50a741..728156a98d3 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 @@ -85,6 +85,7 @@ public RequestMapping resolve(MethodMeta methodMeta) { if (requestMapping == null && httpExchange == null) { AnnotationMeta exceptionHandler = methodMeta.getAnnotation(Annotations.ExceptionHandler); if (exceptionHandler != null) { + methodMeta.initParameters(); methodMeta.getServiceMeta().addExceptionHandler(methodMeta); } return null; diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver b/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver index 7b0136aee70..36e95f532ce 100644 --- a/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver +++ b/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver @@ -7,6 +7,7 @@ spring-request-attribute=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.R spring-request-part=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.RequestPartArgumentResolver spring-request-body=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.RequestBodyArgumentResolver spring-model-attribute=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.ModelAttributeArgumentResolver +spring-bind-param=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.BindParamArgumentResolver spring-misc=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.SpringMiscArgumentResolver spring-fallback=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.FallbackArgumentResolver 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 new file mode 100644 index 00000000000..ec1935b1221 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RestProtocolTest.groovy @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.rpc.protocol.tri.rest.support.spring + +import org.apache.dubbo.rpc.protocol.tri.rest.service.DemoServiceImpl +import org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service.SpringDemoServiceImpl +import org.apache.dubbo.rpc.protocol.tri.rest.test.BaseServiceTest +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()) + } + + 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' + } + + 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 + } + +} 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 new file mode 100644 index 00000000000..bdf66f566a6 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service; + +import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx; + +import org.springframework.web.bind.annotation.GetMapping; + +public interface SpringDemoService { + + @GetMapping("/springBean") + UserEx beanArgTest(UserEx user); +} 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 new file mode 100644 index 00000000000..97ef3cfe71c --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service; + +import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx; + +public class SpringDemoServiceImpl implements SpringDemoService { + + @Override + public UserEx beanArgTest(UserEx user) { + return user; + } +} 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 34d1c53e88a..91337f333b1 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 @@ -23,6 +23,7 @@ + diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java index cc67e9544be..5c88a5e1b07 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java @@ -484,7 +484,7 @@ public List formParameterValues(String name) { values.add(HttpUtils.readPostValue(item)); } } - return values; + return values == null ? Collections.emptyList() : values; } @Override @@ -507,7 +507,7 @@ public Collection formParameterNames() { names.add(item.getName()); } } - return names; + return names == null ? Collections.emptyList() : names; } @Override @@ -553,7 +553,7 @@ public Collection parameterNames() { allNames.add(item.getName()); } } - return allNames; + return allNames == null ? Collections.emptyList() : allNames; } @Override diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Param.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Param.java index de3f3382279..2ff94d4d32d 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Param.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Param.java @@ -22,7 +22,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.PARAMETER) +@Target({ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Param { diff --git a/dubbo-rpc/dubbo-rpc-triple/pom.xml b/dubbo-rpc/dubbo-rpc-triple/pom.xml index a48c3e58616..4dfcbaac20e 100644 --- a/dubbo-rpc/dubbo-rpc-triple/pom.xml +++ b/dubbo-rpc/dubbo-rpc-triple/pom.xml @@ -173,6 +173,26 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + package + + false + + org/apache/dubbo/rpc/protocol/tri/rest/** + org/apache/dubbo/rpc/protocol/tri/test/** + + + + + + org.codehaus.gmavenplus gmavenplus-plugin diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentResolver.java index 9a8690f6114..69c50585bc6 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentResolver.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentResolver.java @@ -74,4 +74,8 @@ public Object resolve(ParameterMeta parameter, HttpRequest request, HttpResponse throw new IllegalStateException(Messages.ARGUMENT_COULD_NOT_RESOLVED.format(parameter.getDescription())); } + + public ArgumentConverter getArgumentConverter() { + return argumentConverter; + } } 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 4752512f414..89dc6153f77 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 @@ -922,8 +922,7 @@ private Map createMap(Class targetClass, int size) { defCt = ct; break; case 1: - Class paramType = ct.getParameterTypes()[0]; - if (paramType == int.class) { + if (ct.getParameterTypes()[0] == int.class) { return (Map) ct.newInstance(CollectionUtils.capacity(size)); } break; 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 54996942b6e..d7401c57cf6 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 @@ -67,7 +67,7 @@ protected final NamedValueMeta updateNamedValueMeta(ParameterMeta parameterMeta, Class type = parameterMeta.getType(); Type genericType = parameterMeta.getGenericType(); if (type == Optional.class) { - Class actualType = TypeUtils.getNestedType(genericType, 0); + Class actualType = TypeUtils.getNestedActualType(genericType, 0); meta.setType(actualType == null ? Object.class : actualType); } else { meta.setType(type); @@ -75,7 +75,7 @@ protected final NamedValueMeta updateNamedValueMeta(ParameterMeta parameterMeta, if (type.isArray()) { meta.setNestedTypes(new Class[] {type.getComponentType()}); } else { - meta.setNestedTypes(TypeUtils.getNestedTypes(genericType)); + meta.setNestedTypes(TypeUtils.getNestedActualTypes(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 8cfa7856e3e..17edec21012 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 @@ -161,6 +161,9 @@ public void unregister(Invoker invoker) { @Override public void destroy() { + if (tree == null) { + return; + } lock.writeLock().lock(); try { tree.clear(); 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 393106e2d3d..35592ce137f 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 @@ -71,7 +71,7 @@ public final Annotation[] getRealAnnotations() { return result; } - public final AnnotationMeta getAnnotation(Class annotationType) { + public final AnnotationMeta getAnnotation(Class annotationType) { return cache.computeIfAbsent(Pair.of(annotationType, GET_KEY), k -> { AnnotatedElement element = getAnnotatedElement(); Annotation annotation = element.getAnnotation(annotationType); @@ -87,7 +87,7 @@ public final AnnotationMeta getAnnotation(AnnotationEnum annotationEnum) { return annotationEnum.isPresent() ? getAnnotation(annotationEnum.type()) : null; } - public final boolean isAnnotated(Class annotationType) { + public final boolean isAnnotated(Class annotationType) { return getAnnotation(annotationType) != null; } @@ -95,7 +95,7 @@ public final boolean isAnnotated(AnnotationEnum annotationEnum) { return getAnnotation(annotationEnum) != null; } - public final AnnotationMeta getMergedAnnotation(Class annotationType) { + public final AnnotationMeta getMergedAnnotation(Class annotationType) { return cache.computeIfAbsent(Pair.of(annotationType, GET_MERGED_KEY), k -> { AnnotatedElement element = getAnnotatedElement(); Annotation[] annotations = element.getAnnotations(); @@ -117,7 +117,7 @@ public final AnnotationMeta getMergedAnnotation(AnnotationEnum annotationEnum) { return annotationEnum.isPresent() ? getMergedAnnotation(annotationEnum.type()) : null; } - public final boolean isMergedAnnotated(Class annotationType) { + public final boolean isMergedAnnotated(Class annotationType) { return getMergedAnnotation(annotationType) != null; } @@ -143,7 +143,7 @@ public final AnnotationMeta[] findAnnotations() { }); } - public final AnnotationMeta findAnnotation(Class annotationType) { + public final AnnotationMeta findAnnotation(Class annotationType) { return cache.computeIfAbsent(Pair.of(annotationType, FIND_KEY), k -> { List elements = getAnnotatedElements(); for (int i = 0, len = elements.size(); i < len; i++) { @@ -162,15 +162,15 @@ public final AnnotationMeta findAnnotation(AnnotationEnum annotationEnum) { return annotationEnum.isPresent() ? findAnnotation(annotationEnum.type()) : null; } - public final boolean isHierarchyAnnotated(Class annotationType) { - return getMergedAnnotation(annotationType) != null; + public final boolean isHierarchyAnnotated(Class annotationType) { + return findAnnotation(annotationType) != null; } public final boolean isHierarchyAnnotated(AnnotationEnum annotationEnum) { - return getMergedAnnotation(annotationEnum) != null; + return findAnnotation(annotationEnum) != null; } - public final AnnotationMeta findMergedAnnotation(Class annotationType) { + public final AnnotationMeta findMergedAnnotation(Class annotationType) { return cache.computeIfAbsent(Pair.of(annotationType, FIND_MERGED_KEY), k -> { List elements = getAnnotatedElements(); for (int i = 0, len = elements.size(); i < len; i++) { @@ -196,7 +196,7 @@ public final AnnotationMeta findMergedAnnotation(AnnotationEnum annotationEnum) return annotationEnum.isPresent() ? findMergedAnnotation(annotationEnum.type()) : null; } - public final boolean isMergedHierarchyAnnotated(Class annotationType) { + public final boolean isMergedHierarchyAnnotated(Class annotationType) { return findMergedAnnotation(annotationType) != null; } 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 new file mode 100644 index 00000000000..ce8fd206e19 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/BeanMeta.java @@ -0,0 +1,387 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta; + +import org.apache.dubbo.remoting.http12.rest.Param; +import org.apache.dubbo.rpc.protocol.tri.rest.RestException; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; +import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; + +import javax.annotation.Nullable; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +public final class BeanMeta { + + private final Map fields = new LinkedHashMap<>(); + private final Map methods = new LinkedHashMap<>(); + private final ConstructorMeta constructor; + + public BeanMeta(RestToolKit toolKit, String prefix, Class type) { + constructor = resolveConstructor(toolKit, prefix, type); + resolveFieldAndMethod(toolKit, prefix, type); + } + + public BeanMeta(RestToolKit toolKit, Class type) { + this(toolKit, null, type); + } + + public Collection getFields() { + return fields.values(); + } + + public FieldMeta getField(String name) { + return fields.get(name); + } + + public Collection getMethods() { + return methods.values(); + } + + public SetMethodMeta getMethod(String name) { + return methods.get(name); + } + + public ConstructorMeta getConstructor() { + return constructor; + } + + public Object newInstance() { + return constructor.newInstance(); + } + + public static ConstructorMeta resolveConstructor(RestToolKit toolKit, String prefix, Class type) { + Constructor[] constructors = type.getConstructors(); + Constructor ct = null; + if (constructors.length == 1) { + ct = constructors[0]; + } else { + try { + ct = type.getDeclaredConstructor(); + } catch (NoSuchMethodException ignored) { + } + } + if (ct == null) { + throw new IllegalArgumentException("No available default constructor found in " + type); + } + return new ConstructorMeta(toolKit, prefix, ct); + } + + private void resolveFieldAndMethod(RestToolKit toolKit, String prefix, Class type) { + if (type == Object.class) { + return; + } + for (Field field : type.getDeclaredFields()) { + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) { + continue; + } + if (field.getAnnotations().length == 0) { + continue; + } + if (!field.isAccessible()) { + field.setAccessible(true); + } + FieldMeta fieldMeta = new FieldMeta(toolKit, prefix, field); + fields.put(fieldMeta.getName(), fieldMeta); + } + for (Method method : type.getDeclaredMethods()) { + if (method.getParameterCount() != 1) { + continue; + } + int modifiers = method.getModifiers(); + if ((modifiers & (Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.STATIC)) == Modifier.PUBLIC) { + Parameter parameter = method.getParameters()[0]; + String name = method.getName(); + if (name.length() > 3 && name.startsWith("set")) { + String getMethodName = "get" + name.substring(3); + Method getMethod = null; + try { + getMethod = type.getDeclaredMethod(getMethodName); + } catch (NoSuchMethodException ignored) { + } + name = Character.toLowerCase(name.charAt(3)) + name.substring(4); + SetMethodMeta methodMeta = new SetMethodMeta(toolKit, method, getMethod, parameter, prefix, name); + methods.put(methodMeta.getName(), methodMeta); + } + } + } + resolveFieldAndMethod(toolKit, prefix, type.getSuperclass()); + } + + public static final class ConstructorMeta { + + private final Constructor constructor; + private final ConstructorParameterMeta[] parameters; + + ConstructorMeta(RestToolKit toolKit, String prefix, Constructor constructor) { + this.constructor = constructor; + parameters = initParameters(toolKit, prefix, constructor); + } + + public ConstructorParameterMeta[] getParameters() { + return parameters; + } + + private ConstructorParameterMeta[] initParameters(RestToolKit toolKit, String prefix, Constructor ct) { + Parameter[] cps = ct.getParameters(); + int len = cps.length; + ConstructorParameterMeta[] parameters = new ConstructorParameterMeta[len]; + for (int i = 0; i < len; i++) { + parameters[i] = new ConstructorParameterMeta(toolKit, cps[i], prefix); + } + return parameters; + } + + public Object newInstance(Object... args) { + try { + return constructor.newInstance(args); + } catch (Throwable t) { + throw RestException.wrap(t); + } + } + } + + public static final class ConstructorParameterMeta extends ParameterMeta { + + private final Parameter parameter; + + ConstructorParameterMeta(RestToolKit toolKit, Parameter parameter, String prefix) { + super(toolKit, prefix, parameter.isNamePresent() ? parameter.getName() : null); + this.parameter = parameter; + } + + @Override + protected AnnotatedElement getAnnotatedElement() { + return parameter; + } + + @Override + public Class getType() { + return parameter.getType(); + } + + @Override + public Type getGenericType() { + return parameter.getParameterizedType(); + } + + @Override + public String getDescription() { + return "ConstructorParameter{" + parameter + '}'; + } + } + + public abstract static class NestableParameterMeta extends ParameterMeta { + + private NestableParameterMeta nestedMeta; + private String finalName; + + public NestableParameterMeta(RestToolKit toolKit, String prefix, String name) { + super(toolKit, prefix, name); + } + + @Nullable + @Override + public final String getName() { + String name = finalName; + if (name == null) { + AnnotationMeta param = findAnnotation(Param.class); + if (param != null) { + name = param.getValue(); + } + if (name == null || name.isEmpty()) { + name = super.getName(); + } + finalName = name; + } + return name; + } + + public void setValue(Object bean, Object value) {} + + public Object getValue(Object bean) { + return null; + } + + public final NestableParameterMeta getNestedMeta() { + return nestedMeta; + } + + protected final void initNestedMeta() { + Type nestedType = null; + Class type = getType(); + if (Map.class.isAssignableFrom(type)) { + nestedType = TypeUtils.getNestedGenericType(getGenericType(), 1); + } else if (Collection.class.isAssignableFrom(type)) { + nestedType = TypeUtils.getNestedGenericType(getGenericType(), 0); + } else if (type.isArray()) { + Type genericType = getGenericType(); + if (genericType instanceof GenericArrayType) { + nestedType = ((GenericArrayType) genericType).getGenericComponentType(); + } else { + nestedType = type.getComponentType(); + } + } + nestedMeta = nestedType == null ? null : new NestedMeta(getToolKit(), nestedType); + } + } + + public static final class FieldMeta extends NestableParameterMeta { + + private final Field field; + + FieldMeta(RestToolKit toolKit, String prefix, Field field) { + super(toolKit, prefix, field.getName()); + this.field = field; + initNestedMeta(); + } + + @Override + public Class getType() { + return field.getType(); + } + + @Override + public Type getGenericType() { + return field.getGenericType(); + } + + @Override + protected AnnotatedElement getAnnotatedElement() { + return field; + } + + public void setValue(Object bean, Object value) { + try { + field.set(bean, value); + } catch (Throwable t) { + throw RestException.wrap(t); + } + } + + public Object getValue(Object bean) { + try { + return field.get(bean); + } catch (Throwable t) { + throw RestException.wrap(t); + } + } + + @Override + public String getDescription() { + return "FieldParameter{" + field + '}'; + } + } + + public static final class SetMethodMeta extends NestableParameterMeta { + + private final Method method; + private final Method getMethod; + private final Parameter parameter; + + SetMethodMeta(RestToolKit toolKit, Method m, Method gm, Parameter p, String prefix, String name) { + super(toolKit, prefix, name); + method = m; + getMethod = gm; + parameter = p; + initNestedMeta(); + } + + @Override + public Class getType() { + return parameter.getType(); + } + + @Override + public Type getGenericType() { + return parameter.getParameterizedType(); + } + + @Override + protected AnnotatedElement getAnnotatedElement() { + return parameter; + } + + public void setValue(Object bean, Object value) { + try { + method.invoke(bean, value); + } catch (Throwable t) { + throw RestException.wrap(t); + } + } + + public Object getValue(Object bean) { + if (getMethod == null) { + return null; + } + try { + return getMethod.invoke(bean); + } catch (Throwable t) { + throw RestException.wrap(t); + } + } + + @Override + public String getDescription() { + return "SetMethodParameter{" + method + '}'; + } + } + + public static final class NestedMeta extends NestableParameterMeta { + + private final Class type; + private final Type genericType; + + NestedMeta(RestToolKit toolKit, Type genericType) { + super(toolKit, null, null); + type = TypeUtils.getActualType(genericType); + this.genericType = genericType; + initNestedMeta(); + } + + @Override + public Class getType() { + return type; + } + + @Override + public Type getGenericType() { + return genericType; + } + + @Override + protected AnnotatedElement getAnnotatedElement() { + return null; + } + + @Override + public String getDescription() { + return "NestedParameter{" + (genericType == null ? type : genericType) + '}'; + } + } +} 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 94a89fc9b6a..2def7071227 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 @@ -16,7 +16,6 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta; -import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.HttpRequest; import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.rpc.protocol.tri.rest.Messages; @@ -27,6 +26,7 @@ import javax.annotation.Nullable; import java.lang.reflect.Type; +import java.util.Collection; import java.util.Optional; public abstract class ParameterMeta extends AnnotationSupport { @@ -39,7 +39,7 @@ public abstract class ParameterMeta extends AnnotationSupport { protected ParameterMeta(RestToolKit toolKit, String prefix, String name) { super(toolKit); - this.prefix = StringUtils.isEmpty(prefix) ? null : prefix; + this.prefix = prefix; this.name = name; } @@ -54,11 +54,12 @@ public String getPrefix() { } @Nullable - public final String getName() { + public String getName() { return name; } - public final String getRequiredName() { + public String getRequiredName() { + String name = getName(); if (name == null) { throw new RestException(Messages.ARGUMENT_NAME_MISSING, getType()); } @@ -68,7 +69,10 @@ public final String getRequiredName() { public final boolean isSimple() { Boolean simple = this.simple; if (simple == null) { - simple = TypeUtils.isSimpleProperty(getActualType()); + Class type = Collection.class.isAssignableFrom(getType()) + ? TypeUtils.getNestedActualType(getGenericType(), 0) + : getActualType(); + simple = TypeUtils.isSimpleProperty(type); this.simple = simple; } return simple; @@ -79,7 +83,7 @@ public final Class getActualType() { if (type == null) { type = getType(); if (type == Optional.class) { - type = TypeUtils.getNestedType(getGenericType(), 0); + type = TypeUtils.getNestedActualType(getGenericType(), 0); if (type == null) { type = Object.class; } 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 269c315f46d..db39ea43f17 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 @@ -28,7 +28,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; -import org.apache.dubbo.rpc.protocol.tri.rest.util.DefaultRestToolKit; import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; @@ -43,7 +42,7 @@ public class BasicRequestMappingResolver implements RequestMappingResolver { public BasicRequestMappingResolver(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; - toolKit = new DefaultRestToolKit(frameworkModel); + toolKit = new BasicRestToolKit(frameworkModel); } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRestToolKit.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRestToolKit.java new file mode 100644 index 00000000000..8aac208d8bf --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRestToolKit.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.basic; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.AbstractRestToolKit; + +final class BasicRestToolKit extends AbstractRestToolKit { + + private final BeanArgumentBinder binder; + + public BasicRestToolKit(FrameworkModel frameworkModel) { + super(frameworkModel); + binder = new BeanArgumentBinder(frameworkModel); + } + + @Override + public int getDialect() { + return RestConstants.DIALECT_BASIC; + } + + @Override + public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) { + return binder.bind(parameter, request, response); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BeanArgumentBinder.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BeanArgumentBinder.java new file mode 100644 index 00000000000..93214b2bff2 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BeanArgumentBinder.java @@ -0,0 +1,375 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.basic; + +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.Pair; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.rest.Param; +import org.apache.dubbo.remoting.http12.rest.ParamType; +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.CompositeArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.ConstructorMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.FieldMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.NestableParameterMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.SetMethodMeta; +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.Array; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +final class BeanArgumentBinder { + + private static final Map, BeanMeta> CACHE = CollectionUtils.newConcurrentHashMap(); + + private final CompositeArgumentResolver argumentResolver; + + public BeanArgumentBinder(FrameworkModel frameworkModel) { + argumentResolver = frameworkModel.getBeanFactory().getOrRegisterBean(CompositeArgumentResolver.class); + } + + public Object bind(ParameterMeta paramMeta, HttpRequest request, HttpResponse response) { + try { + BeanMeta beanMeta = getBeanMeta(paramMeta); + if (beanMeta == null) { + return null; + } + + ConstructorMeta constructor = beanMeta.getConstructor(); + ParameterMeta[] parameters = constructor.getParameters(); + Object bean; + int len = parameters.length; + if (len == 0) { + bean = constructor.newInstance(); + } else { + Object[] args = new Object[len]; + for (int i = 0; i < len; i++) { + ParameterMeta parameter = parameters[i]; + args[i] = parameter.isSimple() ? argumentResolver.resolve(parameter, request, response) : null; + } + bean = constructor.newInstance(args); + } + + Node root = new Node(paramMeta.getName(), bean, beanMeta); + for (String paramName : request.parameterNames()) { + Node current = root; + List parts = StringUtils.tokenizeToList(paramName, '.'); + for (int i = 0, size = parts.size(); i < size; i++) { + if (current == null) { + break; + } + String name = parts.get(i); + Pair pair = parseKeyParam(name); + if (pair == null) { + current = current.getChild(name); + if (i == 0 && current == null && name.equals(root.name)) { + current = root; + } + } else { + name = pair.getLeft(); + current = current.getChild(name); + if (current == null) { + break; + } + String key = pair.getValue(); + if (!key.isEmpty()) { + if (Character.isDigit(key.charAt(0))) { + try { + current = current.getChild(Long.parseLong(key)); + continue; + } catch (NumberFormatException ignored) { + } + } + current = current.getChild(key); + } + } + } + if (current == null) { + continue; + } + Class type = current.paramMeta.getActualType(); + Object value; + if (type.isArray() || Collection.class.isAssignableFrom(type)) { + value = request.parameterValues(paramName); + } else { + value = request.parameter(paramName); + } + //noinspection unchecked + current.setValue(argumentResolver.getArgumentConverter().convert(value, current.paramMeta)); + } + + for (FieldMeta fieldMeta : beanMeta.getFields()) { + resolveParam(fieldMeta, bean, request, response); + } + + for (SetMethodMeta methodMeta : beanMeta.getMethods()) { + resolveParam(methodMeta, bean, request, response); + } + + return bean; + } catch (Exception e) { + throw new RestException(e, Messages.ARGUMENT_BIND_ERROR, paramMeta.getName(), paramMeta.getType()); + } + } + + private void resolveParam(NestableParameterMeta meta, Object bean, HttpRequest request, HttpResponse response) { + AnnotationMeta param = meta.getAnnotation(Param.class); + if (param == null || param.getAnnotation().type() == ParamType.Param) { + return; + } + meta.setValue(bean, argumentResolver.resolve(meta, request, response)); + } + + private static BeanMeta getBeanMeta(ParameterMeta paramMeta) { + Class type = paramMeta.getActualType(); + if (paramMeta.isSimple() || Modifier.isAbstract(type.getModifiers())) { + return null; + } + return CACHE.computeIfAbsent(type, k -> new BeanMeta(paramMeta.getToolKit(), k)); + } + + /** + * See + *

+ * Spring beans-binding + */ + private static Pair parseKeyParam(String name) { + int len = name.length(); + if (name.charAt(len - 1) == ']') { + int start = name.lastIndexOf('['); + if (start > -1) { + return Pair.of(name.substring(0, start), name.substring(start + 1, len - 1)); + } + } + return null; + } + + private static final class Node { + + public final String name; + public NestableParameterMeta paramMeta; + public Consumer setter; + public Object value; + public Map children; + public BeanMeta beanMeta; + + public Node(String name, NestableParameterMeta paramMeta, Consumer setter) { + this.name = name; + this.paramMeta = paramMeta; + this.setter = setter; + } + + public Node(String name, Object value, BeanMeta beanMeta) { + this.name = name; + this.value = value; + this.beanMeta = beanMeta; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public Node getChild(String name) { + if (children != null) { + Node node = children.get(name); + if (node != null) { + return node; + } + } + + if (beanMeta == null) { + Class type = paramMeta.getType(); + if (Map.class.isAssignableFrom(type)) { + return createChild(name, paramMeta.getNestedMeta(), v -> ((Map) value).put(name, v)); + } + return null; + } + + NestableParameterMeta methodMeta = beanMeta.getMethod(name); + if (methodMeta != null) { + return createChild(name, methodMeta, v -> methodMeta.setValue(value, v)); + } + + NestableParameterMeta fieldMeta = beanMeta.getField(name); + if (fieldMeta != null) { + return createChild(name, fieldMeta, v -> fieldMeta.setValue(value, v)); + } + + return null; + } + + @SuppressWarnings({"rawtypes", "unchecked", "SuspiciousSystemArraycopy"}) + public Node getChild(long num) { + if (children != null) { + Node node = children.get(num); + if (node != null) { + return node; + } + } + + NestableParameterMeta nestedMeta = paramMeta.getNestedMeta(); + Class type = paramMeta.getType(); + Object childValue = null; + Node child; + if (List.class.isAssignableFrom(type)) { + int index = (int) num; + List list = (List) value; + child = new Node(name, nestedMeta, v -> { + if (index < 0) { + return; + } + while (list.size() <= index) { + list.add(null); + } + list.set(index, v); + }); + if (index < list.size()) { + childValue = list.get(index); + } + } else if (type.isArray()) { + int index = (int) num; + int len = Array.getLength(value); + child = new Node(name, nestedMeta, v -> { + if (num >= 0 && num < len) { + Array.set(value, index, v); + return; + } + int tail = index < 0 ? len : index; + Object newArr = Array.newInstance(value.getClass().getComponentType(), tail + 1); + System.arraycopy(value, 0, newArr, 0, len); + Array.set(newArr, tail, v); + setter.accept(newArr); + }); + if (index < len) { + childValue = Array.get(value, index); + } + } else if (Map.class.isAssignableFrom(type)) { + Class keyType = TypeUtils.getNestedActualType(paramMeta.getGenericType(), 0); + Object key = TypeUtils.longToObject(num, keyType); + child = new Node(name, nestedMeta, v -> { + ((Map) value).put(key, v); + }); + childValue = ((Map) value).get(key); + } else { + return null; + } + + if (childValue == null) { + childValue = createValue(nestedMeta.getType()); + if (childValue == null) { + if (nestedMeta.isSimple()) { + return child; + } + BeanMeta beanMeta = getBeanMeta(nestedMeta); + if (beanMeta == null) { + return null; + } + child.beanMeta = beanMeta; + childValue = beanMeta.newInstance(); + } + child.setter.accept(childValue); + } else { + child.beanMeta = getBeanMeta(nestedMeta); + } + + putChild(child, num, childValue); + return child; + } + + public void setValue(Object value) { + setter.accept(value); + } + + private Node createChild(String name, NestableParameterMeta paramMeta, Consumer setter) { + Class type = paramMeta.getType(); + if (type.isArray()) { + Consumer arraySetter = setter; + setter = v -> { + arraySetter.accept(v); + if (children != null) { + children.get(name).value = v; + } + }; + } + Node child = new Node(name, paramMeta, setter); + Object childValue = paramMeta.getValue(value); + boolean created = false; + if (childValue == null) { + if (value instanceof Map) { + childValue = ((Map) value).get(name); + if (childValue != null) { + child.beanMeta = getBeanMeta(paramMeta); + } + } + if (childValue == null) { + childValue = createValue(type); + if (childValue == null) { + if (paramMeta.isSimple()) { + return child; + } + } else { + created = true; + } + } + } + if (childValue == null) { + BeanMeta beanMeta = getBeanMeta(paramMeta); + if (beanMeta == null) { + return null; + } + child.beanMeta = beanMeta; + childValue = beanMeta.newInstance(); + created = true; + } + if (created) { + setter.accept(childValue); + } + putChild(child, name, childValue); + return child; + } + + private Object createValue(Class type) { + if (Map.class.isAssignableFrom(type)) { + return TypeUtils.createMap(type); + } + if (Collection.class.isAssignableFrom(type)) { + return TypeUtils.createCollection(type); + } + if (type.isArray()) { + return Array.newInstance(type.getComponentType(), 1); + } + return null; + } + + private void putChild(Node child, Object name, Object value) { + child.value = value; + if (children == null) { + children = new HashMap<>(); + } + children.put(name, child); + } + } +} 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 338208ee139..ab4cbd97a64 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 @@ -25,6 +25,7 @@ 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; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestUtils; import java.util.List; import java.util.Map; @@ -44,24 +45,24 @@ protected NamedValueMeta createNamedValueMeta(ParameterMeta param) { @Override protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { - return doResolveValue(meta, request, true); + return doResolveValue(meta, true, request, response); } @Override protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { - return doResolveValue(meta, request, false); + return doResolveValue(meta, false, request, response); } - protected Object doResolveValue(NamedValueMeta meta, HttpRequest request, boolean single) { + protected Object doResolveValue(NamedValueMeta meta, boolean single, HttpRequest request, HttpResponse response) { + ParameterMeta parameter = meta.parameterMeta(); if (HttpMethods.supportBody(request.method())) { - ParameterMeta parameterMeta = meta.parameterMeta(); - Object body = RequestUtils.decodeBody(request, parameterMeta.getType(), parameterMeta.isSingle()); + Object body = RequestUtils.decodeBody(request, parameter.getType(), parameter.isSingle()); if (body != null) { - if (parameterMeta.getType().isInstance(body)) { + if (parameter.getType().isInstance(body)) { return body; } else if (body instanceof List) { List list = (List) body; - int index = parameterMeta.getIndex(); + int index = parameter.getIndex(); if (index >= 0 && list.size() > index) { return list.get(index); } @@ -73,7 +74,14 @@ protected Object doResolveValue(NamedValueMeta meta, HttpRequest request, boolea } } } - return single ? request.parameter(meta.name()) : request.parameterValues(meta.name()); + if (single) { + String value = request.parameter(meta.name()); + if (parameter.isSimple() || RestUtils.isMaybeJSONObject(value)) { + return value; + } + return parameter.bind(request, response); + } + return request.parameterValues(meta.name()); } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/DefaultRestToolKit.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java similarity index 84% rename from dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/DefaultRestToolKit.java rename to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java index bf654e32073..c35cd414d5a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/DefaultRestToolKit.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java @@ -18,10 +18,7 @@ import org.apache.dubbo.common.config.Environment; import org.apache.dubbo.common.utils.AnnotationUtils; -import org.apache.dubbo.remoting.http12.HttpRequest; -import org.apache.dubbo.remoting.http12.HttpResponse; import org.apache.dubbo.rpc.model.FrameworkModel; -import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; 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; @@ -32,21 +29,16 @@ import java.lang.reflect.Parameter; import java.util.Map; -public class DefaultRestToolKit implements RestToolKit { +public abstract class AbstractRestToolKit implements RestToolKit { protected final FrameworkModel frameworkModel; protected final TypeConverter typeConverter; - public DefaultRestToolKit(FrameworkModel frameworkModel) { + public AbstractRestToolKit(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; typeConverter = frameworkModel.getBeanFactory().getOrRegisterBean(GeneralTypeConverter.class); } - @Override - public int getDialect() { - return RestConstants.DIALECT_BASIC; - } - @Override public String resolvePlaceholders(String text) { return RestUtils.hasPlaceholder(text) ? getEnvironment().resolvePlaceholders(text) : text; @@ -61,11 +53,6 @@ public Object convert(Object value, ParameterMeta parameter) { return typeConverter.convert(value, parameter.getGenericType()); } - @Override - public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) { - return null; - } - @Override public String[] getParameterNames(Method method) { Parameter[] parameters = method.getParameters(); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestUtils.java index a5e5331dc58..1155d3860a4 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestUtils.java @@ -21,6 +21,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension; public final class RestUtils { + private RestUtils() {} public static boolean hasPlaceholder(String text) { @@ -50,6 +51,36 @@ public static boolean hasPlaceholder(String text) { return false; } + public static boolean isMaybeJSONObject(String str) { + if (str == null) { + return false; + } + int i = 0, n = str.length(); + if (n < 3) { + return false; + } + char expected = 0; + for (; i < n; i++) { + char c = str.charAt(i); + if (Character.isWhitespace(c)) { + continue; + } + if (c == '{') { + expected = '}'; + break; + } + return false; + } + for (int j = n - 1; j > i; j--) { + char c = str.charAt(j); + if (Character.isWhitespace(c)) { + continue; + } + return c == expected; + } + return false; + } + public static int getPriority(Object obj) { if (obj instanceof Prioritized) { int priority = ((Prioritized) obj).getPriority(); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java index 04b2a618d54..fc77b1f10b0 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java @@ -22,8 +22,10 @@ import java.io.File; import java.lang.reflect.Array; +import java.lang.reflect.Constructor; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -35,18 +37,34 @@ import java.nio.file.Path; import java.time.ZoneId; import java.time.temporal.Temporal; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Currency; import java.util.Date; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.Queue; import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; import java.util.TimeZone; +import java.util.TreeMap; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; public final class TypeUtils { + private static final Set> SIMPLE_TYPES = new ConcurrentHashSet<>(); static { @@ -125,15 +143,15 @@ public static Class getMapValueType(Class targetClass) { } public static Class getSuperGenericType(Class clazz, int index) { - Class result = getNestedType(clazz.getGenericSuperclass(), index); - return result == null ? getNestedType(ArrayUtils.first(clazz.getGenericInterfaces()), index) : result; + Class result = getNestedActualType(clazz.getGenericSuperclass(), index); + return result == null ? getNestedActualType(ArrayUtils.first(clazz.getGenericInterfaces()), index) : result; } public static Class getSuperGenericType(Class clazz) { return getSuperGenericType(clazz, 0); } - public static Class[] getNestedTypes(Type type) { + public static Class[] getNestedActualTypes(Type type) { if (type instanceof ParameterizedType) { Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments(); int len = typeArgs.length; @@ -146,7 +164,7 @@ public static Class[] getNestedTypes(Type type) { return null; } - public static Class getNestedType(Type type, int index) { + public static Class getNestedActualType(Type type, int index) { if (type instanceof ParameterizedType) { Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments(); if (index < typeArgs.length) { @@ -176,6 +194,16 @@ public static Class getActualType(Type type) { return null; } + public static Type getNestedGenericType(Type type, int index) { + if (type instanceof ParameterizedType) { + Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments(); + if (index < typeArgs.length) { + return getActualGenericType(typeArgs[index]); + } + } + return null; + } + public static Type getActualGenericType(Type type) { if (type instanceof TypeVariable) { return ((TypeVariable) type).getBounds()[0]; @@ -217,6 +245,130 @@ public static Object nullDefault(Class targetClass) { return null; } + public static Object longToObject(long value, Class targetClass) { + if (targetClass == Long.class) { + return value; + } + if (targetClass == Integer.class) { + return (int) value; + } + if (targetClass == Short.class) { + return (short) value; + } + if (targetClass == Character.class) { + return (char) value; + } + if (targetClass == Byte.class) { + return (byte) value; + } + return null; + } + + @SuppressWarnings("rawtypes") + public static Collection createCollection(Class targetClass) { + if (targetClass.isInterface()) { + if (targetClass == List.class || targetClass == Collection.class) { + return new ArrayList<>(); + } + if (targetClass == Set.class) { + return new HashSet<>(); + } + if (targetClass == SortedSet.class) { + return new LinkedHashSet<>(); + } + if (targetClass == Queue.class || targetClass == Deque.class) { + return new LinkedList<>(); + } + } else if (Collection.class.isAssignableFrom(targetClass)) { + if (targetClass == ArrayList.class) { + return new ArrayList<>(); + } + if (targetClass == LinkedList.class) { + return new LinkedList(); + } + if (targetClass == HashSet.class) { + return new HashSet<>(); + } + if (targetClass == LinkedHashSet.class) { + return new LinkedHashSet<>(); + } + if (!Modifier.isAbstract(targetClass.getModifiers())) { + try { + Constructor sizeCt = null; + for (Constructor ct : targetClass.getConstructors()) { + switch (ct.getParameterCount()) { + case 0: + return (Collection) ct.newInstance(); + case 1: + if (ct.getParameterTypes()[0] == int.class) { + sizeCt = ct; + } + break; + default: + } + } + if (sizeCt != null) { + return (Collection) sizeCt.newInstance(16); + } + } catch (Exception ignored) { + } + } + } + throw new IllegalArgumentException("Unsupported collection type: " + targetClass.getName()); + } + + @SuppressWarnings("rawtypes") + public static Map createMap(Class targetClass) { + if (targetClass.isInterface()) { + if (targetClass == Map.class) { + return new HashMap<>(); + } + if (targetClass == ConcurrentMap.class) { + return new ConcurrentHashMap<>(); + } + if (SortedMap.class.isAssignableFrom(targetClass)) { + return new TreeMap<>(); + } + } else if (Map.class.isAssignableFrom(targetClass)) { + if (targetClass == HashMap.class) { + return new HashMap<>(); + } + if (targetClass == LinkedHashMap.class) { + return new LinkedHashMap<>(); + } + if (targetClass == TreeMap.class) { + return new TreeMap<>(); + } + if (targetClass == ConcurrentHashMap.class) { + return new ConcurrentHashMap<>(); + } + if (!Modifier.isAbstract(targetClass.getModifiers())) { + try { + Constructor sizeCt = null; + for (Constructor ct : targetClass.getConstructors()) { + if (Modifier.isPublic(ct.getModifiers())) { + switch (ct.getParameterCount()) { + case 0: + return (Map) ct.newInstance(); + case 1: + if (ct.getParameterTypes()[0] == int.class) { + sizeCt = ct; + } + break; + default: + } + } + } + if (sizeCt != null) { + return (Map) sizeCt.newInstance(16); + } + } catch (Throwable ignored) { + } + } + } + throw new IllegalArgumentException("Unsupported map type: " + targetClass.getName()); + } + public static String buildSig(Method method) { StringBuilder sb = new StringBuilder(8); for (Class type : method.getParameterTypes()) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy similarity index 59% rename from dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy rename to dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy index d42b1f8fcde..23692bc0207 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/RestProtocolTest.groovy +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.dubbo.rpc.protocol.tri.rest +package org.apache.dubbo.rpc.protocol.tri.rest.support.basic import org.apache.dubbo.remoting.http12.message.MediaType import org.apache.dubbo.rpc.protocol.tri.rest.service.Book @@ -51,13 +51,24 @@ class RestProtocolTest extends BaseServiceTest { '/argTest?name=Sam' | '{"age": 8}' | 'Sam is 8 years old' } + def "bean argument test"() { + expect: + runner.post(path, body, Book.class).name == output + where: + path | body | output + '/buy' | new Book(name: "Dubbo") | 'Dubbo' + '/buy' | [new Book(name: "Dubbo")] | 'Dubbo' + '/buy2' | [new Book(name: "Dubbo"), 2] | 'Dubbo' + '/buy2' | [book: new Book(name: "Dubbo"), count: 2] | 'Dubbo' + } + def "bean argument get test"() { expect: runner.get(path, Book.class).price == output where: path | output '/beanArgTest' | 0 - '/beanArgTest?quote=5' | 0 + '/beanArgTest?quote=5' | 5 '/beanArgTest?book={"price": 6}' | 6 '/beanArgTest?book={"price": 6}"e=5' | 5 } @@ -68,21 +79,41 @@ class RestProtocolTest extends BaseServiceTest { where: path | body | output '/beanArgTest' | [] | 0 - '/beanArgTest' | [quote: 5] | 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] | 0 + '/beanArgTest' | [price: 6, quote: 5] | 5 } - def "bean test"() { + def "advance bean argument get test"() { expect: - runner.post(path, body, Book.class).name == output + runner.get(path) contains output where: - path | body | output - '/buy' | new Book(name: "Dubbo") | 'Dubbo' - '/buy' | [new Book(name: "Dubbo")] | 'Dubbo' - '/buy2' | [new Book(name: "Dubbo"), 2] | 'Dubbo' - '/buy2' | [book: new Book(name: "Dubbo"), count: 2] | 'Dubbo' + path | output + '/bean?id=1&name=sam' | '"id":1,"name":"sam"' + '/bean?user.id=1&user.name=sam' | '"id":1,"name":"sam"' + '/bean?name=sam&p=123&email=a@b.com' | '"email":"a@b.com","name":"sam","phone":"123"' + '/bean?group.name=g1&group.owner.name=jack' | '"group":{"id":0,"name":"g1","owner":{"name":"jack"' + '/bean?group.parent.parent.children[0].name=xx' | '"group":{"id":0,"parent":{"id":0,"parent":{"children":[{"id":0,"name":"xx"}],"id":0}}}' + '/bean?group={"name":"g1","id":2}' | '"group":{"id":2,"name":"g1"}' + '/bean?ids=3&ids=4' | '"ids":[3,4]' + '/bean?ids[]=3&ids[]=4' | '"ids":[3,4]' + '/bean?ids[1]=3&ids[2]=4' | '"ids":[0,3,4]' + '/bean?scores=3&scores=4' | '"scores":[3,4]' + '/bean?scores[]=3&scores[]=4' | '"scores":[3,4]' + '/bean?scores[1]=3&scores[2]=4' | '"scores":[null,3,4]' + '/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"}]' + '/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"}]' + '/bean?tagsB[0].name=e&tagsB[1].name=c&tagsB[1].value=d' | '"tagsB":[{"name":"e","value":"b"},{"name":"c","value":"d"}]' + '/bean?tagsC[0].name=e&tagsC[1].name=c&tagsC[1].value=d' | '"tagsC":[{"name":"e","value":"b"},{"name":"c","value":"d"}]' + '/bean?groupMaps[0].one.name=a&groupMaps[1].two.name=b' | '"groupMaps":[{"one":{"id":0,"name":"a"}},{"two":{"id":0,"name":"b"}}]' + '/bean?id=1&features.a=xx&features.b=2' | '"features":{"a":"xx","b":"2"}' + '/bean?id=1&features[a]=xx&features[b]=2' | '"features":{"a":"xx","b":"2"}' + '/bean?group.id=2&group.features.a=1&group.features.b=xx' | '"group":{"features":{"a":"1","b":"xx"},"id":2}' + '/bean?tagMap.a.name=a&tagMap.a.value=b&tagMap.b.name=c&tagMap.b.value=d' | '"tagMap":{"a":{"name":"a","value":"b"},"b":{"name":"c","value":"d"}}' + '/bean?tagMapA.a.name=e&tagMapA.b.name=c&tagMapA.b.value=d' | '"tagMapA":{"a":{"name":"e","value":"b"},"b":{"name":"c","value":"d"}}' + '/bean?tagMapB[2].name=a&tagMapB[2].value=b&tagMapB[3].name=c' | '"tagMapB":{2:{"name":"a","value":"b"},3:{"name":"c"}}' + '/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 "urlEncodeForm test"() { 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 a2cf6da7143..cb3cc8f1da5 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,6 +18,7 @@ import org.apache.dubbo.common.stream.StreamObserver; import org.apache.dubbo.remoting.http12.rest.Mapping; +import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx; import io.grpc.health.v1.HealthCheckRequest; import io.grpc.health.v1.HealthCheckResponse; @@ -31,6 +32,9 @@ public interface DemoService { Book beanArgTest(Book book, Integer quote); + @Mapping("/bean") + UserEx advanceBeanArgTest(UserEx user); + 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 cf7f03ace2e..d17e71a00dd 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 @@ -19,13 +19,12 @@ import org.apache.dubbo.common.stream.StreamObserver; 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.UserEx; import io.grpc.health.v1.HealthCheckRequest; import io.grpc.health.v1.HealthCheckResponse; import io.grpc.health.v1.HealthCheckResponse.ServingStatus; -import static io.grpc.health.v1.HealthCheckResponse.newBuilder; - public class DemoServiceImpl implements DemoService { @Override @@ -42,12 +41,18 @@ public String argTest(String name, int age) { public Book beanArgTest(Book book, Integer quote) { if (book == null) { book = new Book(); - } else if (quote != null) { + } + if (quote != null) { book.setPrice(quote); } return book; } + @Override + public UserEx advanceBeanArgTest(UserEx user) { + return user; + } + @Override public Book buy(Book book) { return book; @@ -88,8 +93,9 @@ public void pbServerStream(HealthCheckRequest request, StreamObserver scores; + private List tags; + private Tag[] tagsA; + private List tagsB = new ArrayList<>(); + private Tag[] tagsC = new Tag[] {new Tag("a", "b")}; + private List> groupMaps; + private Map features; + private Map tagMap; + private Map tagMapA = new HashMap<>(); + private Map tagMapB; + private Map> groupsMap; + + public User() { + tagsB.add(new Tag("a", "b")); + tagMapA.put("a", new Tag("a", "b")); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } + + public long[] getIds() { + return ids; + } + + public void setIds(long[] ids) { + this.ids = ids; + } + + public List getScores() { + return scores; + } + + public void setScores(List scores) { + this.scores = scores; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + public Tag[] getTagsA() { + return tagsA; + } + + public void setTagsA(Tag[] tagsA) { + this.tagsA = tagsA; + } + + public List getTagsB() { + return tagsB; + } + + public void setTagsB(List tagsB) { + this.tagsB = tagsB; + } + + public Tag[] getTagsC() { + return tagsC; + } + + public void setTagsC(Tag[] tagsC) { + this.tagsC = tagsC; + } + + public List> getGroupMaps() { + return groupMaps; + } + + public void setGroupMaps(List> groupMaps) { + this.groupMaps = groupMaps; + } + + public Map getFeatures() { + return features; + } + + public void setFeatures(Map features) { + this.features = features; + } + + public Map getTagMap() { + return tagMap; + } + + public void setTagMap(Map tagMap) { + this.tagMap = tagMap; + } + + public Map getTagMapA() { + return tagMapA; + } + + public void setTagMapA(Map tagMapA) { + this.tagMapA = tagMapA; + } + + public Map getTagMapB() { + return tagMapB; + } + + public void setTagMapB(Map tagMapB) { + this.tagMapB = tagMapB; + } + + public Map> getGroupsMap() { + return groupsMap; + } + + public void setGroupsMap(Map> groupsMap) { + this.groupsMap = groupsMap; + } + + public static class UserEx extends User { + + private String email; + + @Param(value = "p") + private String phone; + + public UserEx(String email) { + this.email = email; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + } + + public static class Group { + + private int id; + private String name; + private User owner; + private Group parent; + private List children; + private Map features; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + + public Group getParent() { + return parent; + } + + public void setParent(Group parent) { + this.parent = parent; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public Map getFeatures() { + return features; + } + + public void setFeatures(Map features) { + this.features = features; + } + } + + public static class Tag { + + private String name; + private String value; + + public Tag() {} + + public Tag(String name, String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java index 4099cdf345c..7a14a0f82f6 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java @@ -72,6 +72,10 @@ public TestRequest setMethod(HttpMethods method) { return setMethod(method.name()); } + public String getContentType() { + return headers.getFirst(HttpHeaderNames.CONTENT_TYPE.getName()); + } + public TestRequest setContentType(String contentType) { headers.set(HttpHeaderNames.CONTENT_TYPE.getName(), contentType); return this; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java index eaac8efaeff..89ffbddc89c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestResponse.java @@ -20,6 +20,7 @@ import org.apache.dubbo.remoting.http12.HttpHeaders; import org.apache.dubbo.remoting.http12.h2.Http2Headers; import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.rpc.protocol.tri.rest.RestException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -54,11 +55,23 @@ public int getStatus() { return Integer.parseInt(headers.getFirst(Http2Headers.STATUS.getName())); } + public boolean isOk() { + return getStatus() == 200; + } + public String getContentType() { return headers.getFirst(HttpHeaderNames.CONTENT_TYPE.getName()); } public T getBody(Class type) { + if (type != String.class) { + int status = getStatus(); + if (status >= 400) { + List bodies = getBodies(String.class); + String message = bodies.isEmpty() ? null : bodies.get(0); + throw new RestException(status, "status: " + status + ", body: " + message); + } + } List bodies = getBodies(type); return bodies.isEmpty() ? null : bodies.get(0); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java index d55f948539d..a0451f39173 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRunnerImpl.java @@ -28,7 +28,9 @@ import org.apache.dubbo.remoting.http12.h2.Http2InputMessageFrame; import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; +import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.remoting.http12.message.codec.JsonCodec; +import org.apache.dubbo.remoting.http12.message.codec.UrlEncodeFormCodec; import org.apache.dubbo.rpc.Protocol; import org.apache.dubbo.rpc.ProxyFactory; import org.apache.dubbo.rpc.model.ApplicationModel; @@ -91,7 +93,7 @@ private void registerProvider(TProvider provider, Protocol protocol, Prox } @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "resource"}) public TestResponse run(TestRequest request) { MockH2StreamChannel channel = new MockH2StreamChannel(); URL url = new URL(TestProtocol.NAME, TestProtocol.HOST, TestProtocol.PORT, request.getProviderParams()); @@ -165,8 +167,24 @@ public TestResponse run(TestRequest request) { RpcInvocationBuildContext context = listener.getContext(); HttpMessageDecoder decoder = JsonCodec.INSTANCE; if (context != null) { - HttpMessageEncoder encoder = context.getHttpMessageEncoder(); - decoder = context.getHttpMessageDecoder(); + String ct = request.getContentType(); + boolean isForm = ct != null && ct.startsWith(MediaType.APPLICATION_FROM_URLENCODED.getName()); + HttpMessageEncoder encoder; + Object coder; + if (isForm) { + encoder = new UrlEncodeFormCodec(null); + coder = context.getHttpMessageDecoder(); + } else { + encoder = context.getHttpMessageEncoder(); + coder = encoder; + } + if (coder instanceof RestHttpMessageCodec) { + coder = ((RestHttpMessageCodec) coder).getMessageEncoder(); + if (coder instanceof HttpMessageDecoder) { + decoder = (HttpMessageDecoder) coder; + } + } + HttpRequest hRequest = (HttpRequest) context.getAttributes().get(TripleConstant.HTTP_REQUEST_KEY); if (CollectionUtils.isEmpty(request.getBodies())) { if (HttpMethods.supportBody(hRequest.method())) { @@ -186,13 +204,6 @@ public TestResponse run(TestRequest request) { } listener.onData(END); } - - if (encoder instanceof RestHttpMessageCodec) { - encoder = ((RestHttpMessageCodec) encoder).getMessageEncoder(); - } - if (encoder instanceof HttpMessageDecoder) { - decoder = (HttpMessageDecoder) encoder; - } } return new TestResponse(channel.getHttpMetadata().headers(), channel.getBodies(), decoder); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/resources/log4j2-test.xml b/dubbo-rpc/dubbo-rpc-triple/src/test/resources/log4j2-test.xml index 34d1c53e88a..91337f333b1 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/resources/log4j2-test.xml +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/resources/log4j2-test.xml @@ -23,6 +23,7 @@ + diff --git a/pom.xml b/pom.xml index 912c7fa1a2f..84737f70612 100644 --- a/pom.xml +++ b/pom.xml @@ -144,6 +144,7 @@ 3.1.0 1.7.1 0.6.1 + 3.0.2 3.22.3 1.54.0 @@ -362,14 +363,7 @@ org.codehaus.gmavenplus gmavenplus-plugin - 3.0.2 - - - - compileTests - - - + ${maven_gmavenplus.version} org.apache.maven.plugins From 4e3083d221b169ab0ab89dc4a3fd47d7147b6573 Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Sat, 20 Jul 2024 15:34:01 +0800 Subject: [PATCH 09/12] don't applyCustomizeException in not grpc mode --- dubbo-rpc/dubbo-rpc-triple/pom.xml | 13 ++++++++++-- .../tri/h12/UnaryServerCallListener.java | 10 ++++----- .../GrpcHttp2ServerTransportListener.java | 14 +++++++++++++ .../DefaultHttp11ServerTransportListener.java | 2 +- .../GenericHttp2ServerTransportListener.java | 21 ++++++------------- 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/dubbo-rpc/dubbo-rpc-triple/pom.xml b/dubbo-rpc/dubbo-rpc-triple/pom.xml index 4dfcbaac20e..cde7d597af0 100644 --- a/dubbo-rpc/dubbo-rpc-triple/pom.xml +++ b/dubbo-rpc/dubbo-rpc-triple/pom.xml @@ -185,8 +185,10 @@ false - org/apache/dubbo/rpc/protocol/tri/rest/** - org/apache/dubbo/rpc/protocol/tri/test/** + org/apache/dubbo/rpc/protocol/tri/rest/test/* + org/apache/dubbo/rpc/protocol/tri/rest/service/* + org/apache/dubbo/rpc/protocol/tri/test/* + META-INF/dubbo/** @@ -196,6 +198,13 @@ org.codehaus.gmavenplus gmavenplus-plugin + + + + compileTests + + + diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/UnaryServerCallListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/UnaryServerCallListener.java index 8c32c30031c..7eb2704f6e2 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/UnaryServerCallListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/UnaryServerCallListener.java @@ -24,14 +24,14 @@ public class UnaryServerCallListener extends AbstractServerCallListener { - private boolean applyCustomizeException = false; + private final boolean applyCustomizeException; public UnaryServerCallListener( - RpcInvocation invocation, Invoker invoker, StreamObserver responseObserver) { + RpcInvocation invocation, + Invoker invoker, + StreamObserver responseObserver, + boolean applyCustomizeException) { super(invocation, invoker, responseObserver); - } - - public void setApplyCustomizeException(boolean applyCustomizeException) { this.applyCustomizeException = applyCustomizeException; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java index 9a7a4828c5d..ce9f861c921 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java @@ -34,6 +34,7 @@ import org.apache.dubbo.rpc.TriRpcStatus; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils; +import org.apache.dubbo.rpc.protocol.tri.ReflectionPackableMethod; import org.apache.dubbo.rpc.protocol.tri.RpcInvocationBuildContext; import org.apache.dubbo.rpc.protocol.tri.compressor.DeCompressor; import org.apache.dubbo.rpc.protocol.tri.compressor.Identity; @@ -138,6 +139,19 @@ protected GrpcStreamingDecoder getStreamingDecoder() { return (GrpcStreamingDecoder) super.getStreamingDecoder(); } + @Override + protected boolean applyCustomizeException() { + RpcInvocationBuildContext context = getContext(); + if (context.isHasStub()) { + return false; + } + MethodMetadata methodMetadata = context.getMethodMetadata(); + return ReflectionPackableMethod.needWrap( + context.getMethodDescriptor(), + methodMetadata.getActualRequestTypes(), + methodMetadata.getActualResponseType()); + } + private class LazyFindMethodListener implements HttpMessageListener { private final StreamingDecoder streamingDecoder; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java index 5d445dcb3c4..7df724d47c4 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java @@ -126,7 +126,7 @@ private static class AutoCompleteUnaryServerCallListener extends UnaryServerCall public AutoCompleteUnaryServerCallListener( RpcInvocation invocation, Invoker invoker, StreamObserver responseObserver) { - super(invocation, invoker, responseObserver); + super(invocation, invoker, responseObserver, false); } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java index 382df659de8..c876299872d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java @@ -29,7 +29,6 @@ import org.apache.dubbo.remoting.http12.message.DefaultListeningDecoder; import org.apache.dubbo.remoting.http12.message.DefaultStreamingDecoder; import org.apache.dubbo.remoting.http12.message.ListeningDecoder; -import org.apache.dubbo.remoting.http12.message.MethodMetadata; import org.apache.dubbo.remoting.http12.message.StreamingDecoder; import org.apache.dubbo.remoting.http12.message.codec.JsonCodec; import org.apache.dubbo.rpc.CancellationContext; @@ -39,7 +38,6 @@ import org.apache.dubbo.rpc.executor.ExecutorSupport; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.model.MethodDescriptor; -import org.apache.dubbo.rpc.protocol.tri.ReflectionPackableMethod; import org.apache.dubbo.rpc.protocol.tri.RpcInvocationBuildContext; import org.apache.dubbo.rpc.protocol.tri.TripleProtocol; import org.apache.dubbo.rpc.protocol.tri.h12.AbstractServerTransportListener; @@ -109,19 +107,8 @@ private ServerCallListener startListener( switch (methodDescriptor.getRpcType()) { case UNARY: onUnary(); - boolean applyCustomizeException = false; - if (!getContext().isHasStub()) { - MethodMetadata methodMetadata = getContext().getMethodMetadata(); - applyCustomizeException = ReflectionPackableMethod.needWrap( - methodDescriptor, - methodMetadata.getActualRequestTypes(), - methodMetadata.getActualResponseType()); - } onListenerStart(); - UnaryServerCallListener unaryServerCallListener = - startUnary(invocation, invoker, getServerChannelObserver()); - unaryServerCallListener.setApplyCustomizeException(applyCustomizeException); - return unaryServerCallListener; + return startUnary(invocation, invoker, getServerChannelObserver()); case SERVER_STREAM: onListenerStart(); return startServerStreaming(invocation, invoker, getServerChannelObserver()); @@ -147,7 +134,7 @@ protected void onListenerStart() { private UnaryServerCallListener startUnary( RpcInvocation invocation, Invoker invoker, Http2ServerChannelObserver responseObserver) { - return new UnaryServerCallListener(invocation, invoker, responseObserver); + return new UnaryServerCallListener(invocation, invoker, responseObserver, applyCustomizeException()); } private ServerStreamServerCallListener startServerStreaming( @@ -216,6 +203,10 @@ protected final Http2ServerChannelObserver getServerChannelObserver() { return serverChannelObserver; } + protected boolean applyCustomizeException() { + return false; + } + @Override public void close() throws Exception { getServerChannelObserver().close(); From b7dcb55b51e15bd2c3f7fda998b69143215917c2 Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Mon, 22 Jul 2024 21:04:00 +0800 Subject: [PATCH 10/12] Refine jaxrs ParamConverter support --- .../rest/support/jaxrs/JaxrsRestToolKit.java | 17 ++---- .../support/jaxrs/ParamConverterFactory.java | 59 ++++++++++--------- .../support/jaxrs/RestProtocolTest.groovy | 7 +++ .../compatible/JaxrsRestProtocolTest.java | 22 ------- .../rest/ParamConverterService.java | 31 ---------- .../rest/ParamConverterServiceImpl.java | 26 -------- .../jaxrs/service/JaxrsDemoService.java | 6 ++ .../jaxrs/service/JaxrsDemoServiceImpl.java | 5 ++ .../ParamConverterProviderImpl.java | 20 ++++--- .../javax.ws.rs.ext.ParamConverterProvider | 2 +- 10 files changed, 67 insertions(+), 128 deletions(-) delete mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java delete mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java rename dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/{compatible/rest => service}/ParamConverterProviderImpl.java (73%) 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 8c4292d238c..56c3365e535 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 @@ -27,12 +27,9 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.ParamConverter; -import java.util.Optional; - final class JaxrsRestToolKit extends AbstractRestToolKit { private final BeanArgumentBinder binder; - private final ParamConverterFactory paramConverterFactory; public JaxrsRestToolKit(FrameworkModel frameworkModel) { @@ -41,13 +38,13 @@ public JaxrsRestToolKit(FrameworkModel frameworkModel) { paramConverterFactory = new ParamConverterFactory(); } - @SuppressWarnings({"unchecked", "rawtypes"}) @Override public int getDialect() { return RestConstants.DIALECT_JAXRS; } @Override + @SuppressWarnings({"unchecked", "rawtypes"}) public Object convert(Object value, ParameterMeta parameter) { if (MultivaluedMap.class.isAssignableFrom(parameter.getType())) { if (value instanceof MultivaluedMap) { @@ -56,16 +53,10 @@ public Object convert(Object value, ParameterMeta parameter) { return typeConverter.convert(value, MultivaluedHashMap.class); } - Optional optional = paramConverterFactory.getParamConverter( + ParamConverter converter = paramConverterFactory.getParamConverter( parameter.getType(), parameter.getGenericType(), parameter.getRealAnnotations()); - if (optional.isPresent()) { - ParamConverter paramConverter = optional.get(); - Object result = value.getClass() == String.class - ? paramConverter.fromString((String) value) - : paramConverter.toString(value); - if (result != null) { - return result; - } + if (converter != null) { + return value instanceof String ? converter.fromString((String) value) : converter.toString(value); } return super.convert(value, parameter); diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/ParamConverterFactory.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/ParamConverterFactory.java index bef0a63b909..15cf8dc36c6 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/ParamConverterFactory.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/ParamConverterFactory.java @@ -16,10 +16,9 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs; +import org.apache.dubbo.common.lang.Nullable; 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.common.utils.Pair; import javax.ws.rs.ext.ParamConverter; import javax.ws.rs.ext.ParamConverterProvider; @@ -27,6 +26,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -34,40 +34,45 @@ import java.util.ServiceLoader; import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_ERROR_LOAD_EXTENSION; +import static org.apache.dubbo.common.logger.LoggerFactory.getErrorTypeAwareLogger; -@SuppressWarnings({"rawtypes"}) -public class ParamConverterFactory { +final class ParamConverterFactory { - private static final ErrorTypeAwareLogger logger = - LoggerFactory.getErrorTypeAwareLogger(ParamConverterFactory.class); - private final Map, Type>, Annotation[]>, Optional> cache = - CollectionUtils.newConcurrentHashMap(); + private static final ErrorTypeAwareLogger logger = getErrorTypeAwareLogger(ParamConverterFactory.class); + private final Map, Optional>> cache = CollectionUtils.newConcurrentHashMap(); private final List providers = new ArrayList<>(); ParamConverterFactory() { - ServiceLoader serviceLoader = ServiceLoader.load(ParamConverterProvider.class); - Iterator iterator = serviceLoader.iterator(); - while (iterator.hasNext()) { + Iterator it = + ServiceLoader.load(ParamConverterProvider.class).iterator(); + while (it.hasNext()) { try { - ParamConverterProvider paramConverterProvider = iterator.next(); - providers.add(paramConverterProvider); - } catch (Throwable e) { - logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", "Spi Fail to load ParamConverterProvider"); + providers.add(it.next()); + } catch (Throwable t) { + logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", "Failed to load ParamConverterProvider", t); } } } - public Optional getParamConverter( - Class rawType, Type genericType, Annotation[] annotations) { - Pair, Type>, Annotation[]> pair = Pair.of(Pair.of(rawType, genericType), annotations); - return cache.computeIfAbsent(pair, k -> { - for (ParamConverterProvider provider : providers) { - ParamConverter converter = provider.getConverter(rawType, genericType, annotations); - if (converter != null) { - return Optional.of(converter); - } - } - return Optional.empty(); - }); + @Nullable + @SuppressWarnings("unchecked") + public ParamConverter getParamConverter(Class type, Type genericType, Annotation[] annotations) { + if (providers.isEmpty()) { + return null; + } + List key = new ArrayList<>(annotations.length + 2); + key.add(type); + key.add(genericType); + Collections.addAll(key, annotations); + return (ParamConverter) cache.computeIfAbsent(key, k -> { + for (ParamConverterProvider provider : providers) { + ParamConverter converter = provider.getConverter(type, genericType, annotations); + if (converter != null) { + return Optional.of(converter); + } + } + return Optional.empty(); + }) + .orElse(null); } } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/RestProtocolTest.groovy b/dubbo-plugin/dubbo-rest-jaxrs/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/RestProtocolTest.groovy index 64c6e0e7cdf..75e34de2680 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/RestProtocolTest.groovy +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/RestProtocolTest.groovy @@ -68,4 +68,11 @@ class RestProtocolTest extends BaseServiceTest { '/beanTest/2?name=sam' | ['first': 'sam', 'last': 'lee'] | '{"form":{"contentType":"application/x-www-form-urlencoded","firstName":"sam","lastName":"lee"},"id":2,"name":"sam"}' } + def "param converter test"() { + expect: + runner.get(path) == output + where: + path | output + '/convertTest?user=3,sam' | '{"id":3,"name":"sam"}' + } } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/JaxrsRestProtocolTest.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/JaxrsRestProtocolTest.java index f46c31310ad..fa4b681843c 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/JaxrsRestProtocolTest.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/JaxrsRestProtocolTest.java @@ -39,8 +39,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.AnotherUserRestServiceImpl; import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.HttpMethodService; import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.HttpMethodServiceImpl; -import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.ParamConverterService; -import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.ParamConverterServiceImpl; import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.RestDemoForTestException; import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.RestDemoService; import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.RestDemoServiceImpl; @@ -564,24 +562,4 @@ private URL registerProvider(URL url, Object impl, Class interfaceClass) { repository.registerProvider(providerModel); return url.setServiceModel(providerModel); } - - @Test - void testParamConverter() { - ParamConverterService service = new ParamConverterServiceImpl(); - URL exportUrl = URL.valueOf( - "tri://127.0.0.1:" + availablePort + "/rest?interface=" + ParamConverterService.class.getName()); - - URL nettyUrl = this.registerProvider(exportUrl, service, ParamConverterService.class); - - tProtocol.export(proxy.getInvoker(service, ParamConverterService.class, nettyUrl)); - - ParamConverterService paramConverterService = - this.proxy.getProxy(protocol.refer(ParamConverterService.class, nettyUrl)); - - User user = paramConverterService.convert(User.getInstance()); - User u = new User(); - u.setAge(20); - u.setId(1L); - Assertions.assertEquals(u, user); - } } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java deleted file mode 100644 index fa1c20c12de..00000000000 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest; - -import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.User; - -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.QueryParam; - -@Path("/ParamConverterService") -public interface ParamConverterService { - - @GET() - @Path("/convert") - User convert(@QueryParam("user") User user); -} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java deleted file mode 100644 index 2b0bedfd213..00000000000 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest; - -import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.User; - -public class ParamConverterServiceImpl implements ParamConverterService { - @Override - public User convert(User user) { - return user; - } -} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoService.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoService.java index 7af104fb406..d23ac72f4cd 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoService.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoService.java @@ -17,8 +17,10 @@ package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service; import javax.ws.rs.BeanParam; +import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; import org.jboss.resteasy.annotations.Form; @@ -31,4 +33,8 @@ public interface JaxrsDemoService { @POST @Path("/beanTest/{id}") User getTest(@BeanParam User user); + + @GET + @Path("/convertTest") + User convertTest(@QueryParam("user") User user); } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoServiceImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoServiceImpl.java index a54240fd750..0cab619b503 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoServiceImpl.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoServiceImpl.java @@ -27,4 +27,9 @@ public UserForm formTest(UserForm userForm) { public User getTest(User user) { return user; } + + @Override + public User convertTest(User user) { + return user; + } } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterProviderImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/ParamConverterProviderImpl.java similarity index 73% rename from dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterProviderImpl.java rename to dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/ParamConverterProviderImpl.java index 24d0a472cb2..84b03445e17 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterProviderImpl.java +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/ParamConverterProviderImpl.java @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest; +package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service; -import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.User; +import org.apache.dubbo.common.utils.StringUtils; import javax.ws.rs.ext.ParamConverter; import javax.ws.rs.ext.ParamConverterProvider; @@ -27,25 +27,29 @@ public class ParamConverterProviderImpl implements ParamConverterProvider { @Override - public ParamConverter getConverter(Class rawType, Type type, Annotation[] annotations) { + @SuppressWarnings("unchecked") + public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { if (rawType.isAssignableFrom(User.class)) { return (ParamConverter) new UserParamConverter(); } return null; } - static class UserParamConverter implements ParamConverter { + static final class UserParamConverter implements ParamConverter { @Override - public User fromString(String param) { + public User fromString(String value) { + String[] arr = StringUtils.tokenize(value, ','); User user = new User(); - user.setId(1L); - user.setAge(20); + if (arr.length > 1) { + user.setId(Long.valueOf(arr[0])); + user.setName(arr[1]); + } return user; } @Override public String toString(User user) { - return "User{" + "id=" + user.getId() + ", name='" + user.getName() + '\'' + ", age=" + user.getAge() + '}'; + return "User{" + "id=" + user.getId() + ", name='" + user.getName() + "\"}"; } } } diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/META-INF/services/javax.ws.rs.ext.ParamConverterProvider b/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/META-INF/services/javax.ws.rs.ext.ParamConverterProvider index b794deb1582..ab4379a54a2 100644 --- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/META-INF/services/javax.ws.rs.ext.ParamConverterProvider +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/META-INF/services/javax.ws.rs.ext.ParamConverterProvider @@ -1 +1 @@ -org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.ParamConverterProviderImpl +org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service.ParamConverterProviderImpl From a0f408236e9ce17b6334eda2fc3fac20144d2c5b Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Tue, 23 Jul 2024 14:19:40 +0800 Subject: [PATCH 11/12] Fix missing bracket --- .../protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java index bcc8a0ff37d..78e528c5ffb 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java @@ -147,6 +147,7 @@ protected boolean applyCustomizeException() { context.getMethodDescriptor(), methodMetadata.getActualRequestTypes(), methodMetadata.getActualResponseType()); + } @Override protected void onSettingMethodDescriptor(MethodDescriptor methodDescriptor) { From 412ee46b8553db63889bbd2b0f551765d457436c Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Tue, 23 Jul 2024 14:31:17 +0800 Subject: [PATCH 12/12] Update GrpcHttp2ServerTransportListener --- .../protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java index 78e528c5ffb..cbc25010653 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java @@ -27,6 +27,7 @@ import org.apache.dubbo.remoting.http12.h2.Http2Header; import org.apache.dubbo.remoting.http12.h2.Http2ServerChannelObserver; import org.apache.dubbo.remoting.http12.h2.Http2TransportListener; +import org.apache.dubbo.remoting.http12.message.MethodMetadata; import org.apache.dubbo.remoting.http12.message.StreamingDecoder; import org.apache.dubbo.rpc.RpcInvocation; import org.apache.dubbo.rpc.TriRpcStatus;