From 2dde7ac09eee12618725d5279f971b2a44ab599b Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Thu, 1 Feb 2024 14:00:16 +0800 Subject: [PATCH] feat(triple): Dubbo triple&rest protocol convergense (#13607) --- .artifacts | 4 + .../common/extension/ExtensionAccessor.java | 22 +- .../apache/dubbo/common/io/StreamUtils.java | 52 + .../apache/dubbo/common/json/JsonUtil.java | 4 + .../dubbo/common/json/impl/FastJson2Impl.java | 10 + .../dubbo/common/json/impl/FastJsonImpl.java | 11 + .../dubbo/common/json/impl/GsonImpl.java | 12 + .../dubbo/common/json/impl/JacksonImpl.java | 11 + .../apache/dubbo/common/utils/ArrayUtils.java | 4 + .../org/apache/dubbo/common/utils/Assert.java | 6 + .../apache/dubbo/common/utils/ClassUtils.java | 4 + .../dubbo/common/utils/CollectionUtils.java | 87 +- .../apache/dubbo/common/utils/DateUtils.java | 259 ++ .../apache/dubbo/common/utils/JsonUtils.java | 9 + .../apache/dubbo/common/utils/LRU2Cache.java | 17 +- .../apache/dubbo/common/utils/LRUCache.java | 17 +- .../org/apache/dubbo/common/utils/Pair.java | 138 + .../dubbo/common/utils/StringUtils.java | 102 +- .../org/apache/dubbo/config/RestConfig.java | 118 + .../org/apache/dubbo/config/TripleConfig.java | 13 + .../deploy/DefaultApplicationDeployer.java | 1 + dubbo-distribution/dubbo-all-shaded/pom.xml | 2329 +++++++---------- dubbo-distribution/dubbo-all/pom.xml | 56 +- dubbo-distribution/dubbo-bom/pom.xml | 15 + dubbo-plugin/dubbo-rest-jaxrs/pom.xml | 102 + .../jaxrs/AbstractJaxrsArgumentResolver.java | 32 + .../tri/rest/support/jaxrs/Annotations.java | 65 + .../support/jaxrs/BeanArgumentBinder.java | 280 ++ .../jaxrs/BeanParamArgumentResolver.java | 44 + .../support/jaxrs/BodyArgumentResolver.java | 56 + .../jaxrs/CookieParamArgumentResolver.java | 74 + .../jaxrs/FallbackArgumentResolver.java | 89 + .../support/jaxrs/FormArgumentResolver.java | 43 + .../jaxrs/FormParamArgumentResolver.java | 52 + .../jaxrs/HeaderParamArgumentResolver.java | 48 + .../tri/rest/support/jaxrs/Helper.java | 85 + .../jaxrs/JaxrsHttpRequestAdaptee.java | 163 ++ .../jaxrs/JaxrsHttpResponseAdaptee.java | 101 + .../jaxrs/JaxrsMiscArgumentResolver.java | 83 + .../jaxrs/JaxrsRequestMappingResolver.java | 93 + .../jaxrs/JaxrsResponseRestFilter.java | 42 + .../rest/support/jaxrs/JaxrsRestToolKit.java | 44 + .../jaxrs/MatrixParamArgumentResolver.java | 53 + .../MultivaluedMapCreationConverter.java | 31 + .../support/jaxrs/MultivaluedMapWrapper.java | 29 + .../jaxrs/PathParamArgumentResolver.java | 69 + .../jaxrs/QueryParamArgumentResolver.java | 49 + .../filter/ContainerRequestContextImpl.java | 214 ++ .../filter/ContainerRequestFilterAdapter.java | 57 + .../filter/ContainerResponseContextImpl.java | 228 ++ .../ContainerResponseFilterAdapter.java | 56 + .../jaxrs/filter/ExceptionMapperAdapter.java | 76 + .../jaxrs/filter/InterceptorContextImpl.java | 84 + .../jaxrs/filter/ReadInterceptorAdapter.java | 52 + .../filter/ReaderInterceptorContextImpl.java | 88 + .../filter/WriterInterceptorAdapter.java | 54 + .../filter/WriterInterceptorContextImpl.java | 88 + .../org.apache.dubbo.common.convert.Converter | 1 + ...rotocol.tri.rest.argument.ArgumentResolver | 11 + ...rpc.protocol.tri.rest.filter.RestExtension | 1 + ...tocol.tri.rest.filter.RestExtensionAdapter | 5 + ...ol.tri.rest.mapping.RequestMappingResolver | 1 + .../support/jaxrs/compatible/DemoService.java | 156 ++ .../jaxrs/compatible/DemoServiceImpl.java | 187 ++ .../compatible/JaxrsRestProtocolTest.java | 563 ++++ .../compatible/ResteasyExceptionMapper.java | 27 + .../rest/support/jaxrs/compatible/User.java | 88 + .../filter/TestContainerRequestFilter.java | 34 + .../jaxrs/compatible/filter/TraceFilter.java | 44 + .../filter/TraceRequestAndResponseFilter.java | 45 + .../intercept/DynamicTraceInterceptor.java | 49 + .../rest/AnotherUserRestService.java | 65 + .../rest/AnotherUserRestServiceImpl.java | 58 + .../compatible/rest/HttpMethodService.java | 67 + .../rest/HttpMethodServiceImpl.java | 55 + .../compatible/rest/RegistrationResult.java | 62 + .../rest/RestDemoForTestException.java | 44 + .../compatible/rest/RestDemoService.java | 65 + .../compatible/rest/RestDemoServiceImpl.java | 80 + .../src/test/resources/log4j2-test.xml | 1 + dubbo-plugin/dubbo-rest-servlet/pom.xml | 46 + .../support/servlet/DummyFilterConfig.java | 67 + .../support/servlet/DummyServletContext.java | 318 +++ .../rest/support/servlet/FilterAdapter.java | 109 + .../tri/rest/support/servlet/Helper.java | 145 + .../support/servlet/HttpSessionFactory.java | 27 + .../servlet/ServletArgumentResolver.java | 95 + .../ServletHttpMessageAdapterFactory.java | 66 + .../servlet/ServletHttpRequestAdaptee.java | 491 ++++ .../servlet/ServletHttpResponseAdaptee.java | 221 ++ ...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 | 115 + .../AbstractSpringArgumentResolver.java | 32 + .../tri/rest/support/spring/Annotations.java | 62 + .../support/spring/BeanArgumentBinder.java | 158 ++ .../support/spring/ConfigurationWrapper.java | 43 + .../spring/CookieValueArgumentResolver.java | 74 + .../spring/FallbackArgumentResolver.java | 66 + .../spring/HandlerInterceptorAdapter.java | 110 + .../tri/rest/support/spring/Helper.java | 44 + .../MatrixVariableArgumentResolver.java | 125 + .../ModelAttributeArgumentResolver.java | 59 + .../spring/PathVariableArgumentResolver.java | 66 + .../RequestAttributeArgumentResolver.java | 43 + .../spring/RequestBodyArgumentResolver.java | 79 + .../spring/RequestHeaderArgumentResolver.java | 48 + .../spring/RequestParamArgumentResolver.java | 49 + .../spring/RequestPartArgumentResolver.java | 68 + .../spring/SpringMiscArgumentResolver.java | 68 + .../SpringMvcRequestMappingResolver.java | 107 + .../spring/SpringResponseRestFilter.java | 195 ++ .../support/spring/SpringRestToolKit.java | 127 + ...rotocol.tri.rest.argument.ArgumentResolver | 12 + ...rpc.protocol.tri.rest.filter.RestExtension | 1 + ...tocol.tri.rest.filter.RestExtensionAdapter | 1 + ...ol.tri.rest.mapping.RequestMappingResolver | 1 + .../compatible/SpringDemoServiceImpl.java | 107 + .../compatible/SpringMvcRestProtocolTest.java | 312 +++ .../compatible/SpringRestDemoService.java | 72 + .../rest/support/spring/compatible/User.java | 88 + .../src/test/resources/log4j2-test.xml | 30 + dubbo-plugin/pom.xml | 7 +- dubbo-remoting/dubbo-remoting-http12/pom.xml | 14 + .../AbstractServerHttpChannelObserver.java | 46 +- .../dubbo/remoting/http12/ErrorResponse.java | 9 + .../dubbo/remoting/http12/HttpChannel.java | 2 + .../dubbo/remoting/http12/HttpCookie.java | 123 + .../dubbo/remoting/http12/HttpMethods.java | 64 + .../dubbo/remoting/http12/HttpRequest.java | 161 ++ .../dubbo/remoting/http12/HttpResponse.java | 98 + .../dubbo/remoting/http12/HttpResult.java | 79 + .../dubbo/remoting/http12/HttpStatus.java | 10 + .../dubbo/remoting/http12/HttpUtils.java | 255 ++ .../http12/exception/DecodeException.java | 4 + .../exception/HttpResultPayloadException.java | 51 + .../http12/exception/HttpStatusException.java | 5 + .../http12/h2/Http2ChannelDelegate.java | 5 + .../DefaultHttpMessageAdapterFactory.java | 37 + .../http12/message/DefaultHttpRequest.java | 679 +++++ .../http12/message/DefaultHttpResponse.java | 368 +++ .../http12/message/DefaultHttpResult.java | 147 ++ .../message/HttpMessageAdapterFactory.java | 35 + .../http12/message/HttpMessageDecoder.java | 21 +- .../http12/message/HttpMessageEncoder.java | 21 +- .../remoting/http12/message/MediaType.java | 52 +- .../http12/message/codec/BinaryCodec.java | 61 + .../message/codec/BinaryCodecFactory.java | 41 + .../http12/message/codec/CodecUtils.java | 132 +- .../http12/message/codec/HtmlCodec.java | 61 + .../message/codec/HtmlCodecFactory.java | 46 + .../http12/message/codec/JsonCodec.java | 105 +- .../message/codec/JsonCodecFactory.java | 15 +- .../http12/message/codec/JsonPbCodec.java | 50 +- .../message/codec/JsonPbCodecFactory.java | 11 +- .../message/codec/MultipartDecoder.java | 11 +- .../codec/MultipartDecoderFactory.java | 2 +- .../http12/message/codec/PlainTextCodec.java | 52 +- .../message/codec/PlainTextCodecFactory.java | 13 +- .../message/codec/UrlEncodeFormCodec.java | 22 +- .../codec/UrlEncodeFormCodecFactory.java | 11 +- .../http12/message/codec/XmlCodec.java | 50 +- .../http12/message/codec/XmlCodecFactory.java | 9 +- .../http12/message/codec/YamlCodec.java | 117 + .../message/codec/YamlCodecFactory.java | 46 + .../http12/netty4/h1/NettyHttp1Channel.java | 5 + .../h1/NettyHttp1ConnectionHandler.java | 79 +- .../netty4/h2/NettyH2StreamChannel.java | 5 + .../h2/NettyHttp2ProtocolSelectorHandler.java | 16 +- ...g.http12.message.HttpMessageAdapterFactory | 1 + ...g.http12.message.HttpMessageDecoderFactory | 9 +- ...g.http12.message.HttpMessageEncoderFactory | 7 +- .../http12/message/codec/CodeUtilsTest.java | 25 +- .../http12/message/codec/CodecTest.java | 15 +- .../java/org/apache/dubbo/rpc/Constants.java | 11 +- .../rpc/protocol/tri/DescriptorUtils.java | 155 ++ .../rpc/protocol/tri/HttpContextFilter.java | 82 + .../rpc/protocol/tri/RequestMetadata.java | 5 +- .../dubbo/rpc/protocol/tri/RestProtocol.java | 14 +- .../tri/RpcInvocationBuildContext.java | 51 + .../rpc/protocol/tri/TripleConstant.java | 14 +- .../rpc/protocol/tri/TripleHeaderEnum.java | 96 +- .../rpc/protocol/tri/TripleHttp2Protocol.java | 12 - .../dubbo/rpc/protocol/tri/TripleInvoker.java | 3 +- .../rpc/protocol/tri/TripleProtocol.java | 50 +- .../h12/AbstractServerTransportListener.java | 409 +-- .../protocol/tri/h12/CompressibleEncoder.java | 9 +- .../tri/h12/HttpMessageDecoderWrapper.java | 65 + .../tri/h12/HttpMessageEncoderWrapper.java | 82 + .../tri/h12/HttpRequestHandlerMapping.java | 121 + .../tri/h12/TripleProtocolDetector.java | 34 +- .../tri/h12/grpc/GrpcCompositeCodec.java | 19 +- .../h12/grpc/GrpcCompositeCodecFactory.java | 6 +- .../GrpcHttp2ServerTransportListener.java | 151 +- ...pcHttp2ServerTransportListenerFactory.java | 2 +- .../h12/grpc/GrpcRequestHandlerMapping.java | 54 + .../h12/grpc/ProtobufHttpMessageCodec.java | 5 +- .../tri/h12/grpc/WrapperHttpMessageCodec.java | 11 +- .../DefaultHttp11ServerTransportListener.java | 86 +- .../GenericHttp2ServerTransportListener.java | 167 +- .../h12/http2/Http2ServerStreamObserver.java | 2 +- .../dubbo/rpc/protocol/tri/rest/Messages.java | 64 + .../tri/rest/PathParserException.java | 30 + .../tri/rest/RestBadRequestException.java | 49 + .../rpc/protocol/tri/rest/RestConstants.java | 61 + .../rpc/protocol/tri/rest/RestException.java | 89 + .../tri/rest/RestHttpMessageCodec.java | 128 + .../tri/rest/RestInitializeException.java | 30 + .../tri/rest/RestParameterException.java | 49 + ...bstractAnnotationBaseArgumentResolver.java | 42 + .../argument/AbstractArgumentResolver.java | 34 + .../AnnotationBaseArgumentResolver.java | 40 + .../tri/rest/argument/ArgumentConverter.java | 27 + .../tri/rest/argument/ArgumentResolver.java | 31 + .../argument/CompositeArgumentConverter.java | 85 + .../argument/CompositeArgumentResolver.java | 77 + .../rest/argument/GeneralTypeConverter.java | 1269 +++++++++ .../rest/argument/MiscArgumentResolver.java | 73 + .../NamedValueArgumentResolverSupport.java | 95 + .../tri/rest/argument/TypeConverter.java | 30 + .../tri/rest/filter/AbstractRestFilter.java | 55 + .../tri/rest/filter/DefaultFilterChain.java | 136 + .../tri/rest/filter/RestExtension.java | 29 + .../tri/rest/filter/RestExtensionAdapter.java | 28 + .../filter/RestExtensionExecutionFilter.java | 198 ++ .../protocol/tri/rest/filter/RestFilter.java | 44 + .../tri/rest/filter/RestFilterAdapter.java | 67 + .../rest/filter/RestHeaderFilterAdapter.java | 42 + .../tri/rest/mapping/ContentNegotiator.java | 154 ++ .../DefaultRequestMappingRegistry.java | 236 ++ .../protocol/tri/rest/mapping/RadixTree.java | 316 +++ .../tri/rest/mapping/RequestMapping.java | 385 +++ .../rest/mapping/RequestMappingRegistry.java | 35 + .../rest/mapping/RequestMappingResolver.java | 37 + .../mapping/RestRequestHandlerMapping.java | 110 + .../tri/rest/mapping/condition/Condition.java | 28 + .../mapping/condition/ConditionWrapper.java | 70 + .../mapping/condition/ConsumesCondition.java | 136 + .../mapping/condition/HeadersCondition.java | 95 + .../condition/MediaTypeExpression.java | 251 ++ .../mapping/condition/MethodsCondition.java | 95 + .../condition/NameValueExpression.java | 115 + .../mapping/condition/ParamsCondition.java | 78 + .../rest/mapping/condition/PathCondition.java | 159 ++ .../mapping/condition/PathExpression.java | 126 + .../rest/mapping/condition/PathParser.java | 462 ++++ .../rest/mapping/condition/PathSegment.java | 242 ++ .../mapping/condition/ProducesCondition.java | 218 ++ .../condition/ServiceVersionCondition.java | 87 + .../tri/rest/mapping/meta/AnnotationEnum.java | 43 + .../tri/rest/mapping/meta/AnnotationMeta.java | 233 ++ .../rest/mapping/meta/AnnotationSupport.java | 216 ++ .../tri/rest/mapping/meta/HandlerMeta.java | 72 + .../tri/rest/mapping/meta/MethodMeta.java | 122 + .../mapping/meta/MethodParameterMeta.java | 106 + .../tri/rest/mapping/meta/NamedValueMeta.java | 109 + .../tri/rest/mapping/meta/ParameterMeta.java | 116 + .../tri/rest/mapping/meta/ResponseMeta.java | 63 + .../tri/rest/mapping/meta/ServiceMeta.java | 104 + .../tri/rest/util/DefaultRestToolKit.java | 87 + .../protocol/tri/rest/util/MethodWalker.java | 108 + .../rpc/protocol/tri/rest/util/PathUtils.java | 260 ++ .../protocol/tri/rest/util/RequestUtils.java | 179 ++ .../protocol/tri/rest/util/RestToolKit.java | 44 + .../rpc/protocol/tri/rest/util/RestUtils.java | 67 + .../rpc/protocol/tri/rest/util/TypeUtils.java | 218 ++ .../tri/route/DefaultRequestRouter.java | 62 + .../protocol/tri/route/RequestHandler.java | 128 + .../tri/route/RequestHandlerMapping.java | 31 + .../rpc/protocol/tri/route/RequestRouter.java | 27 + .../tri/service/TriBuiltinService.java | 4 +- .../rpc/protocol/tri/stream/StreamUtils.java | 249 +- .../AbstractH2TransportListener.java | 2 +- .../internal/org.apache.dubbo.rpc.Filter | 2 + .../internal/org.apache.dubbo.rpc.Protocol | 3 +- ...rotocol.tri.rest.argument.ArgumentResolver | 1 + ...c.protocol.tri.route.RequestHandlerMapping | 3 + .../tri/rest/GeneralTypeConverterTest.java | 50 + .../tri/rest/mapping/PathUtilsTest.java | 43 + .../tri/rest/mapping/RadixTreeTest.java | 56 + .../mapping/condition/PathParserTest.java | 49 + .../protocol/tri/stream/StreamUtilsTest.java | 6 +- .../DubboConfigurationProperties.java | 15 + dubbo-test/dubbo-dependencies-all/pom.xml | 15 + 286 files changed, 23403 insertions(+), 2567 deletions(-) create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/utils/DateUtils.java create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/utils/Pair.java create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/pom.xml create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/AbstractJaxrsArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/Annotations.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanArgumentBinder.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanParamArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/CookieParamArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FormArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FormParamArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/HeaderParamArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/Helper.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdaptee.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdaptee.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsMiscArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsResponseRestFilter.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRestToolKit.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MatrixParamArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MultivaluedMapCreationConverter.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MultivaluedMapWrapper.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/PathParamArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/QueryParamArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestContextImpl.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestFilterAdapter.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerResponseContextImpl.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerResponseFilterAdapter.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ExceptionMapperAdapter.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ReadInterceptorAdapter.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ReaderInterceptorContextImpl.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/WriterInterceptorAdapter.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/WriterInterceptorContextImpl.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.convert.Converter create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/DemoService.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/DemoServiceImpl.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/JaxrsRestProtocolTest.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/ResteasyExceptionMapper.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/User.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TestContainerRequestFilter.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TraceFilter.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TraceRequestAndResponseFilter.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/intercept/DynamicTraceInterceptor.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/AnotherUserRestService.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/AnotherUserRestServiceImpl.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/HttpMethodService.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/HttpMethodServiceImpl.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RegistrationResult.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoForTestException.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoService.java create mode 100644 dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoServiceImpl.java rename dubbo-plugin/{dubbo-plugin-loom => dubbo-rest-jaxrs}/src/test/resources/log4j2-test.xml (93%) create mode 100644 dubbo-plugin/dubbo-rest-servlet/pom.xml create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.java create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdaptee.java create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdaptee.java create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter create mode 100644 dubbo-plugin/dubbo-rest-servlet/src/test/resources/log4j2-test.xml create mode 100644 dubbo-plugin/dubbo-rest-spring/pom.xml create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BeanArgumentBinder.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/ConfigurationWrapper.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/CookieValueArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/FallbackArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/HandlerInterceptorAdapter.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Helper.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/ModelAttributeArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/PathVariableArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestAttributeArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestHeaderArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestParamArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestPartArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMiscArgumentResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringResponseRestFilter.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringRestToolKit.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter create mode 100644 dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver create mode 100644 dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/User.java create mode 100644 dubbo-plugin/dubbo-rest-spring/src/test/resources/log4j2-test.xml create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpCookie.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpResponse.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpResult.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpUtils.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/HttpResultPayloadException.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpMessageAdapterFactory.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpResponse.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpResult.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageAdapterFactory.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/BinaryCodec.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/BinaryCodecFactory.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/HtmlCodec.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/HtmlCodecFactory.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodecFactory.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/HttpContextFilter.java rename dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/IllegalPathException.java => dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RestProtocol.java (73%) create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RpcInvocationBuildContext.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageEncoderWrapper.java create 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/h12/grpc/GrpcRequestHandlerMapping.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/Messages.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/PathParserException.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestBadRequestException.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestException.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestHttpMessageCodec.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestInitializeException.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestParameterException.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AbstractAnnotationBaseArgumentResolver.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AbstractArgumentResolver.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AnnotationBaseArgumentResolver.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/ArgumentConverter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/ArgumentResolver.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentConverter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentResolver.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/GeneralTypeConverter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/MiscArgumentResolver.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/TypeConverter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/AbstractRestFilter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/DefaultFilterChain.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtension.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtensionAdapter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtensionExecutionFilter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestFilter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestFilterAdapter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/ContentNegotiator.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/Condition.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConditionWrapper.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConsumesCondition.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/HeadersCondition.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MediaTypeExpression.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/NameValueExpression.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ParamsCondition.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathCondition.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParser.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ProducesCondition.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationEnum.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationMeta.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationSupport.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/HandlerMeta.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodMeta.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodParameterMeta.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ResponseMeta.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ServiceMeta.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/DefaultRestToolKit.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtils.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestToolKit.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestUtils.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestHandler.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestHandlerMapping.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestRouter.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/GeneralTypeConverterTest.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/PathUtilsTest.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParserTest.java diff --git a/.artifacts b/.artifacts index 7711cde1ea6..3f3d84b604a 100644 --- a/.artifacts +++ b/.artifacts @@ -120,3 +120,7 @@ dubbo-tracing dubbo-xds dubbo-plugin-proxy-bytebuddy dubbo-plugin-loom +dubbo-rest-jaxrs +dubbo-rest-servlet +dubbo-rest-spring + diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionAccessor.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionAccessor.java index 14c4882b60a..c9ed110845b 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionAccessor.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionAccessor.java @@ -16,6 +16,9 @@ */ package org.apache.dubbo.common.extension; +import java.util.Collections; +import java.util.List; + /** * Uniform accessor for extension */ @@ -24,7 +27,7 @@ public interface ExtensionAccessor { ExtensionDirector getExtensionDirector(); default ExtensionLoader getExtensionLoader(Class type) { - return this.getExtensionDirector().getExtensionLoader(type); + return getExtensionDirector().getExtensionLoader(type); } default T getExtension(Class type, String name) { @@ -41,4 +44,21 @@ default T getDefaultExtension(Class type) { ExtensionLoader extensionLoader = getExtensionLoader(type); return extensionLoader != null ? extensionLoader.getDefaultExtension() : null; } + + default List getActivateExtensions(Class type) { + ExtensionLoader extensionLoader = getExtensionLoader(type); + return extensionLoader != null ? extensionLoader.getActivateExtensions() : Collections.emptyList(); + } + + default T getFirstActivateExtension(Class type) { + ExtensionLoader extensionLoader = getExtensionLoader(type); + if (extensionLoader == null) { + throw new IllegalArgumentException("ExtensionLoader for [" + type + "] is not found"); + } + List extensions = extensionLoader.getActivateExtensions(); + if (extensions.isEmpty()) { + throw new IllegalArgumentException("No activate extensions for [" + type + "] found"); + } + return extensions.get(0); + } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/io/StreamUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/io/StreamUtils.java index b0badff64ec..9e10135d87b 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/io/StreamUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/io/StreamUtils.java @@ -16,8 +16,13 @@ */ package org.apache.dubbo.common.io; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; /** * Stream utils. @@ -229,4 +234,51 @@ public static void skipUnusedStream(InputStream is) throws IOException { is.skip(is.available()); } } + + public static void copy(InputStream in, OutputStream out) throws IOException { + if (in.getClass() == ByteArrayInputStream.class) { + copy((ByteArrayInputStream) in, out); + return; + } + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + } + + public static void copy(ByteArrayInputStream in, OutputStream out) throws IOException { + int len = in.available(); + byte[] buffer = new byte[len]; + in.read(buffer, 0, len); + out.write(buffer, 0, len); + } + + public static byte[] readBytes(InputStream in) throws IOException { + if (in.getClass() == ByteArrayInputStream.class) { + return readBytes((ByteArrayInputStream) in); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + return out.toByteArray(); + } + + public static byte[] readBytes(ByteArrayInputStream in) throws IOException { + int len = in.available(); + byte[] bytes = new byte[len]; + in.read(bytes, 0, len); + return bytes; + } + + public static String toString(InputStream in) throws IOException { + return new String(readBytes(in), StandardCharsets.UTF_8); + } + + public static String toString(InputStream in, Charset charset) throws IOException { + return new String(readBytes(in), charset); + } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/json/JsonUtil.java b/dubbo-common/src/main/java/org/apache/dubbo/common/json/JsonUtil.java index cc94b77ebab..b03092ab7ad 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/json/JsonUtil.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/json/JsonUtil.java @@ -40,6 +40,10 @@ public interface JsonUtil { Map getObject(Map obj, String key); + Object convertObject(Object obj, Type type); + + Object convertObject(Object obj, Class clazz); + Double getNumberAsDouble(Map obj, String key); Integer getNumberAsInteger(Map obj, String key); diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/FastJson2Impl.java b/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/FastJson2Impl.java index f7741b229c8..09c795bb6ef 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/FastJson2Impl.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/FastJson2Impl.java @@ -36,4 +36,14 @@ public List toJavaList(String json, Class clazz) { public String toJson(Object obj) { return com.alibaba.fastjson2.JSON.toJSONString(obj, JSONWriter.Feature.WriteEnumsUsingName); } + + @Override + public Object convertObject(Object obj, Type type) { + return com.alibaba.fastjson2.util.TypeUtils.cast(obj, type); + } + + @Override + public Object convertObject(Object obj, Class clazz) { + return com.alibaba.fastjson2.util.TypeUtils.cast(obj, clazz); + } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/FastJsonImpl.java b/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/FastJsonImpl.java index 756dc9457c7..a44291cc6e5 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/FastJsonImpl.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/FastJsonImpl.java @@ -19,6 +19,7 @@ import java.lang.reflect.Type; import java.util.List; +import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializerFeature; public class FastJsonImpl extends AbstractJsonUtilImpl { @@ -37,4 +38,14 @@ public List toJavaList(String json, Class clazz) { public String toJson(Object obj) { return com.alibaba.fastjson.JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect); } + + @Override + public Object convertObject(Object obj, Type type) { + return com.alibaba.fastjson.util.TypeUtils.cast(obj, type, ParserConfig.getGlobalInstance()); + } + + @Override + public Object convertObject(Object obj, Class clazz) { + return com.alibaba.fastjson.util.TypeUtils.cast(obj, clazz, ParserConfig.getGlobalInstance()); + } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/GsonImpl.java b/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/GsonImpl.java index 2a0cad78fae..39586cb19e1 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/GsonImpl.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/GsonImpl.java @@ -42,6 +42,18 @@ public String toJson(Object obj) { return getGson().toJson(obj); } + @Override + public Object convertObject(Object obj, Type type) { + Gson gson = getGson(); + return gson.fromJson(gson.toJsonTree(obj), type); + } + + @Override + public Object convertObject(Object obj, Class clazz) { + Gson gson = getGson(); + return gson.fromJson(gson.toJsonTree(obj), clazz); + } + private Gson getGson() { if (gsonCache == null || !(gsonCache instanceof Gson)) { synchronized (this) { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/JacksonImpl.java b/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/JacksonImpl.java index 3e21416dcf0..686f176ea72 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/JacksonImpl.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/JacksonImpl.java @@ -59,6 +59,17 @@ public String toJson(Object obj) { } } + @Override + public Object convertObject(Object obj, Type type) { + JsonMapper mapper = getJackson(); + return mapper.convertValue(obj, mapper.constructType(type)); + } + + @Override + public Object convertObject(Object obj, Class clazz) { + return getJackson().convertValue(obj, clazz); + } + private JsonMapper getJackson() { if (jacksonCache == null || !(jacksonCache instanceof JsonMapper)) { synchronized (this) { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java index 92efcc26196..ddc8095e805 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java @@ -76,4 +76,8 @@ public static int indexOf(String[] array, String valueToFind, int startIndex) { public static T[] of(T... values) { return values; } + + public static T first(T[] data) { + return isEmpty(data) ? null : data[0]; + } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Assert.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Assert.java index f7a88f033b8..8a8cd1d8e16 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Assert.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Assert.java @@ -28,6 +28,12 @@ public static void notNull(Object obj, String message) { } } + public static void notNull(Object obj, String format, Object... args) { + if (obj == null) { + throw new IllegalArgumentException(String.format(format, args)); + } + } + public static void notEmptyString(String str, String message) { if (StringUtils.isEmpty(str)) { throw new IllegalArgumentException(message); diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassUtils.java index 88eca7a2e4b..6c0656dae60 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassUtils.java @@ -327,6 +327,10 @@ public static boolean isPrimitive(Class type) { return type != null && (type.isPrimitive() || isSimpleType(type)); } + public static boolean isPrimitiveWrapper(Class type) { + return PRIMITIVE_WRAPPER_TYPE_MAP.containsKey(type); + } + /** * The specified type is simple type or not * diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CollectionUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CollectionUtils.java index df6388c936d..3514011e60f 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CollectionUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/CollectionUtils.java @@ -24,11 +24,14 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import java.util.stream.Collectors; import static java.util.Collections.emptySet; @@ -404,13 +407,19 @@ public static T first(Collection values) { return null; } if (values instanceof List) { - List list = (List) values; - return list.get(0); + return ((List) values).get(0); } else { return values.iterator().next(); } } + public static T first(List values) { + if (isEmpty(values)) { + return null; + } + return values.get(0); + } + public static Set toTreeSet(Set set) { if (isEmpty(set)) { return set; @@ -420,4 +429,78 @@ public static Set toTreeSet(Set set) { } return set; } + + public static Set newHashSet(int expectedSize) { + return new HashSet<>(capacity(expectedSize)); + } + + public static Set newLinkedHashSet(int expectedSize) { + return new LinkedHashSet<>(capacity(expectedSize)); + } + + public static Map newHashMap(int expectedSize) { + return new HashMap<>(capacity(expectedSize)); + } + + public static Map newLinkedHashMap(int expectedSize) { + return new LinkedHashMap<>(capacity(expectedSize)); + } + + public static Map newConcurrentHashMap(int expectedSize) { + if (JRE.JAVA_8.isCurrentVersion()) { + return new SafeConcurrentHashMap<>(capacity(expectedSize)); + } + return new ConcurrentHashMap<>(capacity(expectedSize)); + } + + public static Map newConcurrentHashMap() { + if (JRE.JAVA_8.isCurrentVersion()) { + return new SafeConcurrentHashMap<>(); + } + return new ConcurrentHashMap<>(); + } + + public static int capacity(int expectedSize) { + if (expectedSize < 3) { + if (expectedSize < 0) { + throw new IllegalArgumentException("expectedSize cannot be negative but was: " + expectedSize); + } + return expectedSize + 1; + } + if (expectedSize < 1 << (Integer.SIZE - 2)) { + return (int) (expectedSize / 0.75F + 1.0F); + } + return Integer.MAX_VALUE; + } + + public static class SafeConcurrentHashMap extends ConcurrentHashMap { + private static final long serialVersionUID = 1L; + + public SafeConcurrentHashMap() {} + + public SafeConcurrentHashMap(int initialCapacity) { + super(initialCapacity); + } + + public SafeConcurrentHashMap(Map m) { + super(m); + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + V value = get(key); + if (value != null) { + return value; + } + value = mappingFunction.apply(key); + if (value == null) { + return null; + } + V exists = putIfAbsent(key, value); + if (exists != null) { + return exists; + } + return value; + } + } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/DateUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/DateUtils.java new file mode 100644 index 00000000000..764ed3ce46e --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/DateUtils.java @@ -0,0 +1,259 @@ +/* + * 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.common.utils; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.CopyOnWriteArrayList; + +public final class DateUtils { + + public static final ZoneId GMT = ZoneId.of("GMT"); + public static final ZoneId UTC = ZoneId.of("UTC"); + + public static final String DATE = "yyyy-MM-dd"; + public static final String DATE_MIN = "yyyy-MM-dd HH:mm"; + public static final String DATE_TIME = "yyyy-MM-dd HH:mm:ss"; + public static final String JDK_TIME = "EEE MMM dd HH:mm:ss zzz yyyy"; + public static final String ASC_TIME = "EEE MMM d HH:mm:ss yyyy"; + public static final String RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz"; + + public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern(DATE); + public static final DateTimeFormatter DATE_MIN_FORMAT = DateTimeFormatter.ofPattern(DATE_MIN); + public static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern(DATE_TIME); + public static final DateTimeFormatter JDK_TIME_FORMAT = DateTimeFormatter.ofPattern(JDK_TIME, Locale.US); + public static final DateTimeFormatter ASC_TIME_FORMAT = DateTimeFormatter.ofPattern(ASC_TIME, Locale.US); + public static final DateTimeFormatter RFC1036_FORMAT = DateTimeFormatter.ofPattern(RFC1036, Locale.US); + + private static final Map CACHE = new LRUCache<>(64); + private static final List CUSTOM_FORMATTERS = new CopyOnWriteArrayList<>(); + + private DateUtils() {} + + public static void registerFormatter(String pattern) { + CUSTOM_FORMATTERS.add(DateTimeFormatter.ofPattern(pattern)); + } + + public static void registerFormatter(DateTimeFormatter formatter) { + CUSTOM_FORMATTERS.add(formatter); + } + + public static Date parse(String str, String pattern) { + if (DATE_TIME.equals(pattern)) { + return parse(str, DATE_TIME_FORMAT); + } + DateTimeFormatter formatter = getFormatter(pattern); + return parse(str, formatter); + } + + public static Date parse(String str, DateTimeFormatter formatter) { + return toDate(formatter.parse(str)); + } + + public static String format(Date date) { + return format(date, DATE_TIME_FORMAT); + } + + public static String format(Date date, String pattern) { + if (DATE_TIME.equals(pattern)) { + return format(date, DATE_TIME_FORMAT); + } + DateTimeFormatter formatter = getFormatter(pattern); + return format(date, formatter); + } + + public static String format(Date date, DateTimeFormatter formatter) { + return formatter.format(ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())); + } + + public static String format(Date date, DateTimeFormatter formatter, ZoneId zone) { + return formatter.format(ZonedDateTime.ofInstant(date.toInstant(), zone)); + } + + public static String formatGMT(Date date, DateTimeFormatter formatter) { + return formatter.format(ZonedDateTime.ofInstant(date.toInstant(), GMT)); + } + + public static String formatUTC(Date date, DateTimeFormatter formatter) { + return formatter.format(ZonedDateTime.ofInstant(date.toInstant(), UTC)); + } + + public static String formatHeader(Date date) { + return DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.ofInstant(date.toInstant(), GMT)); + } + + private static DateTimeFormatter getFormatter(String pattern) { + return CACHE.computeIfAbsent(pattern, DateTimeFormatter::ofPattern); + } + + public static Date parse(Object value) { + if (value == null) { + return null; + } + if (value instanceof Date) { + return (Date) value; + } + if (value instanceof Calendar) { + return ((Calendar) value).getTime(); + } + if (value.getClass() == Instant.class) { + return Date.from((Instant) value); + } + if (value instanceof TemporalAccessor) { + return Date.from(Instant.from((TemporalAccessor) value)); + } + if (value instanceof Number) { + return new Date(((Number) value).longValue()); + } + if (value instanceof CharSequence) { + return parse(value.toString()); + } + throw new IllegalArgumentException("Can not cast to Date, value : '" + value + "'"); + } + + public static Date parse(String value) { + if (value == null) { + return null; + } + String str = value.trim(); + int len = str.length(); + if (len == 0) { + return null; + } + + boolean isIso = true; + boolean isNumeric = true; + boolean hasDate = false; + boolean hasTime = false; + for (int i = 0; i < len; i++) { + char c = str.charAt(i); + switch (c) { + case ' ': + isIso = false; + break; + case '-': + hasDate = true; + break; + case 'T': + case ':': + hasTime = true; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + continue; + default: + } + if (isNumeric) { + isNumeric = false; + } + } + DateTimeFormatter formatter = null; + if (isIso) { + if (hasDate) { + formatter = hasTime ? DateTimeFormatter.ISO_DATE_TIME : DateTimeFormatter.ISO_DATE; + } else if (hasTime) { + formatter = DateTimeFormatter.ISO_TIME; + } + } + if (isNumeric) { + long num = Long.parseLong(str); + if (num > 21000101 || num < 19700101) { + return new Date(num); + } + formatter = DateTimeFormatter.BASIC_ISO_DATE; + } + switch (len) { + case 10: + formatter = DATE_FORMAT; + break; + case 16: + formatter = DATE_MIN_FORMAT; + break; + case 19: + formatter = DATE_TIME_FORMAT; + break; + case 23: + case 24: + formatter = ASC_TIME_FORMAT; + break; + case 27: + formatter = RFC1036_FORMAT; + break; + case 28: + formatter = JDK_TIME_FORMAT; + break; + case 29: + formatter = DateTimeFormatter.RFC_1123_DATE_TIME; + break; + default: + } + + if (formatter != null) { + try { + return toDate(formatter.parse(str)); + } catch (Exception ignored) { + } + } + for (DateTimeFormatter dtf : CUSTOM_FORMATTERS) { + try { + return parse(str, dtf); + } catch (Exception ignored) { + } + } + throw new IllegalArgumentException("Can not cast to Date, value : '" + value + "'"); + } + + public static Date toDate(TemporalAccessor temporal) { + if (temporal instanceof Instant) { + return Date.from((Instant) temporal); + } + long timestamp; + if (temporal.isSupported(ChronoField.EPOCH_DAY)) { + timestamp = temporal.getLong(ChronoField.EPOCH_DAY) * 86400000; + } else { + timestamp = LocalDate.now().toEpochDay() * 86400000; + } + if (temporal.isSupported(ChronoField.MILLI_OF_DAY)) { + timestamp += temporal.getLong(ChronoField.MILLI_OF_DAY); + } + if (temporal.isSupported(ChronoField.OFFSET_SECONDS)) { + timestamp -= temporal.getLong(ChronoField.OFFSET_SECONDS) * 1000; + } else { + timestamp -= TimeZone.getDefault().getRawOffset(); + } + return new Date(timestamp); + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/JsonUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/JsonUtils.java index 2b47ee3ed5b..767ea5a42e9 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/JsonUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/JsonUtils.java @@ -53,6 +53,7 @@ protected static JsonUtil getJson() { case "jackson": instance = new JacksonImpl(); break; + default: } if (instance != null && instance.isSupport()) { jsonUtil = instance; @@ -123,6 +124,14 @@ public static List getListOfStrings(Map obj, String key) { return getJson().getObject(obj, key); } + public static Object convertObject(Object obj, Type targetType) { + return getJson().convertObject(obj, targetType); + } + + public static Object convertObject(Object obj, Class targetType) { + return getJson().convertObject(obj, targetType); + } + public static Double getNumberAsDouble(Map obj, String key) { return getJson().getNumberAsDouble(obj, key); } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRU2Cache.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRU2Cache.java index 2a725e93343..b17674ffc0e 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRU2Cache.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRU2Cache.java @@ -19,13 +19,14 @@ import java.util.LinkedHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; /** * LRU-2 - *

+ *

* When the data accessed for the first time, add it to history list. If the size of history list reaches max capacity, eliminate the earliest data (first in first out). * When the data already exists in the history list, and be accessed for the second time, then it will be put into cache. - * + *

* TODO, consider replacing with ConcurrentHashMap to improve performance under concurrency */ public class LRU2Cache extends LinkedHashMap { @@ -33,8 +34,8 @@ public class LRU2Cache extends LinkedHashMap { private static final long serialVersionUID = -5167631809472116969L; private static final float DEFAULT_LOAD_FACTOR = 0.75f; - private static final int DEFAULT_MAX_CAPACITY = 1000; + private final Lock lock = new ReentrantLock(); private volatile int maxCapacity; @@ -94,6 +95,16 @@ public V put(K key, V value) { } } + @Override + public V computeIfAbsent(K key, Function fn) { + V value = get(key); + if (value == null) { + value = fn.apply(key); + put(key, value); + } + return value; + } + @Override public V remove(Object key) { lock.lock(); diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRUCache.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRUCache.java index 76d6c19cb71..079fb0766a5 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRUCache.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/LRUCache.java @@ -19,6 +19,7 @@ import java.util.LinkedHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; /** * A 'least recently used' cache based on LinkedHashMap. @@ -31,8 +32,8 @@ public class LRUCache extends LinkedHashMap { private static final long serialVersionUID = -5167631809472116969L; private static final float DEFAULT_LOAD_FACTOR = 0.75f; - private static final int DEFAULT_MAX_CAPACITY = 1000; + private final Lock lock = new ReentrantLock(); private volatile int maxCapacity; @@ -110,6 +111,20 @@ public void clear() { } } + @Override + public V computeIfAbsent(K key, Function fn) { + V value = get(key); + if (value == null) { + lock.lock(); + try { + return super.computeIfAbsent(key, fn); + } finally { + lock.unlock(); + } + } + return value; + } + public void lock() { lock.lock(); } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Pair.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Pair.java new file mode 100644 index 00000000000..d719ff30613 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Pair.java @@ -0,0 +1,138 @@ +/* + * 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.common.utils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public final class Pair implements Map.Entry, Comparable>, Serializable { + + private static final long serialVersionUID = 1L; + + @SuppressWarnings("rawtypes") + private static final Pair NULL = new Pair<>(null, null); + + public final L left; + public final R right; + + public static Pair of(L left, R right) { + return left == null && right == null ? nullPair() : new Pair<>(left, right); + } + + @SuppressWarnings("unchecked") + public static Pair nullPair() { + return NULL; + } + + @SafeVarargs + public static Map toMap(Pair... pairs) { + if (pairs == null) { + return Collections.emptyMap(); + } + return toMap(Arrays.asList(pairs)); + } + + public static Map toMap(Collection> pairs) { + if (pairs == null) { + return Collections.emptyMap(); + } + Map map = CollectionUtils.newLinkedHashMap(pairs.size()); + for (Pair pair : pairs) { + map.put(pair.getLeft(), pair.getRight()); + } + return map; + } + + public static List> toPairs(Map map) { + if (map == null) { + return Collections.emptyList(); + } + List> pairs = new ArrayList<>(map.size()); + for (Map.Entry entry : map.entrySet()) { + pairs.add(of(entry.getKey(), entry.getValue())); + } + return pairs; + } + + public Pair(L left, R right) { + this.left = left; + this.right = right; + } + + public L getLeft() { + return left; + } + + public R getRight() { + return right; + } + + public boolean isNull() { + return this == NULL || left == null && right == null; + } + + @Override + public L getKey() { + return left; + } + + @Override + public R getValue() { + return right; + } + + @Override + public R setValue(R value) { + throw new UnsupportedOperationException(); + } + + @Override + @SuppressWarnings("unchecked") + public int compareTo(Pair other) { + return left.equals(other.left) + ? ((Comparable) right).compareTo(other.right) + : ((Comparable) left).compareTo(other.left); + } + + @Override + public int hashCode() { + return Objects.hashCode(left) ^ Objects.hashCode(right); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof Map.Entry) { + Map.Entry that = (Map.Entry) other; + return Objects.equals(left, that.getKey()) && Objects.equals(right, that.getValue()); + } + return false; + } + + @Override + public String toString() { + return "(" + left + ", " + right + ')'; + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java index 0a8cba4bcd4..2b86d0e256f 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java @@ -1256,10 +1256,6 @@ public static String toCommaDelimitedString(String one, String... others) { /** * Test str whether starts with the prefix ignore case. - * - * @param str - * @param prefix - * @return */ public static boolean startsWithIgnoreCase(String str, String prefix) { if (str == null || prefix == null || str.length() < prefix.length()) { @@ -1268,4 +1264,102 @@ public static boolean startsWithIgnoreCase(String str, String prefix) { // return str.substring(0, prefix.length()).equalsIgnoreCase(prefix); return str.regionMatches(true, 0, prefix, 0, prefix.length()); } + + public static String defaultIf(String str, String defaultStr) { + return isEmpty(str) ? defaultStr : str; + } + + /** + * Returns a substring from 'str' between 'start' and 'end', or to the end if 'end' is -1 + */ + public static String substring(String str, int start, int end) { + if (str == null) { + return null; + } + return end == INDEX_NOT_FOUND ? str.substring(start) : str.substring(start, end); + } + + /** + * Extracts a substring from the given string that precedes the first occurrence of the specified character separator. + * If the character is not found, the entire string is returned. + */ + public static String substringBefore(String str, int separator) { + if (isEmpty(str)) { + return str; + } + int index = str.indexOf(separator); + return index == INDEX_NOT_FOUND ? str : str.substring(0, index); + } + + /** + * Extracts a substring from the given string that precedes the first occurrence of the specified string separator. + * If the separator is not found or is null, the entire string is returned. + */ + public static String substringBefore(String str, String separator) { + if (isEmpty(str) || separator == null) { + return str; + } + if (separator.isEmpty()) { + return EMPTY_STRING; + } + int index = str.indexOf(separator); + return index == INDEX_NOT_FOUND ? str : str.substring(0, index); + } + + /** + * Tokenize the given String into a String array. + * Trims tokens and omits empty tokens. + */ + public static String[] tokenize(String str, char... separators) { + if (isEmpty(str)) { + return EMPTY_STRING_ARRAY; + } + return tokenizeToList(str, separators).toArray(EMPTY_STRING_ARRAY); + } + + public static List tokenizeToList(String str, char... separators) { + if (isEmpty(str)) { + return Collections.emptyList(); + } + if (separators == null || separators.length == 0) { + separators = new char[] {','}; + } + List tokens = new ArrayList<>(); + int start = -1, end = 0; + int i = 0; + out: + for (int len = str.length(), sLen = separators.length; i < len; i++) { + char c = str.charAt(i); + for (int j = 0; j < sLen; j++) { + if (c == separators[j]) { + if (start > -1) { + tokens.add(str.substring(start, end + 1)); + start = -1; + } + continue out; + } + } + switch (c) { + case ' ': + case '\t': + case '\n': + case '\r': + break; + default: + if (start == -1) { + start = i; + } + end = i; + break; + } + } + if (start > -1) { + String part = str.substring(start, end + 1); + if (tokens.isEmpty()) { + return Collections.singletonList(part); + } + tokens.add(part); + } + return tokens; + } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java new file mode 100644 index 00000000000..6a3674e119a --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.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.config; + +import java.io.Serializable; + +/** + * Configuration for triple rest protocol. + */ +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; + + /** + * 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; + + /** + * Whether path matching uses suffix pattern matching (".*"). + * If enabled, a method mapped to "/users" also matches to "/users.*". + *

This also enables suffix content negotiation, with the media-type + * inferred from the URL suffix, e.g., ".json" for "application/json". + *

The default value is {@code true}. + */ + private Boolean suffixPatternMatch; + + /** + * The parameter name that can be used to specify the response format. + *

The default value is 'format'. + */ + private String formatParameterName; + + 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() { + return trailingSlashMatch; + } + + public void setTrailingSlashMatch(Boolean trailingSlashMatch) { + this.trailingSlashMatch = trailingSlashMatch; + } + + public Boolean getCaseSensitiveMatch() { + return caseSensitiveMatch; + } + + public void setCaseSensitiveMatch(Boolean caseSensitiveMatch) { + this.caseSensitiveMatch = caseSensitiveMatch; + } + + public Boolean getSuffixPatternMatch() { + return suffixPatternMatch; + } + + public void setSuffixPatternMatch(Boolean suffixPatternMatch) { + this.suffixPatternMatch = suffixPatternMatch; + } + + public String getFormatParameterName() { + return formatParameterName; + } + + public void setFormatParameterName(String formatParameterName) { + this.formatParameterName = formatParameterName; + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/TripleConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/TripleConfig.java index a10d909fbc4..1b7469ee21a 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/TripleConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/TripleConfig.java @@ -55,6 +55,11 @@ public class TripleConfig implements Serializable { */ private String maxHeaderListSize; + /** + * Whether to pass through standard HTTP headers, default is false. + */ + private Boolean passThroughStandardHttpHeaders; + public String getHeaderTableSize() { return headerTableSize; } @@ -102,4 +107,12 @@ public String getMaxHeaderListSize() { public void setMaxHeaderListSize(String maxHeaderListSize) { this.maxHeaderListSize = maxHeaderListSize; } + + public Boolean getPassThroughStandardHttpHeaders() { + return passThroughStandardHttpHeaders; + } + + public void setPassThroughStandardHttpHeaders(Boolean passThroughStandardHttpHeaders) { + this.passThroughStandardHttpHeaders = passThroughStandardHttpHeaders; + } } diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java index e1e09381533..e4b37d2ad4e 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java @@ -1218,6 +1218,7 @@ public void checkState(ModuleModel moduleModel, DeployState moduleState) { // cannot change to pending from other state // setPending(); break; + default: } } } diff --git a/dubbo-distribution/dubbo-all-shaded/pom.xml b/dubbo-distribution/dubbo-all-shaded/pom.xml index 9221a5c1380..dd826febb3d 100644 --- a/dubbo-distribution/dubbo-all-shaded/pom.xml +++ b/dubbo-distribution/dubbo-all-shaded/pom.xml @@ -1,1359 +1,1022 @@ - + 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. + --> - 4.0.0 - - org.apache.dubbo - dubbo-parent - ${revision} - ../../pom.xml - - dubbo-all-shaded - jar - dubbo-all-shaded - The all in one project of dubbo with dependencies prone to conflict shaded - - false - - - - - org.apache.dubbo - dubbo-cluster - ${project.version} - compile - true - + 4.0.0 + + org.apache.dubbo + dubbo-parent + ${revision} + ../../pom.xml + + dubbo-all-shaded + jar + dubbo-all-shaded + The all in one project of dubbo with dependencies prone to conflict shaded + + false + + + + + org.apache.dubbo + dubbo-cluster + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-common - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-common + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-compatible - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-compatible + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-config-api - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-config-spring - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-config-api + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-config-spring + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-configcenter-zookeeper - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-configcenter-apollo - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-configcenter-nacos - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-configcenter-zookeeper + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-configcenter-apollo + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-configcenter-nacos + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-filter-cache - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-filter-validation - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-filter-cache + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-filter-validation + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-rest-jaxrs + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-rest-servlet + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-rest-spring + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-kubernetes - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-kubernetes + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-metadata-api - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-metadata-report-zookeeper - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-metadata-report-nacos - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-metadata-report-redis - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-metadata-definition-protobuf - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-metadata-api + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-metadata-report-zookeeper + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-metadata-report-nacos + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-metadata-report-redis + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-metadata-definition-protobuf + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-metrics-api - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-metrics-default - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-metrics-registry - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-metrics-prometheus - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-metrics-metadata - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-metrics-config-center - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-metrics-api + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-metrics-default + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-metrics-registry + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-metrics-prometheus + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-metrics-metadata + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-metrics-config-center + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-auth - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-qos-api - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-qos - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-security - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-reactive - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-auth + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-qos-api + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-qos + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-security + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-reactive + ${project.version} + compile + true + - - org.apache.dubbo - dubbo-spring-security - ${project.version} - compile - true - + + org.apache.dubbo + dubbo-spring-security + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-registry-api - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-registry-multicast - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-registry-multiple - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-registry-nacos - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-registry-zookeeper - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-registry-api + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-registry-multicast + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-registry-multiple + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-registry-nacos + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-registry-zookeeper + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-remoting-api - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-remoting-http - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-remoting-netty - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-remoting-netty4 - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-remoting-zookeeper - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-remoting-zookeeper-curator5 - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-remoting-api + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-remoting-http + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-remoting-netty + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-remoting-netty4 + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-remoting-zookeeper + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-remoting-zookeeper-curator5 + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-rpc-api - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-rpc-dubbo - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-rpc-injvm - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-rpc-rest - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-rpc-api + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-rpc-dubbo + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-rpc-injvm + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-rpc-rest + ${project.version} + compile + true + - - org.apache.dubbo - dubbo-rpc-triple - ${project.version} - compile - true - + + org.apache.dubbo + dubbo-rpc-triple + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-serialization-api - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-serialization-hessian2 - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-serialization-fastjson2 - ${project.version} - compile - true - - - org.apache.dubbo - dubbo-serialization-jdk - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-serialization-api + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-serialization-hessian2 + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-serialization-fastjson2 + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-serialization-jdk + ${project.version} + compile + true + - - - org.apache.dubbo - dubbo-xds - ${project.version} - compile - true - + + + org.apache.dubbo + dubbo-xds + ${project.version} + compile + true + - - - io.netty - netty-all - compile - true - - - org.springframework - spring-context - - - com.alibaba.spring - spring-context-support - - - org.javassist - javassist - - - org.yaml - snakeyaml - - - com.alibaba - hessian-lite - - - com.alibaba.fastjson2 - fastjson2 - - - - - - org.apache.maven.plugins - maven-shade-plugin - - - package - - shade - - - true - true - false - - - org.apache.dubbo:dubbo-auth - org.apache.dubbo:dubbo-cluster - org.apache.dubbo:dubbo-common - org.apache.dubbo:dubbo-compatible - org.apache.dubbo:dubbo-config-api - org.apache.dubbo:dubbo-config-spring - org.apache.dubbo:dubbo-configcenter-apollo - org.apache.dubbo:dubbo-configcenter-nacos - org.apache.dubbo:dubbo-configcenter-zookeeper - org.apache.dubbo:dubbo-filter-cache - org.apache.dubbo:dubbo-filter-validation - org.apache.dubbo:dubbo-metadata-api - org.apache.dubbo:dubbo-metadata-definition-protobuf - org.apache.dubbo:dubbo-metadata-report-nacos - org.apache.dubbo:dubbo-metadata-report-redis - org.apache.dubbo:dubbo-metadata-report-zookeeper - org.apache.dubbo:dubbo-metrics-api - org.apache.dubbo:dubbo-metrics-default - org.apache.dubbo:dubbo-metrics-registry - org.apache.dubbo:dubbo-metrics-metadata - org.apache.dubbo:dubbo-metrics-config-center - org.apache.dubbo:dubbo-metrics-prometheus - org.apache.dubbo:dubbo-qos - org.apache.dubbo:dubbo-qos-api - org.apache.dubbo:dubbo-security - org.apache.dubbo:dubbo-reactive - org.apache.dubbo:dubbo-spring-security - org.apache.dubbo:dubbo-registry-api - org.apache.dubbo:dubbo-registry-multicast - org.apache.dubbo:dubbo-registry-multiple - org.apache.dubbo:dubbo-registry-nacos - org.apache.dubbo:dubbo-registry-zookeeper - org.apache.dubbo:dubbo-remoting-api - org.apache.dubbo:dubbo-remoting-http - org.apache.dubbo:dubbo-remoting-netty4 - org.apache.dubbo:dubbo-remoting-netty - org.apache.dubbo:dubbo-remoting-zookeeper - org.apache.dubbo:dubbo-remoting-zookeeper-curator5 - org.apache.dubbo:dubbo-rpc-api - org.apache.dubbo:dubbo-rpc-dubbo - org.apache.dubbo:dubbo-rpc-injvm - org.apache.dubbo:dubbo-rpc-rest - org.apache.dubbo:dubbo-rpc-triple - org.apache.dubbo:dubbo-serialization-api - org.apache.dubbo:dubbo-serialization-hessian2 - org.apache.dubbo:dubbo-serialization-fastjson2 - org.apache.dubbo:dubbo-serialization-jdk - org.apache.dubbo:dubbo-kubernetes - org.apache.dubbo:dubbo-xds - io.netty:* - - - - - - META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory - - - - - META-INF/dubbo/internal/com.alibaba.dubbo.container.page.PageHandler - - - - - META-INF/dubbo/internal/org.apache.dubbo.auth.spi.AccessKeyStorage - - - - - META-INF/dubbo/internal/org.apache.dubbo.auth.spi.Authenticator - - - - - META-INF/dubbo/internal/org.apache.dubbo.cache.CacheFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.compiler.Compiler - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.config.OrderedPropertiesProvider - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.context.ApplicationExt - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.context.ModuleExt - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.convert.Converter - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.convert.multiple.MultiValueConverter - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.deploy.ApplicationDeployListener - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.deploy.ModuleDeployListener - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionInjector - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionLoader - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.infra.InfraAdapter - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.lang.ShutdownHookCallback - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.logger.LoggerAdapter - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.serialize.MultipleSerialization - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.serialize.Serialization - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.status.StatusChecker - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.status.reporter.FrameworkStatusReporter - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.store.DataStore - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.threadpool.ThreadPool - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.threadpool.manager.ExecutorRepository - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.url.component.param.DynamicParamSource - - - - - META-INF/dubbo/internal/org.apache.dubbo.config.ConfigInitializer - - - - - META-INF/dubbo/internal/org.apache.dubbo.config.ConfigPostProcessor - - - - - META-INF/dubbo/internal/org.apache.dubbo.config.ServiceListener - - - - - META-INF/dubbo/internal/org.apache.dubbo.config.bootstrap.DubboBootstrapStartStopListener - - - - - META-INF/dubbo/internal/org.apache.dubbo.config.spring.context.DubboSpringInitCustomizer - - - - - META-INF/dubbo/internal/org.apache.dubbo.config.spring.extension.SpringExtensionInjector - - - - - META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataParamsFilter - - - - - META-INF/dubbo/internal/org.apache.dubbo.metadata.ServiceNameMapping - - - - - META-INF/dubbo/internal/org.apache.dubbo.metadata.annotation.processing.builder.TypeBuilder - - - - - META-INF/dubbo/internal/org.apache.dubbo.metadata.annotation.processing.rest.AnnotatedMethodParameterProcessor - - - - - META-INF/dubbo/internal/org.apache.dubbo.metadata.annotation.processing.rest.ServiceRestMetadataResolver - - - - - META-INF/dubbo/internal/org.apache.dubbo.metadata.definition.builder.TypeBuilder - - - - - META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.metadata.rest.AnnotatedMethodParameterProcessor - - - - - META-INF/dubbo/internal/org.apache.dubbo.metadata.rest.ServiceRestMetadataReader - - - - - META-INF/dubbo/internal/org.apache.dubbo.monitor.MonitorFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.qos.api.BaseCommand - - - - - META-INF/dubbo/internal/org.apache.dubbo.qos.probe.LivenessProbe - - - - - META-INF/dubbo/internal/org.apache.dubbo.qos.probe.ReadinessProbe - - - - - META-INF/dubbo/internal/org.apache.dubbo.qos.probe.StartupProbe - - - - - META-INF/dubbo/internal/org.apache.dubbo.qos.permission.PermissionChecker - - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.PenetrateAttachmentSelector - - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.AddressListener - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.ProviderFirstParams - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryServiceListener - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.client.RegistryClusterIdentifier - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceInstanceCustomizer - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.client.metadata.MetadataServiceURLBuilder - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.ssl.CertProvider - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.client.migration.MigrationAddressComparator - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.client.migration.PreMigratingConditionChecker - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.integration.RegistryProtocolListener - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.xds.XdsCertificateSigner - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.filter.RestResponseInterceptor - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.filter.RestRequestFilter - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.filter.RestResponseFilter - - - - - META-INF/dubbo/internal/org.apache.dubbo.metadata.rest.NoAnnotatedParameterRequestTagProcessor - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodec - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.consumer.HttpConnectionPreBuildIntercept - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.consumer.BaseConsumerParamParser - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.http.factory.RestClientFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.ChannelHandler - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.Codec - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.Codec2 - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.Dispatcher - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.Transporter - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.dubbo.ByteAccessor - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.api.pu.PortUnificationTransporter - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.api.connection.ConnectionManager - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.api.WireProtocol - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.exchange.Exchanger - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.http.HttpBinder - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.telnet.TelnetHandler - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.ExporterListener - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.InvokerListener - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.PenetrateAttachmentSelector - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.ProxyFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.ZoneDetector - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.Cluster - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.ConfiguratorFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.LoadBalance - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.Merger - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.ProviderURLMergeProcessor - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RouterFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RuleConverter - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.filter.ClusterFilter - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.filter.InvocationInterceptorBuilder - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.governance.GovernanceRuleRepository - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.interceptor.ClusterInterceptor - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListenerFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.ValuePattern - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcherFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ApplicationInitListener - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.model.BuiltinServiceDetector - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ScopeModelInitializer - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.injvm.ParamDeepCopyUtil - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.PathResolver - - - - - META-INF/dubbo/internal/org.apache.dubbo.validation.Validation - - - - - META-INF/dubbo/internal/org.apache.dubbo.registry.client.metadata.ServiceInstanceNotificationCustomizer - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.compressor.Compressor - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.compressor.DeCompressor - - - - - META-INF/dubbo/internal/org.apache.dubbo.metrics.service.MetricsService - - - - - META-INF/dubbo/internal/org.apache.dubbo.metrics.service.MetricsServiceExporter - - - - - META-INF/dubbo/internal/org.apache.dubbo.metrics.report.MetricsReporterFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.common.threadpool.event.ThreadPoolExhaustedListener - - - - - META-INF/dubbo/internal/org.apache.dubbo.remoting.api.pu.PortUnificationTransporter - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.mesh.util.TracingContextProvider - - - - - META-INF/dubbo/internal/org.apache.dubbo.metadata.rest.ServiceRestMetadataResolver - - - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.executor.IsolationExecutorSupportFactory - - - - - META-INF/dubbo/internal/org.apache.dubbo.metrics.collector.MetricsCollector - - + + + io.netty + netty-all + compile + true + + + org.springframework + spring-core + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + com.alibaba.spring + spring-context-support + + + org.javassist + javassist + + + org.yaml + snakeyaml + + + com.alibaba + hessian-lite + + + com.alibaba.fastjson2 + fastjson2 + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + true + true + false + + + org.apache.dubbo:dubbo-auth + org.apache.dubbo:dubbo-cluster + org.apache.dubbo:dubbo-common + org.apache.dubbo:dubbo-compatible + org.apache.dubbo:dubbo-config-api + org.apache.dubbo:dubbo-config-spring + org.apache.dubbo:dubbo-configcenter-apollo + org.apache.dubbo:dubbo-configcenter-nacos + org.apache.dubbo:dubbo-configcenter-zookeeper + org.apache.dubbo:dubbo-filter-cache + org.apache.dubbo:dubbo-filter-validation + org.apache.dubbo:dubbo-metadata-api + org.apache.dubbo:dubbo-metadata-definition-protobuf + org.apache.dubbo:dubbo-metadata-report-nacos + org.apache.dubbo:dubbo-metadata-report-redis + org.apache.dubbo:dubbo-metadata-report-zookeeper + org.apache.dubbo:dubbo-metrics-api + org.apache.dubbo:dubbo-metrics-default + org.apache.dubbo:dubbo-metrics-registry + org.apache.dubbo:dubbo-metrics-metadata + org.apache.dubbo:dubbo-metrics-config-center + org.apache.dubbo:dubbo-metrics-prometheus + org.apache.dubbo:dubbo-qos + org.apache.dubbo:dubbo-qos-api + org.apache.dubbo:dubbo-security + org.apache.dubbo:dubbo-reactive + org.apache.dubbo:dubbo-spring-security + org.apache.dubbo:dubbo-registry-api + org.apache.dubbo:dubbo-registry-multicast + org.apache.dubbo:dubbo-registry-multiple + org.apache.dubbo:dubbo-registry-nacos + org.apache.dubbo:dubbo-registry-zookeeper + org.apache.dubbo:dubbo-remoting-api + org.apache.dubbo:dubbo-remoting-http + org.apache.dubbo:dubbo-remoting-netty4 + org.apache.dubbo:dubbo-remoting-netty + org.apache.dubbo:dubbo-remoting-zookeeper + org.apache.dubbo:dubbo-remoting-zookeeper-curator5 + org.apache.dubbo:dubbo-rpc-api + org.apache.dubbo:dubbo-rpc-dubbo + org.apache.dubbo:dubbo-rpc-injvm + org.apache.dubbo:dubbo-rpc-rest + 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-serialization-api + org.apache.dubbo:dubbo-serialization-hessian2 + org.apache.dubbo:dubbo-serialization-fastjson2 + org.apache.dubbo:dubbo-serialization-jdk + org.apache.dubbo:dubbo-kubernetes + org.apache.dubbo:dubbo-xds + io.netty:* + + + + + META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory + + + META-INF/dubbo/internal/com.alibaba.dubbo.container.page.PageHandler + + + META-INF/dubbo/internal/org.apache.dubbo.auth.spi.AccessKeyStorage + + + META-INF/dubbo/internal/org.apache.dubbo.auth.spi.Authenticator + + + META-INF/dubbo/internal/org.apache.dubbo.cache.CacheFactory + + + META-INF/dubbo/internal/org.apache.dubbo.common.compiler.Compiler + + + META-INF/dubbo/internal/org.apache.dubbo.common.config.OrderedPropertiesProvider + + + META-INF/dubbo/internal/org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory + + + META-INF/dubbo/internal/org.apache.dubbo.common.context.ApplicationExt + + + META-INF/dubbo/internal/org.apache.dubbo.common.context.ModuleExt + + + META-INF/dubbo/internal/org.apache.dubbo.common.convert.Converter + + + META-INF/dubbo/internal/org.apache.dubbo.common.convert.multiple.MultiValueConverter + + + META-INF/dubbo/internal/org.apache.dubbo.common.deploy.ApplicationDeployListener + + + META-INF/dubbo/internal/org.apache.dubbo.common.deploy.ModuleDeployListener + + + META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory + + + META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionInjector + + + META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionLoader + + + META-INF/dubbo/internal/org.apache.dubbo.common.infra.InfraAdapter + + + META-INF/dubbo/internal/org.apache.dubbo.common.lang.ShutdownHookCallback + + + META-INF/dubbo/internal/org.apache.dubbo.common.logger.LoggerAdapter + + + META-INF/dubbo/internal/org.apache.dubbo.common.serialize.MultipleSerialization + + + META-INF/dubbo/internal/org.apache.dubbo.common.serialize.Serialization + + + META-INF/dubbo/internal/org.apache.dubbo.common.status.StatusChecker + + + META-INF/dubbo/internal/org.apache.dubbo.common.status.reporter.FrameworkStatusReporter + + + META-INF/dubbo/internal/org.apache.dubbo.common.store.DataStore + + + META-INF/dubbo/internal/org.apache.dubbo.common.threadpool.ThreadPool + + + META-INF/dubbo/internal/org.apache.dubbo.common.threadpool.manager.ExecutorRepository + + + META-INF/dubbo/internal/org.apache.dubbo.common.url.component.param.DynamicParamSource + + + META-INF/dubbo/internal/org.apache.dubbo.config.ConfigInitializer + + + META-INF/dubbo/internal/org.apache.dubbo.config.ConfigPostProcessor + + + META-INF/dubbo/internal/org.apache.dubbo.config.ServiceListener + + + META-INF/dubbo/internal/org.apache.dubbo.config.bootstrap.DubboBootstrapStartStopListener + + + META-INF/dubbo/internal/org.apache.dubbo.config.spring.context.DubboSpringInitCustomizer + + + META-INF/dubbo/internal/org.apache.dubbo.config.spring.extension.SpringExtensionInjector + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataParamsFilter + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.ServiceNameMapping + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.annotation.processing.builder.TypeBuilder + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.annotation.processing.rest.AnnotatedMethodParameterProcessor + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.annotation.processing.rest.ServiceRestMetadataResolver + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.definition.builder.TypeBuilder + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.rest.AnnotatedMethodParameterProcessor + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.rest.ServiceRestMetadataReader + + + META-INF/dubbo/internal/org.apache.dubbo.monitor.MonitorFactory + + + META-INF/dubbo/internal/org.apache.dubbo.qos.api.BaseCommand + + + META-INF/dubbo/internal/org.apache.dubbo.qos.probe.LivenessProbe + + + META-INF/dubbo/internal/org.apache.dubbo.qos.probe.ReadinessProbe + + + META-INF/dubbo/internal/org.apache.dubbo.qos.probe.StartupProbe + + + META-INF/dubbo/internal/org.apache.dubbo.qos.permission.PermissionChecker + - - - META-INF/dubbo/internal/org.apache.dubbo.spring.security.jackson.ObjectMapperCodecCustomer - - + + META-INF/dubbo/internal/org.apache.dubbo.rpc.PenetrateAttachmentSelector + - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.BaseProviderParamParser - - + + META-INF/dubbo/internal/org.apache.dubbo.registry.AddressListener + + + META-INF/dubbo/internal/org.apache.dubbo.registry.ProviderFirstParams + + + META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory + + + META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryServiceListener + + + META-INF/dubbo/internal/org.apache.dubbo.registry.client.RegistryClusterIdentifier + + + META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory + + + META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceInstanceCustomizer + + + META-INF/dubbo/internal/org.apache.dubbo.registry.client.metadata.MetadataServiceURLBuilder + + + META-INF/dubbo/internal/org.apache.dubbo.common.ssl.CertProvider + + + META-INF/dubbo/internal/org.apache.dubbo.registry.client.migration.MigrationAddressComparator + + + META-INF/dubbo/internal/org.apache.dubbo.registry.client.migration.PreMigratingConditionChecker + + + META-INF/dubbo/internal/org.apache.dubbo.registry.integration.RegistryProtocolListener + + + META-INF/dubbo/internal/org.apache.dubbo.registry.xds.XdsCertificateSigner + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.filter.RestResponseInterceptor + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.filter.RestRequestFilter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.filter.RestResponseFilter + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.rest.NoAnnotatedParameterRequestTagProcessor + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodec + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.consumer.HttpConnectionPreBuildIntercept + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.consumer.BaseConsumerParamParser + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.http.factory.RestClientFactory + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.ChannelHandler + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.Codec + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.Codec2 + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.Dispatcher + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.Transporter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.dubbo.ByteAccessor + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.api.pu.PortUnificationTransporter + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.api.connection.ConnectionManager + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.api.WireProtocol + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.exchange.Exchanger + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.http.HttpBinder + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.telnet.TelnetHandler + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.ExporterListener + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.HeaderFilter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.InvokerListener + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.PenetrateAttachmentSelector + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.ProxyFactory + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.ZoneDetector + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.Cluster + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.ConfiguratorFactory + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.LoadBalance + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.Merger + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.ProviderURLMergeProcessor + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RouterFactory + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RuleConverter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.filter.ClusterFilter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.filter.FilterChainBuilder + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.filter.InvocationInterceptorBuilder + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.governance.GovernanceRuleRepository + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.interceptor.ClusterInterceptor + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListenerFactory + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.ValuePattern + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcherFactory + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ApplicationInitListener + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.model.BuiltinServiceDetector + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ScopeModelInitializer + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.injvm.ParamDeepCopyUtil + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.PathResolver + + + META-INF/dubbo/internal/org.apache.dubbo.validation.Validation + + + META-INF/dubbo/internal/org.apache.dubbo.registry.client.metadata.ServiceInstanceNotificationCustomizer + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.compressor.Compressor + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.compressor.DeCompressor + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentConverter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory + + + META-INF/dubbo/internal/org.apache.dubbo.metrics.service.MetricsService + + + META-INF/dubbo/internal/org.apache.dubbo.metrics.service.MetricsServiceExporter + + + META-INF/dubbo/internal/org.apache.dubbo.metrics.report.MetricsReporterFactory + + + META-INF/dubbo/internal/org.apache.dubbo.common.threadpool.event.ThreadPoolExhaustedListener + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.api.pu.PortUnificationTransporter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.mesh.util.TracingContextProvider + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.rest.ServiceRestMetadataResolver + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.executor.IsolationExecutorSupportFactory + + + META-INF/dubbo/internal/org.apache.dubbo.metrics.collector.MetricsCollector + + + META-INF/dubbo/internal/org.apache.dubbo.spring.security.jackson.ObjectMapperCodecCustomer + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.BaseProviderParamParser + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.model.PackableMethodFactory + + + + + org.apache.dubbo:dubbo + + + com/** + org/** + + META-INF/dubbo/** + + + + io.netty:* + + META-INF/** + + + + + + io.netty + org.apache.dubbo.netty.shaded.io.netty + + + + + + + + - - - META-INF/dubbo/internal/org.apache.dubbo.rpc.model.PackableMethodFactory - - - - - - org.apache.dubbo:dubbo - - - com/** - org/** - - META-INF/dubbo/** - - - - io.netty:* - - META-INF/** - - - - - - io.netty - org.apache.dubbo.netty.shaded.io.netty - - - - - - + + + release + + + + maven-javadoc-plugin + ${maven_javadoc_version} + + + attach-javadoc + + jar + + + none + + + + + true + + org.apache.dubbo:dubbo-* + + public + UTF-8 + UTF-8 + UTF-8 + + http://docs.oracle.com/javase/7/docs/api + + + - - - - - release - - - - maven-javadoc-plugin - ${maven_javadoc_version} - - - attach-javadoc - - jar - - - none - - - - - true - - org.apache.dubbo:dubbo-* - - public - UTF-8 - UTF-8 - UTF-8 - - http://docs.oracle.com/javase/7/docs/api - - - - - - - + + + diff --git a/dubbo-distribution/dubbo-all/pom.xml b/dubbo-distribution/dubbo-all/pom.xml index 17e1ae2cb52..ee749b16724 100644 --- a/dubbo-distribution/dubbo-all/pom.xml +++ b/dubbo-distribution/dubbo-all/pom.xml @@ -284,6 +284,28 @@ true + + org.apache.dubbo + dubbo-rest-jaxrs + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-rest-servlet + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-rest-spring + ${project.version} + compile + true + + org.apache.dubbo @@ -448,6 +470,14 @@ + + org.springframework + spring-core + + + org.springframework + spring-beans + org.springframework spring-context @@ -544,6 +574,9 @@ org.apache.dubbo:dubbo-rpc-injvm org.apache.dubbo:dubbo-rpc-rest 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-serialization-api org.apache.dubbo:dubbo-serialization-hessian2 org.apache.dubbo:dubbo-serialization-fastjson2 @@ -913,6 +946,27 @@ META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.compressor.DeCompressor + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentConverter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory + META-INF/dubbo/internal/org.apache.dubbo.metrics.service.MetricsService @@ -980,7 +1034,7 @@ org.apache.dubbo:dubbo - + com/** org/** diff --git a/dubbo-distribution/dubbo-bom/pom.xml b/dubbo-distribution/dubbo-bom/pom.xml index 0edc520744b..4b0d3220c8a 100644 --- a/dubbo-distribution/dubbo-bom/pom.xml +++ b/dubbo-distribution/dubbo-bom/pom.xml @@ -334,6 +334,21 @@ dubbo-plugin-proxy-bytebuddy ${project.version} + + org.apache.dubbo + dubbo-rest-jaxrs + ${project.version} + + + org.apache.dubbo + dubbo-rest-servlet + ${project.version} + + + org.apache.dubbo + dubbo-rest-spring + ${project.version} + diff --git a/dubbo-plugin/dubbo-rest-jaxrs/pom.xml b/dubbo-plugin/dubbo-rest-jaxrs/pom.xml new file mode 100644 index 00000000000..26ab19d02f0 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/pom.xml @@ -0,0 +1,102 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-plugin + ${revision} + ../pom.xml + + + dubbo-rest-jaxrs + + + + org.apache.dubbo + dubbo-rpc-triple + ${project.version} + + + javax.ws.rs + javax.ws.rs-api + + + org.jboss.resteasy + resteasy-jaxrs + + + * + * + + + + + org.apache.dubbo + dubbo-remoting-netty4 + ${project.version} + test + + + javax.xml.bind + jaxb-api + test + + + org.glassfish.jaxb + jaxb-runtime + test + + + org.apache.dubbo + dubbo-rpc-rest + ${project.version} + test + + + javax.validation + validation-api + + + io.swagger + swagger-jaxrs + + + io.swagger + swagger-annotations + + + org.eclipse.jetty + jetty-servlet + + + org.apache.tomcat.embed + tomcat-embed-core + + + org.eclipse.jetty + jetty-server + + + org.jboss.resteasy + resteasy-client + + + + + diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/AbstractJaxrsArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/AbstractJaxrsArgumentResolver.java new file mode 100644 index 00000000000..e15ea565799 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/AbstractJaxrsArgumentResolver.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.support.jaxrs; + +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 java.lang.annotation.Annotation; + +public abstract class AbstractJaxrsArgumentResolver extends AbstractAnnotationBaseArgumentResolver { + + @Override + protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta ann) { + return new NamedValueMeta(ann.getValue(), Helper.isRequired(param), Helper.defaultValue(param)); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/Annotations.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/Annotations.java new file mode 100644 index 00000000000..42ddf4ccbd9 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/Annotations.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.support.jaxrs; + +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationEnum; + +import java.lang.annotation.Annotation; + +public enum Annotations implements AnnotationEnum { + Path, + HttpMethod, + Produces, + Consumes, + PathParam, + MatrixParam, + QueryParam, + HeaderParam, + CookieParam, + FormParam, + BeanParam, + Body("org.jboss.resteasy.annotations.Body"), + Form("org.jboss.resteasy.annotations.Form"), + Context("javax.ws.rs.core.Context"), + Suspended("javax.ws.rs.container.Suspended"), + DefaultValue, + Encoded, + Nonnull("javax.annotation.Nonnull"); + + private final String className; + private Class type; + + Annotations(String className) { + this.className = className; + } + + Annotations() { + className = "javax.ws.rs." + name(); + } + + public String className() { + return className; + } + + @Override + public Class type() { + if (type == null) { + type = loadType(); + } + return type; + } +} 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 new file mode 100644 index 00000000000..294875bd953 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanArgumentBinder.java @@ -0,0 +1,280 @@ +/* + * 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.common.beans.factory.ScopeBeanFactory; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.Pair; +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.Messages; +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.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; + +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.Map; + +final class BeanArgumentBinder { + + private final ArgumentResolver argumentResolver; + + private final Map, String>, BeanMeta> cache = CollectionUtils.newConcurrentHashMap(); + + BeanArgumentBinder(FrameworkModel frameworkModel) { + ScopeBeanFactory beanFactory = frameworkModel.getBeanFactory(); + argumentResolver = beanFactory.getOrRegisterBean(CompositeArgumentResolver.class); + } + + public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) { + try { + return resolveArgument(parameter, 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)); + } + 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) { + } + } + 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; + } + 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)); + } + } + } + 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 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 + '}'; + } + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanParamArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanParamArgumentResolver.java new file mode 100644 index 00000000000..7c59ec6d592 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanParamArgumentResolver.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.jaxrs; + +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.AnnotationBaseArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +import java.lang.annotation.Annotation; + +@Activate(onClass = "javax.ws.rs.BeanParam") +public class BeanParamArgumentResolver implements AnnotationBaseArgumentResolver { + + @Override + public Class accept() { + return Annotations.BeanParam.type(); + } + + @Override + public Object resolve( + ParameterMeta parameter, + AnnotationMeta annotation, + HttpRequest request, + HttpResponse response) { + return parameter.bind(request, response); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.java new file mode 100644 index 00000000000..5d461341033 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.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; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.io.StreamUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.RestException; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.AnnotationBaseArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +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; + +@Activate(onClass = "org.jboss.resteasy.annotations.Body") +public class BodyArgumentResolver implements AnnotationBaseArgumentResolver { + + @Override + public Class accept() { + return Annotations.Body.type(); + } + + @Override + public Object resolve( + ParameterMeta parameter, + AnnotationMeta annotation, + HttpRequest request, + HttpResponse response) { + Class type = parameter.getActualType(); + if (type == byte[].class) { + try { + return StreamUtils.readBytes(request.inputStream()); + } catch (IOException e) { + throw new RestException(e); + } + } + return RequestUtils.decodeBody(request, type); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/CookieParamArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/CookieParamArgumentResolver.java new file mode 100644 index 00000000000..7f1b37b25a1 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/CookieParamArgumentResolver.java @@ -0,0 +1,74 @@ +/* + * 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.common.extension.Activate; +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.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.NamedValueMeta; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Activate(onClass = "javax.ws.rs.CookieParam") +public class CookieParamArgumentResolver extends AbstractJaxrsArgumentResolver { + + @Override + public Class accept() { + return Annotations.CookieParam.type(); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.cookie(meta.name()); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + 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; + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + 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; + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java new file mode 100644 index 00000000000..0ecc924df79 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java @@ -0,0 +1,89 @@ +/* + * 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.common.extension.Activate; +import org.apache.dubbo.common.io.StreamUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.RestException; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.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 org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; + +import java.io.IOException; + +@Activate(order = Integer.MAX_VALUE - 10000, onClass = "javax.ws.rs.Path") +public class FallbackArgumentResolver extends AbstractArgumentResolver { + + @Override + public boolean accept(ParameterMeta param) { + return param.getToolKit().getDialect() == RestConstants.DIALECT_JAXRS; + } + + @Override + protected NamedValueMeta createNamedValueMeta(ParameterMeta param) { + return new NamedValueMeta(param.isAnnotated(Annotations.Nonnull), Helper.defaultValue(param)); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + Object value = RequestUtils.decodeBody(request, meta.type()); + if (value != null) { + return value; + } + if (meta.parameterMeta().isSimple()) { + return request.parameter(meta.name()); + } + return null; + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + Class type = meta.type(); + if (type == byte[].class) { + try { + return StreamUtils.readBytes(request.inputStream()); + } catch (IOException e) { + throw new RestException(e); + } + } + Object value = RequestUtils.decodeBody(request, meta.type()); + if (value != null) { + return value; + } + if (TypeUtils.isSimpleProperty(meta.nestedType(0))) { + return request.parameterValues(meta.name()); + } + return null; + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + Object value = RequestUtils.decodeBody(request, meta.type()); + if (value != null) { + return value; + } + if (TypeUtils.isSimpleProperty(meta.nestedType(1))) { + return RequestUtils.getParametersMap(request); + } + return null; + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FormArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FormArgumentResolver.java new file mode 100644 index 00000000000..4f3678be5fa --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FormArgumentResolver.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.jaxrs; + +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.AnnotationBaseArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +import java.lang.annotation.Annotation; + +@Activate(onClass = "org.jboss.resteasy.annotations.Form") +public class FormArgumentResolver implements AnnotationBaseArgumentResolver { + @Override + public Class accept() { + return Annotations.Form.type(); + } + + @Override + public Object resolve( + ParameterMeta parameter, + AnnotationMeta annotation, + HttpRequest request, + HttpResponse response) { + return parameter.bind(request, response); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FormParamArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FormParamArgumentResolver.java new file mode 100644 index 00000000000..7ac1b2fcda0 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FormParamArgumentResolver.java @@ -0,0 +1,52 @@ +/* + * 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.common.extension.Activate; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.NamedValueMeta; + +import java.lang.annotation.Annotation; + +/** + * TODO: support nested values: (e.g., 'telephoneNumbers[0].countryCode' 'address[INVOICE].street') + */ +@Activate(onClass = "javax.ws.rs.FormParam") +public class FormParamArgumentResolver extends AbstractJaxrsArgumentResolver { + + @Override + public Class accept() { + return Annotations.FormParam.type(); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return CollectionUtils.first(request.formParameterValues(getFullName(meta))); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.formParameterValues(getFullName(meta)); + } + + private String getFullName(NamedValueMeta meta) { + String prefix = meta.parameterMeta().getPrefix(); + return prefix == null ? meta.name() : prefix + '.' + meta.name(); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/HeaderParamArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/HeaderParamArgumentResolver.java new file mode 100644 index 00000000000..8989c0ab062 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/HeaderParamArgumentResolver.java @@ -0,0 +1,48 @@ +/* + * 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.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.mapping.meta.NamedValueMeta; + +import java.lang.annotation.Annotation; + +@Activate(onClass = "javax.ws.rs.HeaderParam") +public class HeaderParamArgumentResolver extends AbstractJaxrsArgumentResolver { + + @Override + public Class accept() { + return Annotations.HeaderParam.type(); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.header(meta.name()); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.headerValues(meta.name()); + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.headers(); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/Helper.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/Helper.java new file mode 100644 index 00000000000..9abde6219ac --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/Helper.java @@ -0,0 +1,85 @@ +/* + * 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.HttpCookie; +import org.apache.dubbo.remoting.http12.HttpResult; +import org.apache.dubbo.remoting.http12.HttpUtils; +import org.apache.dubbo.remoting.http12.message.DefaultHttpResult.Builder; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.NewCookie; +import javax.ws.rs.core.Response; + +import java.util.List; +import java.util.stream.Collectors; + +public final class Helper { + + private Helper() {} + + public static boolean isRequired(ParameterMeta parameter) { + return parameter.isAnnotated(Annotations.Nonnull); + } + + public static String defaultValue(ParameterMeta annotation) { + AnnotationMeta meta = annotation.getAnnotation(Annotations.DefaultValue); + return meta == null ? null : meta.getValue(); + } + + public static HttpResult toBody(Response response) { + Builder builder = HttpResult.builder().status(response.getStatus()); + if (response.hasEntity()) { + builder.body(response.getEntity()); + } + builder.headers(response.getStringHeaders()); + return builder.build(); + } + + public static MediaType toMediaType(String mediaType) { + if (mediaType == null) { + return null; + } + int index = mediaType.indexOf('/'); + if (index == -1) { + return null; + } + return new MediaType(mediaType.substring(0, index), mediaType.substring(index + 1)); + } + + public static String toString(MediaType mediaType) { + return mediaType.getType() + '/' + mediaType.getSubtype(); + } + + public static List toMediaTypes(String accept) { + return HttpUtils.parseAccept(accept).stream().map(Helper::toMediaType).collect(Collectors.toList()); + } + + public static NewCookie convert(HttpCookie cookie) { + return new NewCookie( + cookie.name(), + cookie.value(), + cookie.path(), + cookie.domain(), + null, + (int) cookie.maxAge(), + cookie.secure(), + cookie.httpOnly()); + } +} 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/JaxrsHttpRequestAdaptee.java new file mode 100644 index 00000000000..3ff3f85381f --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdaptee.java @@ -0,0 +1,163 @@ +/* + * 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.HttpRequest; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; + +import java.io.InputStream; +import java.net.URI; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import org.jboss.resteasy.specimpl.ResteasyHttpHeaders; +import org.jboss.resteasy.spi.ResteasyAsynchronousContext; +import org.jboss.resteasy.spi.ResteasyUriInfo; + +public final class JaxrsHttpRequestAdaptee implements org.jboss.resteasy.spi.HttpRequest { + + private final HttpRequest request; + + private HttpHeaders headers; + private ResteasyUriInfo uriInfo; + + public JaxrsHttpRequestAdaptee(HttpRequest request) { + this.request = request; + } + + @Override + public HttpHeaders getHttpHeaders() { + HttpHeaders headers = this.headers; + if (headers == null) { + headers = new ResteasyHttpHeaders(new MultivaluedMapWrapper<>(request.headers())); + this.headers = headers; + } + return headers; + } + + @Override + public MultivaluedMap getMutableHeaders() { + return headers.getRequestHeaders(); + } + + @Override + public InputStream getInputStream() { + return request.inputStream(); + } + + @Override + public void setInputStream(InputStream stream) { + request.setInputStream(stream); + } + + @Override + public ResteasyUriInfo getUri() { + ResteasyUriInfo uriInfo = this.uriInfo; + if (uriInfo == null) { + uriInfo = new ResteasyUriInfo(request.rawPath(), request.query(), "/"); + this.uriInfo = uriInfo; + } + return uriInfo; + } + + @Override + public String getHttpMethod() { + return request.method(); + } + + @Override + public void setHttpMethod(String method) { + request.setMethod(method); + } + + @Override + public void setRequestUri(URI requestUri) throws IllegalStateException { + String query = requestUri.getRawQuery(); + request.setUri(requestUri.getRawPath() + (query == null ? "" : '?' + query)); + } + + @Override + public void setRequestUri(URI baseUri, URI requestUri) throws IllegalStateException { + String query = requestUri.getRawQuery(); + request.setUri(baseUri.getRawPath() + requestUri.getRawPath() + (query == null ? "" : '?' + query)); + } + + @Override + public MultivaluedMap getFormParameters() { + MultivaluedMap result = new MultivaluedHashMap<>(); + for (String name : request.formParameterNames()) { + List values = request.formParameterValues(name); + if (values == null) { + continue; + } + for (String value : values) { + result.add(name, RequestUtils.encodeURL(value)); + } + } + return result; + } + + @Override + public MultivaluedMap getDecodedFormParameters() { + return new MultivaluedMapWrapper<>(RequestUtils.getFormParametersMap(request)); + } + + @Override + public Object getAttribute(String attribute) { + return request.attribute(attribute); + } + + @Override + public void setAttribute(String name, Object value) { + request.setAttribute(name, value); + } + + @Override + public void removeAttribute(String name) { + request.removeAttribute(name); + } + + @Override + public Enumeration getAttributeNames() { + return Collections.enumeration(request.attributeNames()); + } + + @Override + public ResteasyAsynchronousContext getAsyncContext() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isInitial() { + return false; + } + + @Override + public void forward(String path) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean wasForwarded() { + return false; + } +} 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/JaxrsHttpResponseAdaptee.java new file mode 100644 index 00000000000..94a4bf417af --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdaptee.java @@ -0,0 +1,101 @@ +/* + * 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.HttpCookie; +import org.apache.dubbo.remoting.http12.HttpResponse; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.NewCookie; + +import java.io.OutputStream; + +public final class JaxrsHttpResponseAdaptee implements org.jboss.resteasy.spi.HttpResponse { + + private final HttpResponse response; + + private MultivaluedMap headers; + + public JaxrsHttpResponseAdaptee(HttpResponse response) { + this.response = response; + } + + @Override + public int getStatus() { + return response.status(); + } + + @Override + public void setStatus(int status) { + response.setStatus(status); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public MultivaluedMap getOutputHeaders() { + MultivaluedMap headers = this.headers; + if (headers == null) { + headers = new MultivaluedMapWrapper(response.headers()); + this.headers = headers; + } + return headers; + } + + @Override + public OutputStream getOutputStream() { + return response.outputStream(); + } + + @Override + public void setOutputStream(OutputStream os) { + response.setOutputStream(os); + } + + @Override + public void addNewCookie(NewCookie cookie) { + HttpCookie hCookie = new HttpCookie(cookie.getName(), cookie.getValue()); + hCookie.setDomain(cookie.getDomain()); + hCookie.setPath(cookie.getPath()); + hCookie.setMaxAge(cookie.getMaxAge()); + hCookie.setSecure(cookie.isSecure()); + hCookie.setHttpOnly(cookie.isHttpOnly()); + response.addCookie(hCookie); + } + + @Override + public void sendError(int status) { + response.sendError(status); + } + + @Override + public void sendError(int status, String message) { + response.sendError(status, message); + } + + @Override + public boolean isCommitted() { + return response.isCommitted(); + } + + @Override + public void reset() { + response.reset(); + } + + @Override + public void flushBuffer() {} +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsMiscArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsMiscArgumentResolver.java new file mode 100644 index 00000000000..097769d4dac --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsMiscArgumentResolver.java @@ -0,0 +1,83 @@ +/* + * 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.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.ArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; + +import javax.ws.rs.core.Cookie; +import javax.ws.rs.core.Form; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriInfo; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.jboss.resteasy.specimpl.ResteasyHttpHeaders; +import org.jboss.resteasy.spi.ResteasyUriInfo; + +@Activate(onClass = "javax.ws.rs.Path") +public class JaxrsMiscArgumentResolver implements ArgumentResolver { + + private static final Set> SUPPORTED_TYPES = new HashSet<>(); + + static { + SUPPORTED_TYPES.add(Cookie.class); + SUPPORTED_TYPES.add(Form.class); + SUPPORTED_TYPES.add(HttpHeaders.class); + SUPPORTED_TYPES.add(MediaType.class); + SUPPORTED_TYPES.add(MultivaluedMap.class); + SUPPORTED_TYPES.add(UriInfo.class); + } + + @Override + public boolean accept(ParameterMeta parameter) { + return SUPPORTED_TYPES.contains(parameter.getActualType()); + } + + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public Object resolve(ParameterMeta parameter, HttpRequest request, HttpResponse response) { + Class type = parameter.getActualType(); + if (Cookie.class.isAssignableFrom(type)) { + return Helper.convert(request.cookie(parameter.getRequiredName())); + } + if (Form.class.isAssignableFrom(type)) { + return RequestUtils.getFormParametersMap(request); + } + if (HttpHeaders.class.isAssignableFrom(type)) { + return new ResteasyHttpHeaders(new MultivaluedMapWrapper<>(request.headers())); + } + if (MediaType.class.isAssignableFrom(type)) { + return Helper.toMediaType(request.mediaType()); + } + if (MultivaluedMap.class.isAssignableFrom(type)) { + return new MultivaluedMapWrapper<>((Map) RequestUtils.getParametersMap(request)); + } + if (UriInfo.class.isAssignableFrom(type)) { + return new ResteasyUriInfo(request.rawPath(), request.query(), "/"); + } + return null; + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java new file mode 100644 index 00000000000..9d7dc5a1432 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java @@ -0,0 +1,93 @@ +/* + * 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.common.extension.Activate; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationSupport; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; + +@Activate(onClass = "javax.ws.rs.Path") +public class JaxrsRequestMappingResolver implements RequestMappingResolver { + + private final RestToolKit toolKit; + + public JaxrsRequestMappingResolver(FrameworkModel frameworkModel) { + toolKit = new JaxrsRestToolKit(frameworkModel); + } + + @Override + public RestToolKit getRestToolKit() { + return toolKit; + } + + @Override + public RequestMapping resolve(ServiceMeta serviceMeta) { + AnnotationMeta path = serviceMeta.findAnnotation(Annotations.Path); + if (path == null) { + return null; + } + return builder(serviceMeta, path, null) + .name(serviceMeta.getType().getSimpleName()) + .contextPath(serviceMeta.getContextPath()) + .build(); + } + + @Override + public RequestMapping resolve(MethodMeta methodMeta) { + AnnotationMeta path = methodMeta.findAnnotation(Annotations.Path); + if (path == null) { + return null; + } + AnnotationMeta httpMethod = methodMeta.findMergedAnnotation(Annotations.HttpMethod); + if (httpMethod == null) { + return null; + } + ServiceMeta serviceMeta = methodMeta.getServiceMeta(); + return builder(methodMeta, path, httpMethod) + .name(methodMeta.getMethod().getName()) + .contextPath(methodMeta.getServiceMeta().getContextPath()) + .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) + .build(); + } + + private Builder builder(AnnotationSupport meta, AnnotationMeta path, AnnotationMeta httpMethod) { + Builder builder = RequestMapping.builder().path(path.getValue()); + if (httpMethod == null) { + httpMethod = meta.findMergedAnnotation(Annotations.HttpMethod); + } + if (httpMethod != null) { + builder.method(httpMethod.getValue()); + } + AnnotationMeta produces = meta.findAnnotation(Annotations.Produces); + if (produces != null) { + builder.produce(produces.getValueArray()); + } + AnnotationMeta consumes = meta.findAnnotation(Annotations.Consumes); + if (consumes != null) { + builder.consume(consumes.getValueArray()); + } + return builder; + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsResponseRestFilter.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsResponseRestFilter.java new file mode 100644 index 00000000000..3837fd8e80f --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsResponseRestFilter.java @@ -0,0 +1,42 @@ +/* + * 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.common.extension.Activate; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter.Listener; + +import javax.ws.rs.core.Response; + +@Activate(order = -10000, onClass = "javax.ws.rs.Path") +public class JaxrsResponseRestFilter implements RestFilter, Listener { + + @Override + public void onResponse(Result result, HttpRequest request, HttpResponse response) { + if (result.hasException()) { + return; + } + + Object value = result.getValue(); + if (value instanceof Response) { + result.setValue(Helper.toBody((Response) value)); + } + } +} 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 new file mode 100644 index 00000000000..85e66b21e42 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRestToolKit.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.jaxrs; + +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.DefaultRestToolKit; + +final class JaxrsRestToolKit extends DefaultRestToolKit { + + private final BeanArgumentBinder binder; + + public JaxrsRestToolKit(FrameworkModel frameworkModel) { + super(frameworkModel); + binder = new BeanArgumentBinder(frameworkModel); + } + + @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/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MatrixParamArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MatrixParamArgumentResolver.java new file mode 100644 index 00000000000..a3f6b99bea2 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MatrixParamArgumentResolver.java @@ -0,0 +1,53 @@ +/* + * 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.common.extension.Activate; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.NamedValueMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Map; + +@Activate(onClass = "javax.ws.rs.MatrixParam") +public class MatrixParamArgumentResolver extends AbstractJaxrsArgumentResolver { + + @Override + public Class accept() { + return Annotations.MatrixParam.type(); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return CollectionUtils.first(doResolveCollectionValue(meta, request)); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return doResolveCollectionValue(meta, request); + } + + private static List doResolveCollectionValue(NamedValueMeta meta, HttpRequest request) { + Map variableMap = request.attribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + return RequestUtils.parseMatrixVariableValues(variableMap, meta.name()); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MultivaluedMapCreationConverter.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MultivaluedMapCreationConverter.java new file mode 100644 index 00000000000..eb9e9df3c8b --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MultivaluedMapCreationConverter.java @@ -0,0 +1,31 @@ +/* + * 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.common.convert.Converter; + +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; + +@SuppressWarnings("rawtypes") +public class MultivaluedMapCreationConverter implements Converter { + + @Override + public MultivaluedMap convert(Integer source) { + return new MultivaluedHashMap<>(source); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MultivaluedMapWrapper.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MultivaluedMapWrapper.java new file mode 100644 index 00000000000..63dbfd89b5d --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/MultivaluedMapWrapper.java @@ -0,0 +1,29 @@ +/* + * 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 javax.ws.rs.core.AbstractMultivaluedMap; + +import java.util.List; +import java.util.Map; + +public final class MultivaluedMapWrapper extends AbstractMultivaluedMap { + + public MultivaluedMapWrapper(Map> store) { + super(store); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/PathParamArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/PathParamArgumentResolver.java new file mode 100644 index 00000000000..8e1fe007208 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/PathParamArgumentResolver.java @@ -0,0 +1,69 @@ +/* + * 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.common.extension.Activate; +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; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.RestParameterException; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.AnnotationBaseArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +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; +import java.util.Map; + +@Activate(onClass = "javax.ws.rs.PathParam") +public class PathParamArgumentResolver implements AnnotationBaseArgumentResolver { + + @Override + public Class accept() { + return Annotations.PathParam.type(); + } + + @Override + public Object resolve( + ParameterMeta parameter, + AnnotationMeta annotation, + HttpRequest request, + HttpResponse response) { + Map variableMap = request.attribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + String name = annotation.getValue(); + if (StringUtils.isEmpty(name)) { + name = parameter.getRequiredName(); + } + if (variableMap == null) { + if (Helper.isRequired(parameter)) { + throw new RestParameterException(Messages.ARGUMENT_VALUE_MISSING, name, parameter.getType()); + } + return null; + } + String value = variableMap.get(name); + if (value == null) { + return null; + } + int index = value.indexOf(';'); + if (index != -1) { + value = value.substring(0, index); + } + return parameter.isAnnotated(Annotations.Encoded) ? value : RequestUtils.decodeURL(value); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/QueryParamArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/QueryParamArgumentResolver.java new file mode 100644 index 00000000000..9b156c7da0a --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/QueryParamArgumentResolver.java @@ -0,0 +1,49 @@ +/* + * 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.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.mapping.meta.NamedValueMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; + +import java.lang.annotation.Annotation; + +@Activate(onClass = "javax.ws.rs.QueryParam") +public class QueryParamArgumentResolver extends AbstractJaxrsArgumentResolver { + + @Override + public Class accept() { + return Annotations.QueryParam.type(); + } + + @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-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 new file mode 100644 index 00000000000..907524f0f64 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestContextImpl.java @@ -0,0 +1,214 @@ +/* + * 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.filter; + +import org.apache.dubbo.remoting.http12.HttpCookie; +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.MultivaluedMapWrapper; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Cookie; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.UriInfo; + +import java.io.InputStream; +import java.net.URI; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.jboss.resteasy.specimpl.RequestImpl; +import org.jboss.resteasy.spi.ResteasyUriInfo; + +final class ContainerRequestContextImpl implements ContainerRequestContext { + + private final HttpRequest request; + private final HttpResponse response; + + private Request req; + private MultivaluedMap headers; + private UriInfo uriInfo; + + private boolean aborted; + + public ContainerRequestContextImpl(HttpRequest request, HttpResponse response) { + this.request = request; + this.response = response; + } + + @Override + public Object getProperty(String name) { + return request.attribute(name); + } + + @Override + public Collection getPropertyNames() { + return request.parameterNames(); + } + + @Override + public void setProperty(String name, Object object) { + request.setAttribute(name, object); + } + + @Override + public void removeProperty(String name) { + request.removeAttribute(name); + } + + @Override + public UriInfo getUriInfo() { + UriInfo uriInfo = this.uriInfo; + if (uriInfo == null) { + uriInfo = new ResteasyUriInfo(request.rawPath(), request.query(), "/"); + this.uriInfo = uriInfo; + } + return uriInfo; + } + + @Override + public void setRequestUri(URI requestUri) { + String query = requestUri.getRawQuery(); + request.setUri(requestUri.getRawPath() + (query == null ? "" : '?' + query)); + } + + @Override + public void setRequestUri(URI baseUri, URI requestUri) { + String query = requestUri.getRawQuery(); + request.setUri(baseUri.getRawPath() + requestUri.getRawPath() + (query == null ? "" : '?' + query)); + } + + @Override + public Request getRequest() { + Request req = this.req; + if (req == null) { + req = new RequestImpl(new JaxrsHttpRequestAdaptee(request), new JaxrsHttpResponseAdaptee(response)); + this.req = req; + } + return req; + } + + @Override + public String getMethod() { + return request.method(); + } + + @Override + public void setMethod(String method) { + request.setMethod(method); + } + + @Override + public MultivaluedMap getHeaders() { + MultivaluedMap headers = this.headers; + if (headers == null) { + headers = new MultivaluedMapWrapper<>(request.headers()); + this.headers = headers; + } + return headers; + } + + @Override + public String getHeaderString(String name) { + return request.header(name); + } + + @Override + public Date getDate() { + return null; + } + + @Override + public Locale getLanguage() { + return request.locale(); + } + + @Override + public int getLength() { + return request.contentLength(); + } + + @Override + public MediaType getMediaType() { + return Helper.toMediaType(request.mediaType()); + } + + @Override + public List getAcceptableMediaTypes() { + return Helper.toMediaTypes(request.accept()); + } + + @Override + public List getAcceptableLanguages() { + return request.locales(); + } + + @Override + public Map getCookies() { + Collection cookies = request.cookies(); + Map result = new HashMap<>(cookies.size()); + for (HttpCookie cookie : cookies) { + result.put(cookie.name(), Helper.convert(cookie)); + } + return result; + } + + @Override + @SuppressWarnings("resource") + public boolean hasEntity() { + return request.inputStream() != null; + } + + @Override + public InputStream getEntityStream() { + return request.inputStream(); + } + + @Override + public void setEntityStream(InputStream input) { + request.setInputStream(input); + } + + @Override + public SecurityContext getSecurityContext() { + return null; + } + + @Override + public void setSecurityContext(SecurityContext context) {} + + @Override + public void abortWith(Response response) { + this.response.setBody(Helper.toBody(response)); + aborted = true; + } + + public boolean isAborted() { + return aborted; + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestFilterAdapter.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestFilterAdapter.java new file mode 100644 index 00000000000..71ab5b681b2 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestFilterAdapter.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.jaxrs.filter; + +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.filter.AbstractRestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter; + +import javax.ws.rs.container.ContainerRequestFilter; + +@Activate(onClass = "javax.ws.rs.container.ContainerResponseFilter") +public class ContainerRequestFilterAdapter implements RestExtensionAdapter { + + @Override + public boolean accept(Object extension) { + return extension instanceof ContainerRequestFilter; + } + + @Override + public RestFilter adapt(ContainerRequestFilter extension) { + return new Filter(extension); + } + + private static final class Filter extends AbstractRestFilter { + + public Filter(ContainerRequestFilter extension) { + super(extension); + } + + @Override + public void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) throws Exception { + ContainerRequestContextImpl context = new ContainerRequestContextImpl(request, response); + extension.filter(context); + if (context.isAborted()) { + return; + } + chain.doFilter(request, response); + } + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerResponseContextImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerResponseContextImpl.java new file mode 100644 index 00000000000..109947cda7f --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerResponseContextImpl.java @@ -0,0 +1,228 @@ +/* + * 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.filter; + +import org.apache.dubbo.remoting.http12.HttpCookie; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.HttpUtils; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.Helper; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.MultivaluedMapWrapper; + +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.core.EntityTag; +import javax.ws.rs.core.Link; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.NewCookie; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.Response.StatusType; + +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.net.URI; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import io.netty.handler.codec.DateFormatter; + +final class ContainerResponseContextImpl implements ContainerResponseContext { + + private final HttpRequest request; + private final HttpResponse response; + private final Result result; + + private MultivaluedMap headers; + + public ContainerResponseContextImpl(HttpRequest request, HttpResponse response, Result result) { + this.request = request; + this.response = response; + this.result = result; + } + + @Override + public int getStatus() { + return response.status(); + } + + @Override + public void setStatus(int code) { + response.setStatus(code); + } + + @Override + public StatusType getStatusInfo() { + return Status.fromStatusCode(response.status()); + } + + @Override + public void setStatusInfo(StatusType statusInfo) { + response.setStatus(statusInfo.getStatusCode()); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public MultivaluedMap getHeaders() { + return (MultivaluedMap) getStringHeaders(); + } + + @Override + public MultivaluedMap getStringHeaders() { + MultivaluedMap headers = this.headers; + if (headers == null) { + headers = new MultivaluedMapWrapper<>(response.headers()); + this.headers = headers; + } + return headers; + } + + @Override + public String getHeaderString(String name) { + return response.header(name); + } + + @Override + public Set getAllowedMethods() { + return Collections.singleton(request.method()); + } + + @Override + public Date getDate() { + return null; + } + + @Override + public Locale getLanguage() { + return HttpUtils.parseLocale(response.locale()); + } + + @Override + public int getLength() { + return -1; + } + + @Override + public MediaType getMediaType() { + return Helper.toMediaType(response.mediaType()); + } + + @Override + public Map getCookies() { + Collection cookies = request.cookies(); + Map result = new HashMap<>(cookies.size()); + for (HttpCookie cookie : cookies) { + result.put(cookie.name(), Helper.convert(cookie)); + } + return result; + } + + @Override + public EntityTag getEntityTag() { + return null; + } + + @Override + public Date getLastModified() { + String value = response.header("last-modified"); + return value == null ? null : DateFormatter.parseHttpDate(value); + } + + @Override + public URI getLocation() { + String location = response.header("location"); + return location == null ? null : URI.create(location); + } + + @Override + public Set getLinks() { + return null; + } + + @Override + public boolean hasLink(String relation) { + return false; + } + + @Override + public Link getLink(String relation) { + return null; + } + + @Override + public Link.Builder getLinkBuilder(String relation) { + return null; + } + + @Override + public boolean hasEntity() { + return getEntity() != null; + } + + @Override + public Object getEntity() { + return result.getValue(); + } + + @Override + public Class getEntityClass() { + return getHandler().getMethod().getReturnType(); + } + + @Override + public Type getEntityType() { + return getHandler().getMethod().getGenericReturnType(); + } + + @Override + public void setEntity(Object entity) { + result.setValue(entity); + } + + @Override + public void setEntity(Object entity, Annotation[] annotations, MediaType mediaType) { + result.setValue(entity); + response.setContentType(Helper.toString(mediaType)); + } + + @Override + public Annotation[] getEntityAnnotations() { + return new Annotation[0]; + } + + @Override + public OutputStream getEntityStream() { + return response.outputStream(); + } + + @Override + public void setEntityStream(OutputStream outputStream) { + response.setOutputStream(outputStream); + } + + private HandlerMeta getHandler() { + return request.attribute(RestConstants.HANDLER_ATTRIBUTE); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerResponseFilterAdapter.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerResponseFilterAdapter.java new file mode 100644 index 00000000000..469d2567c5d --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerResponseFilterAdapter.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.filter; + +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.Result; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.AbstractRestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter.Listener; + +import javax.ws.rs.container.ContainerResponseFilter; + +@Activate(onClass = "javax.ws.rs.container.ContainerResponseFilter") +public class ContainerResponseFilterAdapter implements RestExtensionAdapter { + + @Override + public boolean accept(Object extension) { + return extension instanceof ContainerResponseFilter; + } + + @Override + public RestFilter adapt(ContainerResponseFilter extension) { + return new Filter(extension); + } + + private static final class Filter extends AbstractRestFilter implements Listener { + + public Filter(ContainerResponseFilter extension) { + super(extension); + } + + @Override + public void onResponse(Result result, HttpRequest request, HttpResponse response) throws Exception { + extension.filter( + new ContainerRequestContextImpl(request, response), + new ContainerResponseContextImpl(request, response, result)); + } + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ExceptionMapperAdapter.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ExceptionMapperAdapter.java new file mode 100644 index 00000000000..f15911dd54f --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ExceptionMapperAdapter.java @@ -0,0 +1,76 @@ +/* + * 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.filter; + +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.Result; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.AbstractRestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter.Listener; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.Helper; +import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; + +@Activate(onClass = "javax.ws.rs.ext.ExceptionMapper") +public final class ExceptionMapperAdapter implements RestExtensionAdapter> { + + @Override + public boolean accept(Object extension) { + return extension instanceof ExceptionMapper; + } + + @Override + public RestFilter adapt(ExceptionMapper extension) { + return new Filter(extension); + } + + private static final class Filter extends AbstractRestFilter> implements Listener { + + private final Class exceptionType; + + public Filter(ExceptionMapper extension) { + super(extension); + exceptionType = TypeUtils.getSuperGenericType(extension.getClass()); + } + + @Override + public void onResponse(Result result, HttpRequest request, HttpResponse response) throws Exception { + if (result.hasException()) { + Throwable t = result.getException(); + if (exceptionType.isInstance(t)) { + try (Response r = extension.toResponse(t)) { + response.setBody(Helper.toBody(r)); + } + } + } + } + + @Override + public void onError(Throwable t, HttpRequest request, HttpResponse response) { + if (exceptionType.isInstance(t)) { + try (Response r = extension.toResponse(t)) { + response.setBody(Helper.toBody(r)); + } + } + } + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java new file mode 100644 index 00000000000..ee67447bb51 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java @@ -0,0 +1,84 @@ +/* + * 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.filter; + +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.meta.HandlerMeta; + +import javax.ws.rs.ext.InterceptorContext; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collection; + +public abstract class InterceptorContextImpl implements InterceptorContext { + + protected final HttpRequest request; + + public InterceptorContextImpl(HttpRequest request) { + this.request = request; + } + + @Override + public Object getProperty(String name) { + return request.attribute(name); + } + + @Override + public Collection getPropertyNames() { + return request.parameterNames(); + } + + @Override + public void setProperty(String name, Object object) { + request.setAttribute(name, object); + } + + @Override + public void removeProperty(String name) { + request.removeAttribute(name); + } + + @Override + public Annotation[] getAnnotations() { + return getHandler().getMethod().getRealAnnotations(); + } + + @Override + public void setAnnotations(Annotation[] annotations) {} + + @Override + public Class getType() { + return getHandler().getMethod().getReturnType(); + } + + @Override + public void setType(Class type) {} + + @Override + public Type getGenericType() { + return getHandler().getMethod().getGenericReturnType(); + } + + @Override + public void setGenericType(Type genericType) {} + + private HandlerMeta getHandler() { + return request.attribute(RestConstants.HANDLER_ATTRIBUTE); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ReadInterceptorAdapter.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ReadInterceptorAdapter.java new file mode 100644 index 00000000000..c5a3339ee57 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ReadInterceptorAdapter.java @@ -0,0 +1,52 @@ +/* + * 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.filter; + +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.filter.AbstractRestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter; + +import javax.ws.rs.ext.ReaderInterceptor; + +@Activate(onClass = "javax.ws.rs.ext.ReaderInterceptor") +public final class ReadInterceptorAdapter implements RestExtensionAdapter { + + @Override + public boolean accept(Object extension) { + return extension instanceof ReaderInterceptor; + } + + @Override + public RestFilter adapt(ReaderInterceptor extension) { + return new Filter(extension); + } + + private static final class Filter extends AbstractRestFilter { + + public Filter(ReaderInterceptor extension) { + super(extension); + } + + @Override + public void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) throws Exception { + extension.aroundReadFrom(new ReaderInterceptorContextImpl(request, response, chain)); + } + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ReaderInterceptorContextImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ReaderInterceptorContextImpl.java new file mode 100644 index 00000000000..87a19013e28 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ReaderInterceptorContextImpl.java @@ -0,0 +1,88 @@ +/* + * 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.filter; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter.FilterChain; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.Helper; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.MultivaluedMapWrapper; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.ReaderInterceptorContext; + +import java.io.IOException; +import java.io.InputStream; + +final class ReaderInterceptorContextImpl extends InterceptorContextImpl implements ReaderInterceptorContext { + + private final HttpResponse response; + private final FilterChain chain; + + private MultivaluedMap headers; + + public ReaderInterceptorContextImpl(HttpRequest request, HttpResponse response, FilterChain chain) { + super(request); + this.response = response; + this.chain = chain; + headers = new MultivaluedMapWrapper<>(request.headers()); + } + + @Override + public Object proceed() throws IOException, WebApplicationException { + try { + chain.doFilter(request, response); + } catch (RuntimeException | IOException e) { + throw e; + } catch (Exception e) { + throw new WebApplicationException(e); + } + return null; + } + + @Override + public InputStream getInputStream() { + return request.inputStream(); + } + + @Override + public void setInputStream(InputStream is) { + request.setInputStream(is); + } + + @Override + public MultivaluedMap getHeaders() { + MultivaluedMap headers = this.headers; + if (headers == null) { + headers = new MultivaluedMapWrapper<>(request.headers()); + this.headers = headers; + } + return headers; + } + + @Override + public MediaType getMediaType() { + return Helper.toMediaType(request.mediaType()); + } + + @Override + public void setMediaType(MediaType mediaType) { + request.setContentType(Helper.toString(mediaType)); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/WriterInterceptorAdapter.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/WriterInterceptorAdapter.java new file mode 100644 index 00000000000..ac877bc16e3 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/WriterInterceptorAdapter.java @@ -0,0 +1,54 @@ +/* + * 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.filter; + +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.Result; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.AbstractRestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter.Listener; + +import javax.ws.rs.ext.WriterInterceptor; + +@Activate(onClass = "javax.ws.rs.ext.WriterInterceptor") +public final class WriterInterceptorAdapter implements RestExtensionAdapter { + + @Override + public boolean accept(Object extension) { + return extension instanceof WriterInterceptor; + } + + @Override + public RestFilter adapt(WriterInterceptor extension) { + return new Filter(extension); + } + + private static final class Filter extends AbstractRestFilter implements Listener { + + public Filter(WriterInterceptor extension) { + super(extension); + } + + @Override + public void onResponse(Result result, HttpRequest request, HttpResponse response) throws Exception { + extension.aroundWriteTo(new WriterInterceptorContextImpl(request, response, result)); + } + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/WriterInterceptorContextImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/WriterInterceptorContextImpl.java new file mode 100644 index 00000000000..08e850ea4a1 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/WriterInterceptorContextImpl.java @@ -0,0 +1,88 @@ +/* + * 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.filter; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.Helper; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.MultivaluedMapWrapper; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.WriterInterceptorContext; + +import java.io.OutputStream; + +final class WriterInterceptorContextImpl extends InterceptorContextImpl implements WriterInterceptorContext { + + private final HttpResponse response; + private final Result result; + + private MultivaluedMap headers; + + public WriterInterceptorContextImpl(HttpRequest request, HttpResponse response, Result result) { + super(request); + this.response = response; + this.result = result; + } + + @Override + public void proceed() throws WebApplicationException {} + + @Override + public Object getEntity() { + return result.getValue(); + } + + @Override + public void setEntity(Object entity) { + result.setValue(entity); + } + + @Override + public OutputStream getOutputStream() { + return response.outputStream(); + } + + @Override + public void setOutputStream(OutputStream os) { + response.setOutputStream(os); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public MultivaluedMap getHeaders() { + MultivaluedMap headers = this.headers; + if (headers == null) { + headers = new MultivaluedMapWrapper(response.headers()); + this.headers = headers; + } + return headers; + } + + @Override + public MediaType getMediaType() { + return Helper.toMediaType(response.mediaType()); + } + + @Override + public void setMediaType(MediaType mediaType) { + response.setContentType(Helper.toString(mediaType)); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.convert.Converter b/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.convert.Converter new file mode 100644 index 00000000000..1af72a984d2 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.convert.Converter @@ -0,0 +1 @@ +integer-to-multi-valued-map=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.MultivaluedMapCreationConverter diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver b/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver new file mode 100644 index 00000000000..be5aca08b26 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver @@ -0,0 +1,11 @@ +jaxrs-path-param=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.PathParamArgumentResolver +jaxrs-matrix-param=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.MatrixParamArgumentResolver +jaxrs-query-param=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.QueryParamArgumentResolver +jaxrs-header-param=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.HeaderParamArgumentResolver +jaxrs-cookie-param=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.CookieParamArgumentResolver +jaxrs-form-param=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.FormParamArgumentResolver +jaxrs-bean-param=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.BeanParamArgumentResolver +jaxrs-body=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.BodyArgumentResolver +jaxrs-form=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.FormArgumentResolver +jaxrs-misc=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.JaxrsMiscArgumentResolver +jaxrs-fallback=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.FallbackArgumentResolver diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension b/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension new file mode 100644 index 00000000000..311b476b1d0 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension @@ -0,0 +1 @@ +jaxrs-response=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.JaxrsResponseRestFilter diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter b/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter new file mode 100644 index 00000000000..327cb84fa36 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter @@ -0,0 +1,5 @@ +jaxrs-request-filter=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.filter.ContainerRequestFilterAdapter +jaxrs-response-filter=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.filter.ContainerResponseFilterAdapter +jaxrs-read-interceptor=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.filter.ReadInterceptorAdapter +jaxrs-writer-interceptor=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.filter.WriterInterceptorAdapter +jaxrs-exception-mapper=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.filter.ExceptionMapperAdapter diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver b/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver new file mode 100644 index 00000000000..7ae7ecb0e1d --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver @@ -0,0 +1 @@ +jaxrs=org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.JaxrsRequestMappingResolver diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/DemoService.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/DemoService.java new file mode 100644 index 00000000000..882d1af0be9 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/DemoService.java @@ -0,0 +1,156 @@ +/* + * 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; + +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import org.jboss.resteasy.annotations.Form; + +@Path("/demoService") +public interface DemoService { + @GET + @Path("/hello") + Integer hello(@QueryParam("a") Integer a, @QueryParam("b") Integer b); + + @GET + @Path("/error") + @Consumes({MediaType.TEXT_PLAIN}) + @Produces({MediaType.TEXT_PLAIN}) + String error(); + + @POST + @Path("/say") + @Consumes({MediaType.TEXT_PLAIN}) + String sayHello(String name); + + @POST + @Path("number") + Long testFormBody(@FormParam("number") Long number); + + boolean isCalled(); + + @GET + @Path("/primitive") + int primitiveInt(@QueryParam("a") int a, @QueryParam("b") int b); + + @GET + @Path("/primitiveLong") + long primitiveLong(@QueryParam("a") long a, @QueryParam("b") Long b); + + @GET + @Path("/primitiveByte") + long primitiveByte(@QueryParam("a") byte a, @QueryParam("b") Long b); + + @POST + @Path("/primitiveShort") + long primitiveShort(@QueryParam("a") short a, @QueryParam("b") Long b, int c); + + @GET + @Path("/request") + void request(DefaultFullHttpRequest defaultFullHttpRequest); + + @GET + @Path("testMapParam") + @Produces({MediaType.TEXT_PLAIN}) + @Consumes({MediaType.TEXT_PLAIN}) + String testMapParam(@QueryParam("test") Map params); + + @GET + @Path("testMapHeader") + @Produces({MediaType.TEXT_PLAIN}) + @Consumes({MediaType.TEXT_PLAIN}) + String testMapHeader(@HeaderParam("test") Map headers); + + @POST + @Path("testMapForm") + @Produces({MediaType.APPLICATION_JSON}) + @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) + List testMapForm(MultivaluedMap params); + + @POST + @Path("/header") + @Consumes({MediaType.TEXT_PLAIN}) + String header(@HeaderParam("header") String header); + + @POST + @Path("/headerInt") + @Consumes({MediaType.TEXT_PLAIN}) + int headerInt(@HeaderParam("header") int header); + + @POST + @Path("/noStringParam") + @Consumes({MediaType.TEXT_PLAIN}) + String noStringParam(@QueryParam("param") String param); + + @POST + @Path("/noStringHeader") + @Consumes({MediaType.TEXT_PLAIN}) + String noStringHeader(@HeaderParam("header") String header); + + @POST + @Path("/noIntHeader") + @Consumes({MediaType.TEXT_PLAIN}) + int noIntHeader(int header); + + @POST + @Path("/noIntParam") + @Consumes({MediaType.TEXT_PLAIN}) + int noIntParam(int header); + + @POST + @Path("/noBodyArg") + @Consumes({MediaType.APPLICATION_JSON}) + User noBodyArg(User user); + + @POST + @Path("/list") + List list(List users); + + @POST + @Path("/set") + Set set(Set users); + + @POST + @Path("/array") + User[] array(User[] users); + + @POST + @Path("/stringMap") + Map stringMap(Map userMap); + + @POST + @Path("/map") + Map userMap(Map userMap); + + @POST + @Path("/formBody") + User formBody(@Form User user); +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/DemoServiceImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/DemoServiceImpl.java new file mode 100644 index 00000000000..cac64422e9f --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/DemoServiceImpl.java @@ -0,0 +1,187 @@ +/* + * 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; + +import org.apache.dubbo.rpc.RpcContext; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.netty.handler.codec.http.DefaultFullHttpRequest; + +@Path("/demoService") +public class DemoServiceImpl implements DemoService { + private static Map context; + private boolean called; + + @POST + @Path("/say") + @Consumes({MediaType.TEXT_PLAIN}) + @Override + public String sayHello(String name) { + called = true; + return "Hello, " + name; + } + + @Override + public Long testFormBody(Long number) { + return number; + } + + public boolean isCalled() { + return called; + } + + @Override + public int primitiveInt(int a, int b) { + return a + b; + } + + @Override + public long primitiveLong(long a, Long b) { + return a + b; + } + + @Override + public long primitiveByte(byte a, Long b) { + return a + b; + } + + @Override + public long primitiveShort(short a, Long b, int c) { + return a + b; + } + + @Override + public void request(DefaultFullHttpRequest defaultFullHttpRequest) {} + + @Override + public String testMapParam(Map params) { + return params.get("param"); + } + + @Override + public String testMapHeader(Map headers) { + return headers.get("header"); + } + + @Override + public List testMapForm(MultivaluedMap params) { + return params.get("form"); + } + + @Override + public String header(String header) { + return header; + } + + @Override + public int headerInt(int header) { + return header; + } + + @Override + public String noStringParam(String param) { + return param; + } + + @Override + public String noStringHeader(String header) { + return header; + } + + @POST + @Path("/noIntHeader") + @Consumes({MediaType.TEXT_PLAIN}) + @Override + public int noIntHeader(@HeaderParam("header") int header) { + return header; + } + + @POST + @Path("/noIntParam") + @Consumes({MediaType.TEXT_PLAIN}) + @Override + public int noIntParam(@QueryParam("header") int header) { + return header; + } + + @Override + public User noBodyArg(User user) { + return user; + } + + @GET + @Path("/hello") + @Override + public Integer hello(@QueryParam("a") Integer a, @QueryParam("b") Integer b) { + context = RpcContext.getServerAttachment().getObjectAttachments(); + return a + b; + } + + @GET + @Path("/error") + @Override + public String error() { + throw new RuntimeException("test error"); + } + + public static Map getAttachments() { + return context; + } + + @Override + public List list(List users) { + return users; + } + + @Override + public Set set(Set users) { + return users; + } + + @Override + public User[] array(User[] users) { + return users; + } + + @Override + public Map stringMap(Map userMap) { + return userMap; + } + + @Override + public Map userMap(Map userMap) { + return userMap; + } + + @Override + public User formBody(User user) { + user.setName("formBody"); + return user; + } +} 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 new file mode 100644 index 00000000000..9caa0b6a93f --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/JaxrsRestProtocolTest.java @@ -0,0 +1,563 @@ +/* + * 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; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.rpc.Exporter; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Protocol; +import org.apache.dubbo.rpc.ProxyFactory; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.RpcInvocation; +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.protocol.tri.rest.support.jaxrs.compatible.filter.TestContainerRequestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.filter.TraceFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.filter.TraceRequestAndResponseFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.intercept.DynamicTraceInterceptor; +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.AnotherUserRestService; +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.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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import org.hamcrest.CoreMatchers; +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.apache.dubbo.remoting.Constants.SERVER_KEY; +import static org.apache.dubbo.rpc.protocol.tri.rest.RestConstants.EXTENSION_KEY; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class JaxrsRestProtocolTest { + private final Protocol tProtocol = + ApplicationModel.defaultModel().getExtensionLoader(Protocol.class).getExtension("tri"); + private final Protocol protocol = + ApplicationModel.defaultModel().getExtensionLoader(Protocol.class).getExtension("rest"); + private final ProxyFactory proxy = ApplicationModel.defaultModel() + .getExtensionLoader(ProxyFactory.class) + .getAdaptiveExtension(); + + private final int availablePort = NetUtils.getAvailablePort(); + private final URL exportUrl = + URL.valueOf("tri://127.0.0.1:" + availablePort + "/rest?interface=" + DemoService.class.getName()); + + private static final String SERVER = "netty4"; + + private final ModuleServiceRepository repository = + ApplicationModel.defaultModel().getDefaultModule().getServiceRepository(); + + @AfterEach + public void tearDown() { + tProtocol.destroy(); + protocol.destroy(); + FrameworkModel.destroyAll(); + } + + @Test + void testRestProtocol() { + URL url = URL.valueOf("tri://127.0.0.1:" + NetUtils.getAvailablePort() + "/?version=1.0.0&interface=" + + DemoService.class.getName()); + + DemoServiceImpl server = new DemoServiceImpl(); + + url = registerProvider(url, server, DemoService.class); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, url)); + + Invoker invoker = protocol.refer(DemoService.class, url); + Assertions.assertFalse(server.isCalled()); + + DemoService client = proxy.getProxy(invoker); + String result = client.sayHello("haha"); + Assertions.assertTrue(server.isCalled()); + Assertions.assertEquals("Hello, haha", result); + + String header = client.header("header test"); + Assertions.assertEquals("header test", header); + + Assertions.assertEquals(1, client.headerInt(1)); + invoker.destroy(); + exporter.unexport(); + } + + @Test + void testAnotherUserRestProtocolByDifferentRestClient() { + testAnotherUserRestProtocol(org.apache.dubbo.remoting.Constants.OK_HTTP); + testAnotherUserRestProtocol(org.apache.dubbo.remoting.Constants.APACHE_HTTP_CLIENT); + testAnotherUserRestProtocol(org.apache.dubbo.remoting.Constants.URL_CONNECTION); + } + + void testAnotherUserRestProtocol(String restClient) { + URL url = URL.valueOf("tri://127.0.0.1:" + NetUtils.getAvailablePort() + "/?version=1.0.0&interface=" + + AnotherUserRestService.class.getName() + "&" + org.apache.dubbo.remoting.Constants.CLIENT_KEY + "=" + + restClient); + + AnotherUserRestServiceImpl server = new AnotherUserRestServiceImpl(); + + url = this.registerProvider(url, server, AnotherUserRestService.class); + + Exporter exporter = + tProtocol.export(proxy.getInvoker(server, AnotherUserRestService.class, url)); + Invoker invoker = protocol.refer(AnotherUserRestService.class, url); + + AnotherUserRestService client = proxy.getProxy(invoker); + User result = client.getUser(123l); + + Assertions.assertEquals(123l, result.getId()); + + result.setName("dubbo"); + Assertions.assertEquals(123l, client.registerUser(result).getId()); + + Assertions.assertEquals("context", client.getContext()); + + byte[] bytes = {1, 2, 3, 4}; + Assertions.assertTrue(Arrays.equals(bytes, client.bytes(bytes))); + + Assertions.assertEquals(1l, client.number(1l)); + + HashMap map = new HashMap<>(); + map.put("headers", "h1"); + Assertions.assertEquals("h1", client.headerMap(map)); + Assertions.assertEquals(null, client.headerMap(null)); + + invoker.destroy(); + exporter.unexport(); + } + + @Test + void testRestProtocolWithContextPath() { + DemoServiceImpl server = new DemoServiceImpl(); + Assertions.assertFalse(server.isCalled()); + int port = NetUtils.getAvailablePort(); + URL url = URL.valueOf( + "tri://127.0.0.1:" + port + "/a/b/c?version=1.0.0&interface=" + DemoService.class.getName()); + + url = this.registerProvider(url, server, DemoService.class); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, url)); + + url = URL.valueOf( + "rest://127.0.0.1:" + port + "/a/b/c/?version=1.0.0&interface=" + DemoService.class.getName()); + Invoker invoker = protocol.refer(DemoService.class, url); + DemoService client = proxy.getProxy(invoker); + String result = client.sayHello("haha"); + Assertions.assertTrue(server.isCalled()); + Assertions.assertEquals("Hello, haha", result); + invoker.destroy(); + exporter.unexport(); + } + + @Test + void testExport() { + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + RpcContext.getClientAttachment().setAttachment("timeout", "20000"); + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, url)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, url)); + + Integer echoString = demoService.hello(1, 2); + assertThat(echoString, is(3)); + + exporter.unexport(); + } + + @Test + void testNettyServer() { + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + URL nettyUrl = url.addParameter(SERVER_KEY, SERVER); + Exporter exporter = + tProtocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Integer echoString = demoService.hello(10, 10); + assertThat(echoString, is(20)); + + exporter.unexport(); + } + + @Test + void testInvoke() { + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, url)); + + RpcInvocation rpcInvocation = new RpcInvocation( + "hello", DemoService.class.getName(), "", new Class[] {Integer.class, Integer.class}, new Integer[] { + 2, 3 + }); + rpcInvocation.setTargetServiceUniqueName(url.getServiceKey()); + + Result result = exporter.getInvoker().invoke(rpcInvocation); + assertThat(result.getValue(), CoreMatchers.is(5)); + } + + @Test + void testDefaultPort() { + assertThat(protocol.getDefaultPort(), is(80)); + } + + @Test + void testRestExceptionMapper() { + + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + URL exceptionUrl = url.addParameter(EXTENSION_KEY, ResteasyExceptionMapper.class.getName()); + + tProtocol.export(proxy.getInvoker(server, DemoService.class, exceptionUrl)); + + DemoService referDemoService = this.proxy.getProxy(protocol.refer(DemoService.class, exceptionUrl)); + + Assertions.assertEquals("test-exception", referDemoService.error()); + } + + @Test + void testFormConsumerParser() { + DemoService server = new DemoServiceImpl(); + URL nettyUrl = this.registerProvider(exportUrl, server, DemoService.class); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Long number = demoService.testFormBody(18l); + Assertions.assertEquals(18l, number); + + exporter.unexport(); + } + + @Test + void test404() { + Assertions.assertThrows(RpcException.class, () -> { + DemoService server = new DemoServiceImpl(); + URL nettyUrl = this.registerProvider(exportUrl, server, DemoService.class); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + URL referUrl = URL.valueOf( + "tri://127.0.0.1:" + availablePort + "/rest?interface=" + RestDemoForTestException.class.getName()); + + RestDemoForTestException restDemoForTestException = + this.proxy.getProxy(protocol.refer(RestDemoForTestException.class, referUrl)); + + restDemoForTestException.test404(); + + exporter.unexport(); + }); + } + + @Test + void test400() { + Assertions.assertThrows(RpcException.class, () -> { + DemoService server = new DemoServiceImpl(); + URL nettyUrl = this.registerProvider(exportUrl, server, DemoService.class); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + URL referUrl = URL.valueOf( + "tri://127.0.0.1:" + availablePort + "/rest?interface=" + RestDemoForTestException.class.getName()); + + RestDemoForTestException restDemoForTestException = + this.proxy.getProxy(protocol.refer(RestDemoForTestException.class, referUrl)); + + restDemoForTestException.test400("abc", "edf"); + + exporter.unexport(); + }); + } + + @Test + void testPrimitive() { + DemoService server = new DemoServiceImpl(); + + URL nettyUrl = this.registerProvider(exportUrl, server, DemoService.class); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Integer result = demoService.primitiveInt(1, 2); + Long resultLong = demoService.primitiveLong(1, 2l); + long resultByte = demoService.primitiveByte((byte) 1, 2l); + long resultShort = demoService.primitiveShort((short) 1, 2l, 1); + + assertThat(result, is(3)); + assertThat(resultShort, is(3l)); + assertThat(resultLong, is(3l)); + assertThat(resultByte, is(3l)); + + exporter.unexport(); + } + + @Test + void testMapParam() { + DemoService server = new DemoServiceImpl(); + + URL nettyUrl = this.registerProvider(exportUrl, server, DemoService.class); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Map params = new HashMap<>(); + params.put("param", "P1"); + ; + + Map headers = new HashMap<>(); + headers.put("header", "H1"); + + Assertions.assertEquals("P1", demoService.testMapParam(params)); + Assertions.assertEquals("H1", demoService.testMapHeader(headers)); + + MultivaluedMapImpl forms = new MultivaluedMapImpl<>(); + forms.put("form", Arrays.asList("F1")); + + Assertions.assertEquals(Arrays.asList("F1"), demoService.testMapForm(forms)); + exporter.unexport(); + } + + @Test + void testNoArgParam() { + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + URL nettyUrl = url.addParameter(SERVER_KEY, SERVER); + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Assertions.assertEquals(null, demoService.noStringHeader(null)); + Assertions.assertEquals(null, demoService.noStringParam(null)); + /* Assertions.assertThrows(RpcException.class, () -> { + demoService.noIntHeader(1); + }); + + Assertions.assertThrows(RpcException.class, () -> { + demoService.noIntParam(1); + });*/ + + Assertions.assertEquals(null, demoService.noBodyArg(null)); + exporter.unexport(); + } + + @Test + void testHttpMethods() { + testHttpMethod(org.apache.dubbo.remoting.Constants.OK_HTTP); + testHttpMethod(org.apache.dubbo.remoting.Constants.APACHE_HTTP_CLIENT); + testHttpMethod(org.apache.dubbo.remoting.Constants.URL_CONNECTION); + } + + void testHttpMethod(String restClient) { + HttpMethodService server = new HttpMethodServiceImpl(); + + URL url = URL.valueOf("tri://127.0.0.1:" + NetUtils.getAvailablePort() + "/?version=1.0.0&interface=" + + HttpMethodService.class.getName() + "&" + + org.apache.dubbo.remoting.Constants.CLIENT_KEY + "=" + restClient); + url = this.registerProvider(url, server, HttpMethodService.class); + Exporter exporter = tProtocol.export(proxy.getInvoker(server, HttpMethodService.class, url)); + + HttpMethodService demoService = this.proxy.getProxy(protocol.refer(HttpMethodService.class, url)); + + String expect = "hello"; + Assertions.assertEquals(null, demoService.sayHelloHead()); + Assertions.assertEquals(expect, demoService.sayHelloDelete("hello")); + Assertions.assertEquals(expect, demoService.sayHelloGet("hello")); + Assertions.assertEquals(expect, demoService.sayHelloOptions("hello")); + // Assertions.assertEquals(expect, demoService.sayHelloPatch("hello")); + Assertions.assertEquals(expect, demoService.sayHelloPost("hello")); + Assertions.assertEquals(expect, demoService.sayHelloPut("hello")); + exporter.unexport(); + } + + @Test + void test405() { + int availablePort = NetUtils.getAvailablePort(); + URL url = URL.valueOf("tri://127.0.0.1:" + availablePort + + "/?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.rest.RestDemoService&"); + + RestDemoServiceImpl server = new RestDemoServiceImpl(); + + url = this.registerProvider(url, server, RestDemoService.class); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, RestDemoService.class, url)); + + URL consumer = URL.valueOf("rest://127.0.0.1:" + availablePort + "/?version=1.0.0&interface=" + + RestDemoForTestException.class.getName()); + + consumer = this.registerProvider(consumer, server, RestDemoForTestException.class); + + Invoker invoker = protocol.refer(RestDemoForTestException.class, consumer); + + RestDemoForTestException client = proxy.getProxy(invoker); + + Assertions.assertThrows(RpcException.class, () -> { + client.testMethodDisallowed("aaa"); + }); + + invoker.destroy(); + exporter.unexport(); + } + + @Test + void testContainerRequestFilter() { + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + URL nettyUrl = url.addParameter(EXTENSION_KEY, TestContainerRequestFilter.class.getName()); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Assertions.assertEquals("return-success", demoService.sayHello("hello")); + exporter.unexport(); + } + + @Test + void testIntercept() { + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + URL nettyUrl = url.addParameter(EXTENSION_KEY, DynamicTraceInterceptor.class.getName()); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Assertions.assertEquals("intercept", demoService.sayHello("hello")); + exporter.unexport(); + } + + @Test + void testResponseFilter() { + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + URL nettyUrl = url.addParameter(EXTENSION_KEY, TraceFilter.class.getName()); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Assertions.assertEquals("response-filter", demoService.sayHello("hello")); + exporter.unexport(); + } + + @Test + void testCollectionResult() { + DemoService server = new DemoServiceImpl(); + + URL nettyUrl = this.registerProvider(exportUrl, server, DemoService.class); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Assertions.assertEquals( + User.getInstance(), + demoService.list(Arrays.asList(User.getInstance())).get(0)); + + HashSet objects = new HashSet<>(); + objects.add(User.getInstance()); + Assertions.assertEquals(User.getInstance(), new ArrayList<>(demoService.set(objects)).get(0)); + + Assertions.assertEquals(User.getInstance(), demoService.array(objects.toArray(new User[0]))[0]); + + Map map = new HashMap<>(); + map.put("map", User.getInstance()); + Assertions.assertEquals(User.getInstance(), demoService.stringMap(map).get("map")); + + // Map maps = new HashMap<>(); + // maps.put(User.getInstance(), User.getInstance()); + // Assertions.assertEquals(User.getInstance(), demoService.userMap(maps).get(User.getInstance())); + exporter.unexport(); + } + + @Test + void testRequestAndResponseFilter() { + DemoService server = new DemoServiceImpl(); + + URL exportUrl = URL.valueOf("tri://127.0.0.1:" + availablePort + "/rest?interface=" + + DemoService.class.getName() + "&extension=" + TraceRequestAndResponseFilter.class.getName()); + + URL nettyUrl = this.registerProvider(exportUrl, server, DemoService.class); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Assertions.assertEquals("header-result", demoService.sayHello("hello")); + exporter.unexport(); + } + + @Test + void testFormBody() { + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + URL nettyUrl = url.addParameter(SERVER_KEY, SERVER); + + Exporter exporter = tProtocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + User user = demoService.formBody(User.getInstance()); + + Assertions.assertEquals("formBody", user.getName()); + exporter.unexport(); + } + + private URL registerProvider(URL url, Object impl, Class interfaceClass) { + ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass); + ProviderModel providerModel = new ProviderModel(url.getServiceKey(), impl, serviceDescriptor, null, null); + repository.registerProvider(providerModel); + return url.setServiceModel(providerModel); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/ResteasyExceptionMapper.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/ResteasyExceptionMapper.java new file mode 100644 index 00000000000..fafcccbf0ca --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/ResteasyExceptionMapper.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.jaxrs.compatible; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; + +public class ResteasyExceptionMapper implements ExceptionMapper { + @Override + public Response toResponse(RuntimeException exception) { + return Response.status(200).entity("test-exception").build(); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/User.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/User.java new file mode 100644 index 00000000000..1e76a2b17e5 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/User.java @@ -0,0 +1,88 @@ +/* + * 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; + +import java.io.Serializable; +import java.util.Objects; + +/** + * User Entity + * + * @since 2.7.6 + */ +public class User implements Serializable { + + private Long id; + + private String name; + + private Integer age; + + 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 Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public static User getInstance() { + User user = new User(); + user.setAge(18); + user.setName("dubbo"); + user.setId(404l); + return user; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + User user = (User) o; + return Objects.equals(id, user.id) && Objects.equals(name, user.name) && Objects.equals(age, user.age); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, age); + } + + @Override + public String toString() { + return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TestContainerRequestFilter.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TestContainerRequestFilter.java new file mode 100644 index 00000000000..ab38be82315 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TestContainerRequestFilter.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.compatible.filter; + +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.Response; + +import java.io.IOException; + +@Priority(Priorities.USER) +public class TestContainerRequestFilter implements ContainerRequestFilter { + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + + requestContext.abortWith(Response.status(200).entity("return-success").build()); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TraceFilter.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TraceFilter.java new file mode 100644 index 00000000000..a8cc72a3bde --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TraceFilter.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.jaxrs.compatible.filter; + +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; + +import java.io.IOException; + +@Priority(Priorities.USER) +public class TraceFilter implements ContainerRequestFilter, ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + System.out.println( + "Request filter invoked: " + requestContext.getUriInfo().getAbsolutePath()); + } + + @Override + public void filter( + ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) + throws IOException { + containerResponseContext.setEntity("response-filter"); + System.out.println("Response filter invoked."); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TraceRequestAndResponseFilter.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TraceRequestAndResponseFilter.java new file mode 100644 index 00000000000..fc4e6159624 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/filter/TraceRequestAndResponseFilter.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.rpc.protocol.tri.rest.support.jaxrs.compatible.filter; + +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; + +import java.io.IOException; + +@Priority(Priorities.USER) +public class TraceRequestAndResponseFilter implements ContainerRequestFilter, ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + + requestContext.getHeaders().add("test-response", "header-result"); + } + + @Override + public void filter( + ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) + throws IOException { + + String headerString = containerRequestContext.getHeaderString("test-response"); + containerResponseContext.setEntity(headerString); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/intercept/DynamicTraceInterceptor.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/intercept/DynamicTraceInterceptor.java new file mode 100644 index 00000000000..826cad76df1 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/intercept/DynamicTraceInterceptor.java @@ -0,0 +1,49 @@ +/* + * 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.intercept; + +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.ext.ReaderInterceptor; +import javax.ws.rs.ext.ReaderInterceptorContext; +import javax.ws.rs.ext.WriterInterceptor; +import javax.ws.rs.ext.WriterInterceptorContext; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +@Priority(Priorities.USER) +public class DynamicTraceInterceptor implements ReaderInterceptor, WriterInterceptor { + + public DynamicTraceInterceptor() {} + + @Override + public Object aroundReadFrom(ReaderInterceptorContext readerInterceptorContext) + throws IOException, WebApplicationException { + System.out.println("Dynamic reader interceptor invoked"); + return readerInterceptorContext.proceed(); + } + + @Override + public void aroundWriteTo(WriterInterceptorContext writerInterceptorContext) + throws IOException, WebApplicationException { + System.out.println("Dynamic writer interceptor invoked"); + writerInterceptorContext.getOutputStream().write("intercept".getBytes(StandardCharsets.UTF_8)); + writerInterceptorContext.proceed(); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/AnotherUserRestService.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/AnotherUserRestService.java new file mode 100644 index 00000000000..3ab2a2c392d --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/AnotherUserRestService.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.support.jaxrs.compatible.rest; + +import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.User; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import java.util.Map; + +@Path("u") +@Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML}) +public interface AnotherUserRestService { + + @GET + @Path("{id : \\d+}") + @Produces({MediaType.APPLICATION_JSON}) + User getUser(@PathParam("id") Long id); + + @POST + @Path("register") + @Produces("text/xml; charset=UTF-8") + RegistrationResult registerUser(User user); + + @GET + @Path("context") + @Produces({MediaType.TEXT_PLAIN}) + String getContext(); + + @POST + @Path("bytes") + @Produces({MediaType.APPLICATION_JSON}) + byte[] bytes(byte[] bytes); + + @POST + @Path("number") + @Produces({MediaType.APPLICATION_JSON}) + Long number(Long number); + + @POST + @Path("headerMap") + @Produces({MediaType.TEXT_PLAIN}) + String headerMap(@HeaderParam("headers") Map headers); +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/AnotherUserRestServiceImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/AnotherUserRestServiceImpl.java new file mode 100644 index 00000000000..0c0cb67f3e6 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/AnotherUserRestServiceImpl.java @@ -0,0 +1,58 @@ +/* + * 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 java.util.Map; + +public class AnotherUserRestServiceImpl implements AnotherUserRestService { + + @Override + public User getUser(Long id) { + + User user = new User(); + user.setId(id); + return user; + } + + @Override + public RegistrationResult registerUser(User user) { + return new RegistrationResult(user.getId()); + } + + @Override + public String getContext() { + + return "context"; + } + + @Override + public byte[] bytes(byte[] bytes) { + return bytes; + } + + @Override + public Long number(Long number) { + return number; + } + + @Override + public String headerMap(Map headers) { + return headers.get("headers"); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/HttpMethodService.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/HttpMethodService.java new file mode 100644 index 00000000000..144e2f95dda --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/HttpMethodService.java @@ -0,0 +1,67 @@ +/* + * 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 javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.OPTIONS; +import javax.ws.rs.PATCH; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; + +@Path("/demoService") +public interface HttpMethodService { + + @POST + @Path("/sayPost") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + String sayHelloPost(@QueryParam("name") String name); + + @DELETE + @Path("/sayDelete") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + String sayHelloDelete(@QueryParam("name") String name); + + @HEAD + @Path("/sayHead") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + String sayHelloHead(); + + @GET + @Path("/sayGet") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + String sayHelloGet(@QueryParam("name") String name); + + @PUT + @Path("/sayPut") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + String sayHelloPut(@QueryParam("name") String name); + + @PATCH + @Path("/sayPatch") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + String sayHelloPatch(@QueryParam("name") String name); + + @OPTIONS + @Path("/sayOptions") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + String sayHelloOptions(@QueryParam("name") String name); +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/HttpMethodServiceImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/HttpMethodServiceImpl.java new file mode 100644 index 00000000000..fcbdc9c334a --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/HttpMethodServiceImpl.java @@ -0,0 +1,55 @@ +/* + * 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; + +public class HttpMethodServiceImpl implements HttpMethodService { + + @Override + public String sayHelloPost(String name) { + return name; + } + + @Override + public String sayHelloDelete(String name) { + return name; + } + + @Override + public String sayHelloHead() { + return "hello"; + } + + @Override + public String sayHelloGet(String name) { + return name; + } + + @Override + public String sayHelloPut(String name) { + return name; + } + + @Override + public String sayHelloPatch(String name) { + return name; + } + + @Override + public String sayHelloOptions(String name) { + return name; + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RegistrationResult.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RegistrationResult.java new file mode 100644 index 00000000000..67dca9380f2 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RegistrationResult.java @@ -0,0 +1,62 @@ +/* + * 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 javax.xml.bind.annotation.XmlRootElement; + +import java.io.Serializable; +import java.util.Objects; + +/** + * DTO to customize the returned message + */ +@XmlRootElement +public class RegistrationResult implements Serializable { + + private Long id; + + public RegistrationResult() {} + + public RegistrationResult(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RegistrationResult that = (RegistrationResult) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoForTestException.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoForTestException.java new file mode 100644 index 00000000000..ea23b151fe4 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoForTestException.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.jaxrs.compatible.rest; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +@Path("/demoService") +public interface RestDemoForTestException { + + @POST + @Path("/noFound") + @Produces(MediaType.TEXT_PLAIN) + String test404(); + + @GET + @Consumes({MediaType.TEXT_PLAIN}) + @Path("/hello") + Integer test400(@QueryParam("a") String a, @QueryParam("b") String b); + + @POST + @Path("{uid}") + String testMethodDisallowed(@PathParam("uid") String uid); +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoService.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoService.java new file mode 100644 index 00000000000..1b551ba00e0 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoService.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.support.jaxrs.compatible.rest; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Path("/demoService") +public interface RestDemoService { + @GET + @Path("/hello") + Integer hello(@QueryParam("a") Integer a, @QueryParam("b") Integer b); + + @GET + @Path("/findUserById") + Response findUserById(@QueryParam("id") Integer id); + + @GET + @Path("/error") + String error(); + + @POST + @Path("/say") + @Consumes({MediaType.TEXT_PLAIN}) + String sayHello(String name); + + @POST + @Path("number") + @Produces({MediaType.APPLICATION_FORM_URLENCODED}) + @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) + Long testFormBody(@FormParam("number") Long number); + + boolean isCalled(); + + @DELETE + @Path("{uid}") + String deleteUserByUid(@PathParam("uid") String uid); + + @DELETE + @Path("/deleteUserById/{uid}") + public Response deleteUserById(@PathParam("uid") String uid); +} diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoServiceImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoServiceImpl.java new file mode 100644 index 00000000000..e72005833d7 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/RestDemoServiceImpl.java @@ -0,0 +1,80 @@ +/* + * 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.RpcContext; + +import javax.ws.rs.core.Response; + +import java.util.HashMap; +import java.util.Map; + +import org.jboss.resteasy.specimpl.BuiltResponse; + +public class RestDemoServiceImpl implements RestDemoService { + private static Map context; + private boolean called; + + @Override + public String sayHello(String name) { + called = true; + return "Hello, " + name; + } + + @Override + public Long testFormBody(Long number) { + return number; + } + + public boolean isCalled() { + return called; + } + + @Override + public String deleteUserByUid(String uid) { + return uid; + } + + @Override + public Integer hello(Integer a, Integer b) { + context = RpcContext.getServerAttachment().getObjectAttachments(); + return a + b; + } + + @Override + public Response findUserById(Integer id) { + Map content = new HashMap<>(); + content.put("username", "jack"); + content.put("id", id); + + return BuiltResponse.ok(content).build(); + } + + @Override + public String error() { + throw new RuntimeException(); + } + + @Override + public Response deleteUserById(String uid) { + return Response.status(300).entity("deleted").build(); + } + + public static Map getAttachments() { + return context; + } +} diff --git a/dubbo-plugin/dubbo-plugin-loom/src/test/resources/log4j2-test.xml b/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/log4j2-test.xml similarity index 93% rename from dubbo-plugin/dubbo-plugin-loom/src/test/resources/log4j2-test.xml rename to dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/log4j2-test.xml index ba99f52cc2d..34d1c53e88a 100644 --- a/dubbo-plugin/dubbo-plugin-loom/src/test/resources/log4j2-test.xml +++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/log4j2-test.xml @@ -22,6 +22,7 @@ + diff --git a/dubbo-plugin/dubbo-rest-servlet/pom.xml b/dubbo-plugin/dubbo-rest-servlet/pom.xml new file mode 100644 index 00000000000..c4efd467daf --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/pom.xml @@ -0,0 +1,46 @@ + + + + 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/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java b/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java new file mode 100644 index 00000000000..58820c00a4e --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java @@ -0,0 +1,67 @@ +/* + * 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.servlet; + +import org.apache.dubbo.common.config.Configuration; +import org.apache.dubbo.common.config.ConfigurationUtils; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; + +import java.util.Collections; +import java.util.Enumeration; + +final class DummyFilterConfig implements FilterConfig { + + private final String filterName; + private final FrameworkModel frameworkModel; + private final ServletContext servletContext; + + public DummyFilterConfig(String filterName, FrameworkModel frameworkModel, ServletContext servletContext) { + this.filterName = filterName; + this.frameworkModel = frameworkModel; + this.servletContext = servletContext; + } + + @Override + public String getFilterName() { + return filterName; + } + + @Override + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public String getInitParameter(String name) { + String prefix = RestConstants.CONFIG_PREFIX + "filter-config."; + Configuration conf = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); + String value = conf.getString(prefix + filterName + "." + name); + if (value == null) { + value = conf.getString(prefix + name); + } + return value; + } + + @Override + public Enumeration getInitParameterNames() { + return Collections.emptyEnumeration(); + } +} 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-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java new file mode 100644 index 00000000000..3bafcd4367b --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java @@ -0,0 +1,318 @@ +/* + * 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.servlet; + +import org.apache.dubbo.common.config.Configuration; +import org.apache.dubbo.common.config.ConfigurationUtils; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; + +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletRegistration; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.descriptor.JspConfigDescriptor; + +import java.io.InputStream; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +final class DummyServletContext implements ServletContext { + + private final FrameworkModel frameworkModel; + private final Map attributes = new HashMap<>(); + private final Map initParameters = new HashMap<>(); + + public DummyServletContext(FrameworkModel frameworkModel) { + this.frameworkModel = frameworkModel; + } + + @Override + public String getContextPath() { + return "/"; + } + + @Override + public ServletContext getContext(String uripath) { + return this; + } + + @Override + public int getMajorVersion() { + return 3; + } + + @Override + public int getMinorVersion() { + return 1; + } + + @Override + public int getEffectiveMajorVersion() { + return 3; + } + + @Override + public int getEffectiveMinorVersion() { + return 1; + } + + @Override + public String getMimeType(String file) { + return null; + } + + @Override + public Set getResourcePaths(String path) { + return null; + } + + @Override + public URL getResource(String path) { + return null; + } + + @Override + public InputStream getResourceAsStream(String path) { + return null; + } + + @Override + public RequestDispatcher getRequestDispatcher(String path) { + return null; + } + + @Override + 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(); + } + + @Override + 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); + } + + @Override + public void log(String message, Throwable throwable) { + LoggerFactory.getLogger(DummyServletContext.class).info(message, throwable); + } + + @Override + public String getRealPath(String path) { + throw new UnsupportedOperationException(); + } + + @Override + public String getServerInfo() { + return "Dubbo Rest Server/1.0"; + } + + @Override + public String getInitParameter(String name) { + String value = initParameters.get(name); + if (value != null) { + return value; + } + Configuration conf = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); + return conf.getString(RestConstants.CONFIG_PREFIX + "servlet-context." + name); + } + + @Override + public Enumeration getInitParameterNames() { + return Collections.enumeration(initParameters.keySet()); + } + + @Override + public boolean setInitParameter(String name, String value) { + return initParameters.putIfAbsent(name, value) == null; + } + + @Override + public Object getAttribute(String name) { + return attributes.get(name); + } + + @Override + public Enumeration getAttributeNames() { + return Collections.enumeration(attributes.keySet()); + } + + @Override + public void setAttribute(String name, Object object) { + attributes.put(name, object); + } + + @Override + public void removeAttribute(String name) { + attributes.remove(name); + } + + @Override + public String getServletContextName() { + return ""; + } + + @Override + public ServletRegistration.Dynamic addServlet(String servletName, String className) { + throw new UnsupportedOperationException(); + } + + @Override + public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) { + throw new UnsupportedOperationException(); + } + + @Override + public ServletRegistration.Dynamic addServlet(String servletName, Class servletClass) { + throw new UnsupportedOperationException(); + } + + @Override + public T createServlet(Class clazz) { + throw new UnsupportedOperationException(); + } + + @Override + public ServletRegistration getServletRegistration(String servletName) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getServletRegistrations() { + throw new UnsupportedOperationException(); + } + + @Override + public FilterRegistration.Dynamic addFilter(String filterName, String className) { + throw new UnsupportedOperationException(); + } + + @Override + public FilterRegistration.Dynamic addFilter(String filterName, Filter filter) { + throw new UnsupportedOperationException(); + } + + @Override + public FilterRegistration.Dynamic addFilter(String filterName, Class filterClass) { + throw new UnsupportedOperationException(); + } + + @Override + public T createFilter(Class clazz) { + throw new UnsupportedOperationException(); + } + + @Override + public FilterRegistration getFilterRegistration(String filterName) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getFilterRegistrations() { + throw new UnsupportedOperationException(); + } + + @Override + public SessionCookieConfig getSessionCookieConfig() { + throw new UnsupportedOperationException(); + } + + @Override + public void setSessionTrackingModes(Set sessionTrackingModes) { + throw new UnsupportedOperationException(); + } + + @Override + public Set getDefaultSessionTrackingModes() { + throw new UnsupportedOperationException(); + } + + @Override + public Set getEffectiveSessionTrackingModes() { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(String className) { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(T t) { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(Class listenerClass) { + throw new UnsupportedOperationException(); + } + + @Override + public T createListener(Class clazz) { + throw new UnsupportedOperationException(); + } + + @Override + public JspConfigDescriptor getJspConfigDescriptor() { + throw new UnsupportedOperationException(); + } + + @Override + public ClassLoader getClassLoader() { + return getClass().getClassLoader(); + } + + @Override + public void declareRoles(String... roleNames) { + throw new UnsupportedOperationException(); + } + + @Override + public String getVirtualServerName() { + 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-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java new file mode 100644 index 00000000000..f4a4bc46b02 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.support.servlet; + +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.HttpMessageAdapterFactory; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.RestException; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestUtils; + +import javax.servlet.Filter; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import java.io.IOException; +import java.util.Arrays; + +@Activate(onClass = "javax.servlet.Filter") +public final class FilterAdapter implements RestExtensionAdapter { + + private final ServletHttpMessageAdapterFactory adapterFactory; + + public FilterAdapter(FrameworkModel frameworkModel) { + adapterFactory = (ServletHttpMessageAdapterFactory) + frameworkModel.getExtension(HttpMessageAdapterFactory.class, "servlet"); + } + + @Override + public boolean accept(Object extension) { + return extension instanceof Filter; + } + + @Override + public RestFilter adapt(Filter extension) { + try { + String filterName = extension.getClass().getSimpleName(); + extension.init(adapterFactory.adaptFilterConfig(filterName)); + } catch (ServletException e) { + throw new RestException(e); + } + return new FilterRestFilter(extension); + } + + private static final class FilterRestFilter implements RestFilter { + + private final Filter filter; + + @Override + public int getPriority() { + return RestUtils.getPriority(filter); + } + + @Override + public String[] getPatterns() { + return RestUtils.getPattens(filter); + } + + public FilterRestFilter(Filter filter) { + this.filter = filter; + } + + @Override + public void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) throws Exception { + filter.doFilter((ServletRequest) request, (ServletResponse) response, (q, p) -> { + try { + chain.doFilter(request, response); + } catch (RuntimeException | IOException | ServletException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } + }); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("RestFilter{filter="); + sb.append(filter); + int priority = getPriority(); + if (priority != 0) { + sb.append(", priority=").append(priority); + } + String[] patterns = getPatterns(); + if (patterns != null) { + sb.append(", patterns=").append(Arrays.toString(patterns)); + } + return sb.append('}').toString(); + } + } +} 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-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java new file mode 100644 index 00000000000..b7c6c218e02 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java @@ -0,0 +1,145 @@ +/* + * 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.servlet; + +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.FileUpload; +import org.apache.dubbo.rpc.protocol.tri.rest.RestException; + +import javax.servlet.http.Part; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +final class Helper { + + private Helper() {} + + static javax.servlet.http.Cookie[] convertCookies(Collection hCookies) { + javax.servlet.http.Cookie[] cookies = new javax.servlet.http.Cookie[hCookies.size()]; + int i = 0; + for (HttpCookie cookie : hCookies) { + cookies[i++] = convert(cookie); + } + return cookies; + } + + static javax.servlet.http.Cookie convert(HttpCookie hCookie) { + javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(hCookie.name(), hCookie.value()); + if (hCookie.domain() != null) { + cookie.setDomain(hCookie.domain()); + } + cookie.setMaxAge((int) hCookie.maxAge()); + cookie.setHttpOnly(hCookie.httpOnly()); + cookie.setPath(hCookie.path()); + cookie.setSecure(hCookie.secure()); + return cookie; + } + + static HttpCookie convert(javax.servlet.http.Cookie sCookie) { + HttpCookie cookie = new HttpCookie(sCookie.getName(), sCookie.getValue()); + cookie.setDomain(sCookie.getDomain()); + cookie.setMaxAge(sCookie.getMaxAge()); + cookie.setHttpOnly(sCookie.isHttpOnly()); + cookie.setPath(sCookie.getPath()); + cookie.setSecure(sCookie.getSecure()); + return cookie; + } + + public static Part convert(FileUpload part) { + return new FileUploadPart(part); + } + + public static Collection convertParts(Collection parts) { + if (CollectionUtils.isEmpty(parts)) { + return Collections.emptyList(); + } + List result = new ArrayList<>(parts.size()); + for (FileUpload part : parts) { + result.add(convert(part)); + } + return result; + } + + public static final class FileUploadPart implements Part { + + private final FileUpload fileUpload; + + public FileUploadPart(FileUpload fileUpload) { + this.fileUpload = fileUpload; + } + + @Override + public InputStream getInputStream() { + return fileUpload.inputStream(); + } + + @Override + public String getContentType() { + return fileUpload.contentType(); + } + + @Override + public String getName() { + return fileUpload.name(); + } + + @Override + public String getSubmittedFileName() { + return fileUpload.filename(); + } + + @Override + public long getSize() { + return fileUpload.size(); + } + + @Override + public void write(String fileName) { + try (FileOutputStream fos = new FileOutputStream(fileName)) { + StreamUtils.copy(fileUpload.inputStream(), fos); + } catch (IOException e) { + throw new RestException(e); + } + } + + @Override + public void delete() {} + + @Override + public String getHeader(String name) { + return null; + } + + @Override + public Collection getHeaders(String name) { + return null; + } + + @Override + public Collection getHeaderNames() { + return Collections.emptyList(); + } + } +} 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-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.java new file mode 100644 index 00000000000..408b3db4e5c --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.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.servlet; + +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +public interface HttpSessionFactory extends RestExtension { + + HttpSession getSession(HttpServletRequest request, boolean create); +} 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-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java new file mode 100644 index 00000000000..a1703c0599e --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java @@ -0,0 +1,95 @@ +/* + * 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.servlet; + +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.RestException; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.util.HashSet; +import java.util.Set; + +@Activate(onClass = "javax.servlet.http.HttpServletRequest") +public class ServletArgumentResolver implements ArgumentResolver { + + private static final Set> SUPPORTED_TYPES = new HashSet<>(); + + static { + SUPPORTED_TYPES.add(ServletRequest.class); + SUPPORTED_TYPES.add(HttpServletRequest.class); + SUPPORTED_TYPES.add(ServletResponse.class); + SUPPORTED_TYPES.add(HttpServletResponse.class); + SUPPORTED_TYPES.add(HttpSession.class); + SUPPORTED_TYPES.add(Cookie.class); + SUPPORTED_TYPES.add(Cookie[].class); + SUPPORTED_TYPES.add(Reader.class); + SUPPORTED_TYPES.add(Writer.class); + } + + @Override + public boolean accept(ParameterMeta parameter) { + return SUPPORTED_TYPES.contains(parameter.getActualType()); + } + + @Override + public Object resolve(ParameterMeta parameter, HttpRequest request, HttpResponse response) { + Class type = parameter.getActualType(); + if (type == ServletRequest.class || type == HttpServletRequest.class) { + return request; + } + if (type == ServletResponse.class || type == HttpServletResponse.class) { + return response; + } + if (type == HttpSession.class) { + return ((HttpServletRequest) request).getSession(); + } + if (type == Cookie.class) { + return Helper.convert(request.cookie(parameter.getRequiredName())); + } + if (type == Cookie[].class) { + return ((HttpServletRequest) request).getCookies(); + } + if (type == Reader.class) { + try { + return ((HttpServletRequest) request).getReader(); + } catch (IOException e) { + throw new RestException(e); + } + } + if (type == Writer.class) { + try { + return ((HttpServletResponse) response).getWriter(); + } catch (IOException e) { + throw new RestException(e); + } + } + return null; + } +} 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-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java new file mode 100644 index 00000000000..1f85d58b745 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java @@ -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.support.servlet; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.http12.HttpChannel; +import org.apache.dubbo.remoting.http12.HttpMetadata; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; + +@Activate(order = -100, onClass = "javax.servlet.http.HttpServletRequest") +public final class ServletHttpMessageAdapterFactory + implements HttpMessageAdapterFactory { + + private final FrameworkModel frameworkModel; + private final ServletContext servletContext; + private final HttpSessionFactory httpSessionFactory; + + public ServletHttpMessageAdapterFactory(FrameworkModel frameworkModel) { + this.frameworkModel = frameworkModel; + servletContext = new DummyServletContext(frameworkModel); + httpSessionFactory = getHttpSessionFactory(frameworkModel); + } + + private HttpSessionFactory getHttpSessionFactory(FrameworkModel frameworkModel) { + for (RestExtension extension : frameworkModel.getActivateExtensions(RestExtension.class)) { + if (extension instanceof HttpSessionFactory) { + return (HttpSessionFactory) extension; + } + } + return null; + } + + @Override + public ServletHttpRequestAdaptee adaptRequest(HttpMetadata rawRequest, HttpChannel channel) { + return new ServletHttpRequestAdaptee(rawRequest, channel, servletContext, httpSessionFactory); + } + + @Override + public HttpResponse adaptResponse(ServletHttpRequestAdaptee request, HttpMetadata rawRequest, Void rawResponse) { + return new ServletHttpResponseAdaptee(); + } + + public FilterConfig adaptFilterConfig(String filterName) { + return new DummyFilterConfig(filterName, frameworkModel, servletContext); + } +} 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-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdaptee.java new file mode 100644 index 00000000000..102ae8402b6 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdaptee.java @@ -0,0 +1,491 @@ +/* + * 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.servlet; + +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.remoting.http12.HttpChannel; +import org.apache.dubbo.remoting.http12.HttpMetadata; +import org.apache.dubbo.remoting.http12.message.DefaultHttpRequest; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.ReadListener; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.Principal; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class ServletHttpRequestAdaptee extends DefaultHttpRequest implements HttpServletRequest { + + private final ServletContext servletContext; + private final HttpSessionFactory sessionFactory; + + private ServletInputStream sis; + private BufferedReader reader; + + public ServletHttpRequestAdaptee( + HttpMetadata metadata, + HttpChannel channel, + ServletContext servletContext, + HttpSessionFactory sessionFactory) { + super(metadata, channel); + this.servletContext = servletContext; + this.sessionFactory = sessionFactory; + } + + @Override + public String getAuthType() { + return header("www-authenticate"); + } + + @Override + public Cookie[] getCookies() { + return Helper.convertCookies(cookies()); + } + + @Override + public long getDateHeader(String name) { + Date date = dateHeader(name); + return date == null ? -1L : date.getTime(); + } + + @Override + public String getHeader(String name) { + return header(name); + } + + @Override + public Enumeration getHeaders(String name) { + return Collections.enumeration(headerValues(name)); + } + + @Override + public Enumeration getHeaderNames() { + return Collections.enumeration(headerNames()); + } + + @Override + public int getIntHeader(String name) { + String headerValue = getHeader(name); + try { + return Integer.parseInt(headerValue); + } catch (NumberFormatException e) { + return -1; + } + } + + @Override + public String getMethod() { + return method(); + } + + @Override + public String getPathInfo() { + return null; + } + + @Override + public String getPathTranslated() { + return null; + } + + @Override + public String getContextPath() { + return "/"; + } + + @Override + public String getQueryString() { + return query(); + } + + @Override + public String getRemoteUser() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isUserInRole(String role) { + throw new UnsupportedOperationException(); + } + + @Override + public Principal getUserPrincipal() { + throw new UnsupportedOperationException(); + } + + @Override + public String getRequestedSessionId() { + throw new UnsupportedOperationException(); + } + + @Override + public String getRequestURI() { + return path(); + } + + @Override + public StringBuffer getRequestURL() { + StringBuffer url = new StringBuffer(32); + String scheme = getScheme(); + int port = getServerPort(); + url.append(scheme).append("://").append(getServerName()); + if (("http".equals(scheme) && port != 80) || ("https".equals(scheme) && port != 443)) { + url.append(':'); + url.append(port); + } + url.append(path()); + return url; + } + + @Override + public String getServletPath() { + return path(); + } + + @Override + public HttpSession getSession(boolean create) { + if (sessionFactory == null) { + throw new UnsupportedOperationException("No HttpSessionFactory found"); + } + return sessionFactory.getSession(this, create); + } + + @Override + public HttpSession getSession() { + return getSession(true); + } + + @Override + public String changeSessionId() { + return null; + } + + @Override + public boolean isRequestedSessionIdValid() { + return true; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + return true; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + return false; + } + + @Override + public boolean authenticate(HttpServletResponse response) { + throw new UnsupportedOperationException(); + } + + @Override + public void login(String username, String password) { + throw new UnsupportedOperationException(); + } + + @Override + public void logout() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getParts() { + return Helper.convertParts(parts()); + } + + @Override + public Part getPart(String name) { + return Helper.convert(part(name)); + } + + @Override + public T upgrade(Class handlerClass) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getAttribute(String name) { + return attribute(name); + } + + @Override + public Enumeration getAttributeNames() { + return Collections.enumeration(attributeNames()); + } + + @Override + public String getCharacterEncoding() { + return charset(); + } + + @Override + public void setCharacterEncoding(String env) { + setCharset(env); + } + + @Override + public int getContentLength() { + return contentLength(); + } + + @Override + public long getContentLengthLong() { + return contentLength(); + } + + @Override + public String getContentType() { + return contentType(); + } + + @Override + public ServletInputStream getInputStream() { + if (sis == null) { + sis = new HttpInputStream(inputStream()); + } + return sis; + } + + @Override + public String getParameter(String name) { + return parameter(name); + } + + @Override + public Enumeration getParameterNames() { + return Collections.enumeration(parameterNames()); + } + + @Override + public String[] getParameterValues(String name) { + List values = parameterValues(name); + return values == null ? null : values.toArray(new String[0]); + } + + @Override + public Map getParameterMap() { + Collection paramNames = parameterNames(); + if (paramNames.isEmpty()) { + return Collections.emptyMap(); + } + Map result = CollectionUtils.newLinkedHashMap(paramNames.size()); + for (String paramName : paramNames) { + result.put(paramName, getParameterValues(paramName)); + } + return result; + } + + @Override + public String getProtocol() { + return isHttp2() ? "HTTP/2.0" : "HTTP/1.1"; + } + + @Override + public String getScheme() { + return scheme(); + } + + @Override + public String getServerName() { + return serverName(); + } + + @Override + public int getServerPort() { + return serverPort(); + } + + @Override + public BufferedReader getReader() { + if (reader == null) { + reader = new BufferedReader(new InputStreamReader(inputStream(), charsetOrDefault())); + } + return reader; + } + + @Override + public String getRemoteAddr() { + return remoteAddr(); + } + + @Override + public String getRemoteHost() { + return String.valueOf(remotePort()); + } + + @Override + public Locale getLocale() { + return locale(); + } + + @Override + public Enumeration getLocales() { + return Collections.enumeration(locales()); + } + + @Override + public boolean isSecure() { + return "https".equals(scheme()); + } + + @Override + public RequestDispatcher getRequestDispatcher(String path) { + throw new UnsupportedOperationException(); + } + + @Override + public String getRealPath(String path) { + return null; + } + + @Override + public int getRemotePort() { + return remotePort(); + } + + @Override + public String getLocalName() { + return localHost(); + } + + @Override + public String getLocalAddr() { + return localAddr(); + } + + @Override + public int getLocalPort() { + return localPort(); + } + + @Override + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + throw new IllegalStateException(); + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) + throws IllegalStateException { + throw new IllegalStateException(); + } + + @Override + public boolean isAsyncStarted() { + return false; + } + + @Override + public boolean isAsyncSupported() { + return false; + } + + @Override + public AsyncContext getAsyncContext() { + throw new IllegalStateException(); + } + + @Override + public DispatcherType getDispatcherType() { + return DispatcherType.REQUEST; + } + + @Override + public String toString() { + return "ServletHttpRequestAdaptee{" + fieldToString() + '}'; + } + + private static final class HttpInputStream extends ServletInputStream { + + private final InputStream is; + + HttpInputStream(InputStream is) { + this.is = is; + } + + @Override + public int read() throws IOException { + return is.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return is.read(b); + } + + @Override + public void close() throws IOException { + is.close(); + } + + @Override + public int readLine(byte[] b, int off, int len) throws IOException { + return is.read(b, off, len); + } + + @Override + public boolean isFinished() { + try { + return is.available() == 0; + } catch (IOException e) { + return false; + } + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + throw new UnsupportedOperationException(); + } + } +} 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-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdaptee.java new file mode 100644 index 00000000000..75068cf3992 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdaptee.java @@ -0,0 +1,221 @@ +/* + * 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.servlet; + +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.remoting.http12.HttpUtils; +import org.apache.dubbo.remoting.http12.message.DefaultHttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Date; +import java.util.Locale; + +public class ServletHttpResponseAdaptee extends DefaultHttpResponse implements HttpServletResponse { + + private ServletOutputStream sos; + private PrintWriter writer; + + @Override + public void addCookie(Cookie cookie) { + addCookie(Helper.convert(cookie)); + } + + @Override + public boolean containsHeader(String name) { + return hasHeader(name); + } + + @Override + public String encodeURL(String url) { + return RequestUtils.encodeURL(url); + } + + @Override + 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); + } + + @Override + public void setDateHeader(String name, long date) { + setHeader(name, new Date(date)); + } + + @Override + public void addDateHeader(String name, long date) { + addHeader(name, new Date(date)); + } + + @Override + public void setIntHeader(String name, int value) { + setHeader(name, String.valueOf(value)); + } + + @Override + public void addIntHeader(String name, int value) { + addHeader(name, String.valueOf(value)); + } + + @Override + public void setStatus(int sc, String sm) { + setStatus(sc); + setBody(sm); + } + + @Override + public int getStatus() { + return status(); + } + + @Override + public String getHeader(String name) { + return header(name); + } + + @Override + public Collection getHeaders(String name) { + return headerValues(name); + } + + @Override + public Collection getHeaderNames() { + return headerNames(); + } + + @Override + public String getCharacterEncoding() { + return charset(); + } + + @Override + public String getContentType() { + return contentType(); + } + + @Override + public ServletOutputStream getOutputStream() { + if (sos == null) { + sos = new HttpOutputStream(outputStream()); + } + return sos; + } + + @Override + public PrintWriter getWriter() { + if (writer == null) { + writer = new PrintWriter(getOutputStream()); + } + return writer; + } + + @Override + public void setCharacterEncoding(String charset) { + setCharset(charset); + } + + @Override + public void setContentLength(int len) {} + + @Override + public void setContentLengthLong(long len) {} + + @Override + public void setBufferSize(int size) {} + + @Override + public int getBufferSize() { + return 0; + } + + @Override + public void flushBuffer() throws IOException { + //noinspection resource + OutputStream os = outputStream(); + if (os instanceof BufferedOutputStream) { + os.flush(); + } + } + + @Override + public void setLocale(Locale loc) { + setLocale(loc.toLanguageTag()); + } + + @Override + public Locale getLocale() { + Locale locale = CollectionUtils.first(HttpUtils.parseContentLanguage(locale())); + return locale == null ? Locale.getDefault() : locale; + } + + @Override + public String toString() { + return "ServletHttpResponseAdaptee{" + fieldToString() + '}'; + } + + private static final class HttpOutputStream extends ServletOutputStream { + + private final OutputStream outputStream; + + private HttpOutputStream(OutputStream outputStream) { + this.outputStream = outputStream; + } + + @Override + public void write(int b) throws IOException { + outputStream.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + outputStream.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + outputStream.write(b, off, len); + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + throw new UnsupportedOperationException(); + } + } +} 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 new file mode 100644 index 00000000000..ac45cbe9473 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000000..5f4a984c541 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000000..ec669308fb3 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000000..34d1c53e88a --- /dev/null +++ b/dubbo-plugin/dubbo-rest-servlet/src/test/resources/log4j2-test.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/dubbo-plugin/dubbo-rest-spring/pom.xml b/dubbo-plugin/dubbo-rest-spring/pom.xml new file mode 100644 index 00000000000..5366743d486 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/pom.xml @@ -0,0 +1,115 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-plugin + ${revision} + ../pom.xml + + + dubbo-rest-spring + + + + org.apache.dubbo + dubbo-rpc-triple + ${project.version} + + + org.apache.dubbo + dubbo-config-spring + ${project.version} + + + org.apache.dubbo + dubbo-rest-servlet + ${project.version} + + + org.springframework + spring-context + + + org.springframework + spring-web + + + org.springframework + spring-webmvc + true + + + org.apache.dubbo + dubbo-remoting-netty4 + ${project.version} + test + + + javax.xml.bind + jaxb-api + test + + + org.glassfish.jaxb + jaxb-runtime + test + + + org.apache.dubbo + dubbo-rpc-rest + ${project.version} + test + + + javax.validation + validation-api + + + io.swagger + swagger-jaxrs + + + io.swagger + swagger-annotations + + + org.eclipse.jetty + jetty-servlet + + + org.apache.tomcat.embed + tomcat-embed-core + + + org.apache.httpcomponents + httpclient + + + org.eclipse.jetty + jetty-server + + + org.jboss.resteasy + resteasy-client + + + + + 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 new file mode 100644 index 00000000000..fef61723e7f --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.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.support.spring; + +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 java.lang.annotation.Annotation; + +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)); + } +} 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 new file mode 100644 index 00000000000..be8b1c58652 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java @@ -0,0 +1,62 @@ +/* + * 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.mapping.meta.AnnotationEnum; + +import java.lang.annotation.Annotation; + +public enum Annotations implements AnnotationEnum { + RequestMapping, + PathVariable, + MatrixVariable, + RequestParam, + RequestHeader, + CookieValue, + RequestAttribute, + RequestPart, + RequestBody, + ModelAttribute, + ResponseStatus, + CrossOrigin, + ExceptionHandler, + Nonnull("javax.annotation.Nonnull"); + + private final String className; + private Class type; + + Annotations() { + className = "org.springframework.web.bind.annotation." + name(); + } + + Annotations(String className) { + this.className = className; + } + + @Override + public String className() { + return className; + } + + @Override + public Class type() { + if (type == null) { + type = loadType(); + } + return type; + } +} 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 new file mode 100644 index 00000000000..17005d5fda3 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BeanArgumentBinder.java @@ -0,0 +1,158 @@ +/* + * 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.beans.factory.ScopeBeanFactory; +import org.apache.dubbo.common.utils.CollectionUtils; +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.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.ArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.CompositeArgumentResolver; +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; +import org.springframework.core.convert.ConversionService; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.DataBinder; +import org.springframework.web.bind.WebDataBinder; + +final class BeanArgumentBinder { + + 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); + try { + Object bean = buildBean(parameter, request, response); + WebDataBinder binder = new WebDataBinder(bean, name); + binder.setConversionService(conversionService); + binder.bind(new MutablePropertyValues(RequestUtils.getParametersMap(request))); + BindingResult result = binder.getBindingResult(); + if (result.hasErrors()) { + throw new BindException(result); + } + return binder.getTarget(); + } catch (Exception e) { + throw new RestException(e, Messages.ARGUMENT_BIND_ERROR, name, parameter.getType()); + } + } + + private Object buildBean(ParameterMeta parameter, HttpRequest request, HttpResponse response) throws Exception { + Class type = parameter.getActualType(); + if (Modifier.isAbstract(type.getModifiers())) { + throw new IllegalStateException(Messages.ARGUMENT_COULD_NOT_RESOLVED.format(parameter.getDescription())); + } + ConstructorMeta ct = cache.computeIfAbsent(type, k -> resolveConstructor(parameter.getToolKit(), k)); + ParameterMeta[] parameters = ct.parameters; + int len = parameters.length; + Object[] args = new Object[len]; + for (int i = 0; i < len; i++) { + args[i] = argumentResolver.resolve(parameters[i], request, response); + } + 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/ConfigurationWrapper.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/ConfigurationWrapper.java new file mode 100644 index 00000000000..be6d3f75ad7 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/ConfigurationWrapper.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.spring; + +import org.apache.dubbo.common.config.Configuration; +import org.apache.dubbo.rpc.model.ApplicationModel; + +import java.util.Properties; + +@SuppressWarnings("serial") +public final class ConfigurationWrapper extends Properties { + + private final Configuration configuration; + + ConfigurationWrapper(ApplicationModel applicationModel) { + configuration = applicationModel.modelEnvironment().getConfiguration(); + } + + @Override + public String getProperty(String key) { + Object value = configuration.getProperty(key); + return value == null ? null : value.toString(); + } + + @Override + public Object get(Object key) { + return configuration.getProperty(key == null ? null : key.toString()); + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/CookieValueArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/CookieValueArgumentResolver.java new file mode 100644 index 00000000000..0e3767efff4 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/CookieValueArgumentResolver.java @@ -0,0 +1,74 @@ +/* + * 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.common.utils.CollectionUtils; +import org.apache.dubbo.remoting.http12.HttpCookie; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.NamedValueMeta; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Activate(onClass = "org.springframework.web.bind.annotation.CookieValue") +public class CookieValueArgumentResolver extends AbstractSpringArgumentResolver { + + @Override + public Class accept() { + return Annotations.CookieValue.type(); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.cookie(meta.name()); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + 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; + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + 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; + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/FallbackArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/FallbackArgumentResolver.java new file mode 100644 index 00000000000..79e1121650b --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/FallbackArgumentResolver.java @@ -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.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.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 org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; + +@Activate(order = Integer.MAX_VALUE - 10000, onClass = "org.springframework.web.bind.annotation.RequestMapping") +public class FallbackArgumentResolver extends AbstractArgumentResolver { + + @Override + public boolean accept(ParameterMeta param) { + return param.getToolKit().getDialect() == RestConstants.DIALECT_SPRING_MVC; + } + + @Override + protected NamedValueMeta createNamedValueMeta(ParameterMeta param) { + return new NamedValueMeta(param.isAnnotated(Annotations.Nonnull), null); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + ParameterMeta parameter = meta.parameterMeta(); + if (parameter.isSimple()) { + return request.parameter(meta.name()); + } + return parameter.bind(request, response); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + if (TypeUtils.isSimpleProperty(meta.nestedType(0))) { + return request.parameterValues(meta.name()); + } + return null; + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + if (TypeUtils.isSimpleProperty(meta.nestedType(1))) { + return RequestUtils.getParametersMap(request); + } + return null; + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/HandlerInterceptorAdapter.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/HandlerInterceptorAdapter.java new file mode 100644 index 00000000000..44d720ed053 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/HandlerInterceptorAdapter.java @@ -0,0 +1,110 @@ +/* + * 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.Result; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter.Listener; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.util.Arrays; + +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +@Activate(onClass = "org.springframework.web.servlet.HandlerInterceptor") +public final class HandlerInterceptorAdapter implements RestExtensionAdapter { + @Override + public boolean accept(Object extension) { + return extension instanceof HandlerInterceptor; + } + + @Override + public RestFilter adapt(HandlerInterceptor extension) { + return new HandlerInterceptorRestFilter(extension); + } + + private static final class HandlerInterceptorRestFilter implements RestFilter, Listener { + + private final HandlerInterceptor interceptor; + + @Override + public int getPriority() { + return RestUtils.getPriority(interceptor); + } + + @Override + public String[] getPatterns() { + return RestUtils.getPattens(interceptor); + } + + public HandlerInterceptorRestFilter(HandlerInterceptor interceptor) { + this.interceptor = interceptor; + } + + @Override + public void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) throws Exception { + Object handler = request.attribute(RestConstants.HANDLER_ATTRIBUTE); + if (interceptor.preHandle((HttpServletRequest) request, (HttpServletResponse) response, handler)) { + chain.doFilter(request, response); + } + } + + @Override + public void onResponse(Result result, HttpRequest request, HttpResponse response) throws Exception { + if (result.hasException()) { + onError(result.getException(), request, response); + return; + } + Object handler = request.attribute(RestConstants.HANDLER_ATTRIBUTE); + ModelAndView mv = new ModelAndView(); + mv.addObject("result", result); + interceptor.postHandle((HttpServletRequest) request, (HttpServletResponse) response, handler, mv); + } + + @Override + public void onError(Throwable t, HttpRequest request, HttpResponse response) throws Exception { + Object handler = request.attribute(RestConstants.HANDLER_ATTRIBUTE); + Exception ex = t instanceof Exception ? (Exception) t : new RpcException(t); + interceptor.afterCompletion((HttpServletRequest) request, (HttpServletResponse) response, handler, ex); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("RestFilter{interceptor="); + sb.append(interceptor); + int priority = getPriority(); + if (priority != 0) { + sb.append(", priority=").append(priority); + } + String[] patterns = getPatterns(); + if (patterns != null) { + sb.append(", patterns=").append(Arrays.toString(patterns)); + } + return sb.append('}').toString(); + } + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Helper.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Helper.java new file mode 100644 index 00000000000..fd818c06770 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Helper.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.spring; + +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; + +import java.lang.annotation.Annotation; + +import org.springframework.web.bind.annotation.ValueConstants; + +final class Helper { + + private Helper() {} + + public static boolean isRequired(AnnotationMeta annotation) { + return annotation.getBoolean("required"); + } + + public static String defaultValue(AnnotationMeta annotation) { + return defaultValue(annotation, "defaultValue"); + } + + public static String defaultValue(AnnotationMeta annotation, String name) { + return defaultValue(annotation.getString(name)); + } + + public static String defaultValue(String value) { + return ValueConstants.DEFAULT_NONE.equals(value) ? null : value; + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java new file mode 100644 index 00000000000..1ca99a5cb08 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java @@ -0,0 +1,125 @@ +/* + * 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.common.utils.CollectionUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +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; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Activate(onClass = "org.springframework.web.bind.annotation.MatrixVariable") +public class MatrixVariableArgumentResolver extends AbstractSpringArgumentResolver { + + @Override + public Class accept() { + return Annotations.MatrixVariable.type(); + } + + @Override + protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta ann) { + return new MatrixNamedValueMeta( + ann.getValue(), Helper.isRequired(ann), Helper.defaultValue(ann), Helper.defaultValue(ann, "pathVar")); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return CollectionUtils.first(doResolveCollectionValue(meta, request)); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return doResolveCollectionValue(meta, request); + } + + private static List doResolveCollectionValue(NamedValueMeta meta, HttpRequest request) { + String name = meta.name(); + Map variableMap = request.attribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + if (variableMap == null) { + return Collections.emptyList(); + } + List result = null; + String pathVar = ((MatrixNamedValueMeta) meta).pathVar; + if (pathVar == null) { + result = RequestUtils.parseMatrixVariableValues(variableMap, name); + } else { + String value = variableMap.get(pathVar); + if (value != null) { + Map> matrixVariables = RequestUtils.parseMatrixVariables(value); + if (matrixVariables != null) { + List values = matrixVariables.get(name); + if (values != null) { + return values; + } + } + } + } + return result == null ? Collections.emptyList() : result; + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + Map variableMap = request.attribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + if (variableMap == null) { + return Collections.emptyMap(); + } + Map> result = null; + String pathVar = ((MatrixNamedValueMeta) meta).pathVar; + if (pathVar == null) { + for (Map.Entry entry : variableMap.entrySet()) { + Map> matrixVariables = RequestUtils.parseMatrixVariables(entry.getValue()); + if (matrixVariables == null) { + continue; + } + if (result == null) { + result = new HashMap<>(); + } + for (Map.Entry> matrixEntry : matrixVariables.entrySet()) { + result.computeIfAbsent(matrixEntry.getKey(), k -> new ArrayList<>()) + .addAll(matrixEntry.getValue()); + } + } + } else { + String value = variableMap.get(pathVar); + if (value != null) { + result = RequestUtils.parseMatrixVariables(value); + } + } + return result == null ? Collections.emptyMap() : result; + } + + private static class MatrixNamedValueMeta extends NamedValueMeta { + + private final String pathVar; + + public MatrixNamedValueMeta(String name, boolean required, String defaultValue, String pathVar) { + super(name, required, defaultValue); + this.pathVar = pathVar; + } + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/ModelAttributeArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/ModelAttributeArgumentResolver.java new file mode 100644 index 00000000000..89b2581dd3e --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/ModelAttributeArgumentResolver.java @@ -0,0 +1,59 @@ +/* + * 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.mapping.meta.NamedValueMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; +import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; + +import java.lang.annotation.Annotation; + +@Activate(onClass = "org.springframework.web.bind.annotation.ModelAttribute") +public final class ModelAttributeArgumentResolver extends AbstractSpringArgumentResolver { + + @Override + public Class accept() { + return Annotations.ModelAttribute.type(); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + if (meta.parameterMeta().isSimple()) { + return request.parameter(meta.name()); + } + return meta.parameterMeta().bind(request, response); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + if (meta.parameterMeta().isSimple()) { + return request.parameterValues(meta.name()); + } + return meta.parameterMeta().bind(request, response); + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + if (TypeUtils.isSimpleProperty(meta.nestedType(1))) { + return RequestUtils.getParametersMap(request); + } + return meta.parameterMeta().bind(request, response); + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/PathVariableArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/PathVariableArgumentResolver.java new file mode 100644 index 00000000000..a1c389f54f4 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/PathVariableArgumentResolver.java @@ -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.support.spring; + +import org.apache.dubbo.common.extension.Activate; +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; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.RestParameterException; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.AnnotationBaseArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +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; +import java.util.Map; + +@Activate(onClass = "org.springframework.web.bind.annotation.PathVariable") +public class PathVariableArgumentResolver implements AnnotationBaseArgumentResolver { + + @Override + public Class accept() { + return Annotations.PathVariable.type(); + } + + @Override + public Object resolve( + ParameterMeta parameter, + AnnotationMeta annotation, + HttpRequest request, + HttpResponse response) { + Map variableMap = request.attribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + String name = annotation.getValue(); + if (StringUtils.isEmpty(name)) { + name = parameter.getRequiredName(); + } + if (variableMap == null) { + if (Helper.isRequired(annotation)) { + throw new RestParameterException(Messages.ARGUMENT_VALUE_MISSING, name, parameter.getType()); + } + return null; + } + String value = variableMap.get(name); + if (value == null) { + return null; + } + int index = value.indexOf(';'); + return RequestUtils.decodeURL(index == -1 ? value : value.substring(0, index)); + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestAttributeArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestAttributeArgumentResolver.java new file mode 100644 index 00000000000..7e61abcf442 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestAttributeArgumentResolver.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.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.mapping.meta.NamedValueMeta; + +import java.lang.annotation.Annotation; + +@Activate(onClass = "org.springframework.web.bind.annotation.RequestAttribute") +public class RequestAttributeArgumentResolver extends AbstractSpringArgumentResolver { + + @Override + public Class accept() { + return Annotations.RequestAttribute.type(); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.attribute(meta.name()); + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.attributes(); + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java new file mode 100644 index 00000000000..4bc2bf14013 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java @@ -0,0 +1,79 @@ +/* + * 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.common.io.StreamUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.RestException; +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; + +@Activate(onClass = "org.springframework.web.bind.annotation.RequestBody") +public class RequestBodyArgumentResolver extends AbstractSpringArgumentResolver { + + @Override + public Class accept() { + return Annotations.RequestBody.type(); + } + + @Override + protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta ann) { + return new NamedValueMeta(Helper.isRequired(ann), null); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + if (RequestUtils.isFormOrMultiPart(request)) { + if (meta.parameterMeta().isSimple()) { + return request.formParameter(meta.name()); + } + return meta.parameterMeta().bind(request, response); + } + return RequestUtils.decodeBody(request, meta.type()); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + 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()); + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + if (RequestUtils.isFormOrMultiPart(request)) { + return RequestUtils.getFormParametersMap(request); + } + return RequestUtils.decodeBody(request, meta.type()); + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestHeaderArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestHeaderArgumentResolver.java new file mode 100644 index 00000000000..df554d8eff2 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestHeaderArgumentResolver.java @@ -0,0 +1,48 @@ +/* + * 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.mapping.meta.NamedValueMeta; + +import java.lang.annotation.Annotation; + +@Activate(onClass = "org.springframework.web.bind.annotation.RequestHeader") +public final class RequestHeaderArgumentResolver extends AbstractSpringArgumentResolver { + + @Override + public Class accept() { + return Annotations.RequestHeader.type(); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.header(meta.name()); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.headerValues(meta.name()); + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.headers(); + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestParamArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestParamArgumentResolver.java new file mode 100644 index 00000000000..56a1b1577f4 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestParamArgumentResolver.java @@ -0,0 +1,49 @@ +/* + * 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.mapping.meta.NamedValueMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; + +import java.lang.annotation.Annotation; + +@Activate(onClass = "org.springframework.web.bind.annotation.RequestParam") +public final class RequestParamArgumentResolver extends AbstractSpringArgumentResolver { + + @Override + public Class accept() { + return Annotations.RequestParam.type(); + } + + @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/RequestPartArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestPartArgumentResolver.java new file mode 100644 index 00000000000..1be4bdac45f --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestPartArgumentResolver.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.spring; + +import org.apache.dubbo.common.extension.Activate; +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.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 java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +@Activate(onClass = "org.springframework.web.bind.annotation.RequestPart") +public class RequestPartArgumentResolver extends AbstractSpringArgumentResolver { + + @Override + public Class accept() { + return Annotations.RequestPart.type(); + } + + @Override + protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta ann) { + return new NamedValueMeta(ann.getValue(), Helper.isRequired(ann), null); + } + + @Override + protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.part(meta.name()); + } + + @Override + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return request.parts(); + } + + @Override + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + 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; + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMiscArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMiscArgumentResolver.java new file mode 100644 index 00000000000..5a6fd55b54b --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMiscArgumentResolver.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.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.ArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +import javax.servlet.http.HttpServletRequest; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.util.CollectionUtils; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; + +@Activate(onClass = "org.springframework.web.context.request.WebRequest") +public class SpringMiscArgumentResolver implements ArgumentResolver { + + private static final Set> SUPPORTED_TYPES = new HashSet<>(); + + static { + SUPPORTED_TYPES.add(WebRequest.class); + SUPPORTED_TYPES.add(NativeWebRequest.class); + SUPPORTED_TYPES.add(HttpEntity.class); + SUPPORTED_TYPES.add(HttpHeaders.class); + } + + @Override + public boolean accept(ParameterMeta parameter) { + return SUPPORTED_TYPES.contains(parameter.getActualType()); + } + + @Override + public Object resolve(ParameterMeta parameter, HttpRequest request, HttpResponse response) { + Class type = parameter.getActualType(); + if (type == WebRequest.class || type == NativeWebRequest.class) { + return new ServletWebRequest((HttpServletRequest) request); + } + if (type == HttpEntity.class) { + return new HttpEntity<>(CollectionUtils.toMultiValueMap(request.headers())); + } + if (type == HttpHeaders.class) { + return new HttpHeaders(CollectionUtils.toMultiValueMap(request.headers())); + } + return null; + } +} 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 new file mode 100644 index 00000000000..ca4dd68a3bc --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java @@ -0,0 +1,107 @@ +/* + * 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.common.utils.StringUtils; +import org.apache.dubbo.rpc.model.FrameworkModel; +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.MethodMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; + +import org.springframework.http.HttpStatus; + +@Activate(onClass = "org.springframework.web.bind.annotation.RequestMapping") +public class SpringMvcRequestMappingResolver implements RequestMappingResolver { + + private final FrameworkModel frameworkModel; + private volatile RestToolKit toolKit; + + public SpringMvcRequestMappingResolver(FrameworkModel frameworkModel) { + this.frameworkModel = frameworkModel; + } + + @Override + public RestToolKit getRestToolKit() { + RestToolKit toolKit = this.toolKit; + if (toolKit == null) { + synchronized (this) { + toolKit = this.toolKit; + if (toolKit == null) { + toolKit = new SpringRestToolKit(frameworkModel); + this.toolKit = toolKit; + } + } + } + return toolKit; + } + + @Override + public RequestMapping resolve(ServiceMeta serviceMeta) { + AnnotationMeta requestMapping = serviceMeta.findMergedAnnotation(Annotations.RequestMapping); + if (requestMapping == null) { + return null; + } + AnnotationMeta responseStatus = serviceMeta.findMergedAnnotation(Annotations.ResponseStatus); + return builder(requestMapping, responseStatus) + .name(serviceMeta.getType().getSimpleName()) + .contextPath(serviceMeta.getContextPath()) + .build(); + } + + @Override + public RequestMapping resolve(MethodMeta methodMeta) { + AnnotationMeta requestMapping = methodMeta.findMergedAnnotation(Annotations.RequestMapping); + if (requestMapping == 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); + return builder(requestMapping, responseStatus) + .name(methodMeta.getMethod().getName()) + .contextPath(serviceMeta.getContextPath()) + .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())) + .build(); + } + + private Builder builder(AnnotationMeta requestMapping, AnnotationMeta responseStatus) { + Builder builder = RequestMapping.builder(); + if (responseStatus != null) { + HttpStatus value = responseStatus.getEnum("value"); + builder.responseStatus(value.value()); + String reason = responseStatus.getString("reason"); + if (StringUtils.isNotEmpty(reason)) { + builder.responseReason(reason); + } + } + return builder.path(requestMapping.getValueArray()) + .method(requestMapping.getStringArray("method")) + .param(requestMapping.getStringArray("params")) + .header(requestMapping.getStringArray("headers")) + .consume(requestMapping.getStringArray("consumes")) + .produce(requestMapping.getStringArray("produces")); + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringResponseRestFilter.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringResponseRestFilter.java new file mode 100644 index 00000000000..05458703c58 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringResponseRestFilter.java @@ -0,0 +1,195 @@ +/* + * 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.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.HttpResult; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.RestException; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.CompositeArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter.Listener; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +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.ParameterMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ResponseMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Activate(order = -10000, onClass = "org.springframework.http.ResponseEntity") +public class SpringResponseRestFilter implements RestFilter, Listener { + + private final ArgumentResolver argumentResolver; + private final Map> cache = CollectionUtils.newConcurrentHashMap(); + + public SpringResponseRestFilter(FrameworkModel frameworkModel) { + argumentResolver = frameworkModel.getBeanFactory().getOrRegisterBean(CompositeArgumentResolver.class); + } + + @Override + public void onResponse(Result result, HttpRequest request, HttpResponse response) { + if (result.hasException()) { + HandlerMeta handler = request.attribute(RestConstants.HANDLER_ATTRIBUTE); + if (handler == null) { + return; + } + + Throwable t = result.getException(); + Key key = new Key(handler.getMethod().getMethod(), t.getClass()); + cache.computeIfAbsent(key, k -> findSuitableExceptionHandler(handler.getService(), k.type)) + .ifPresent(m -> { + try { + result.setValue(invokeExceptionHandler(m, t, request, response)); + result.setException(null); + } catch (Throwable th) { + result.setException(th); + } + }); + return; + } + + Object value = result.getValue(); + if (value instanceof ResponseEntity) { + ResponseEntity entity = (ResponseEntity) value; + result.setValue(HttpResult.builder() + .body(entity.getBody()) + .status(entity.getStatusCode().value()) + .headers(entity.getHeaders()) + .build()); + return; + } + + RequestMapping mapping = request.attribute(RestConstants.MAPPING_ATTRIBUTE); + if (mapping == null) { + return; + } + ResponseMeta responseMeta = mapping.getResponse(); + if (responseMeta == null) { + return; + } + String reason = responseMeta.getReason(); + result.setValue(HttpResult.builder() + .body(reason == null ? result.getValue() : reason) + .status(responseMeta.getStatus()) + .build()); + } + + private Optional findSuitableExceptionHandler(ServiceMeta serviceMeta, Class exType) { + if (serviceMeta.getExceptionHandlers() == null) { + return Optional.empty(); + } + List, MethodMeta>> candidates = new ArrayList<>(); + for (MethodMeta methodMeta : serviceMeta.getExceptionHandlers()) { + ExceptionHandler anno = methodMeta.getMethod().getAnnotation(ExceptionHandler.class); + if (anno == null) { + continue; + } + for (Class type : anno.value()) { + if (type.isAssignableFrom(exType)) { + candidates.add(Pair.of(type, methodMeta)); + } + } + } + int size = candidates.size(); + if (size == 0) { + return Optional.empty(); + } + if (size > 1) { + candidates.sort((o1, o2) -> { + Class c1 = o1.getLeft(); + Class c2 = o2.getLeft(); + if (c1.equals(c2)) { + return 0; + } + return c1.isAssignableFrom(c2) ? 1 : -1; + }); + } + return Optional.of(candidates.get(0).getRight()); + } + + private Object invokeExceptionHandler(MethodMeta meta, Throwable t, HttpRequest request, HttpResponse response) { + ParameterMeta[] parameters = meta.getParameters(); + int len = parameters.length; + Object[] args = new Object[len]; + for (int i = 0; i < len; i++) { + ParameterMeta parameter = parameters[i]; + if (parameter.getType().isInstance(t)) { + args[i] = t; + } else { + args[i] = argumentResolver.resolve(parameter, request, response); + } + } + Object value; + try { + value = meta.getMethod().invoke(meta.getServiceMeta().getService(), args); + } catch (Exception e) { + throw new RestException("Failed to invoke exception handler method: " + meta.getMethod(), e); + } + AnnotationMeta rs = meta.getAnnotation(ResponseStatus.class); + if (rs == null) { + return value; + } + HttpStatus status = rs.getEnum("value"); + String reason = rs.getString("reason"); + return HttpResult.builder() + .body(StringUtils.isEmpty(reason) ? value : reason) + .status(status.value()) + .build(); + } + + private static final class Key { + private final Method method; + private final Class type; + + Key(Method method, Class type) { + this.method = method; + this.type = type; + } + + @Override + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + public boolean equals(Object obj) { + Key other = (Key) obj; + return method.equals(other.method) && type.equals(other.type); + } + + @Override + public int hashCode() { + return method.hashCode() * 31 + type.hashCode(); + } + } +} 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 new file mode 100644 index 00000000000..9e5011b683c --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringRestToolKit.java @@ -0,0 +1,127 @@ +/* + * 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.config.spring.extension.SpringExtensionInjector; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.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.MethodParameterMeta; +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.RestUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Map; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.util.PropertyPlaceholderHelper; + +final class SpringRestToolKit implements RestToolKit { + + private final ConfigurableBeanFactory beanFactory; + private final PropertyPlaceholderHelper placeholderHelper; + private final ConfigurationWrapper configuration; + private final ConversionService conversionService; + private final TypeConverter typeConverter; + private final BeanArgumentBinder argumentBinder; + private final ParameterNameDiscoverer discoverer; + + public SpringRestToolKit(FrameworkModel frameworkModel) { + ApplicationModel applicationModel = frameworkModel.defaultApplication(); + SpringExtensionInjector injector = SpringExtensionInjector.get(applicationModel); + ApplicationContext context = injector.getContext(); + if (context instanceof ConfigurableApplicationContext) { + beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory(); + placeholderHelper = null; + configuration = null; + } else { + beanFactory = null; + placeholderHelper = new PropertyPlaceholderHelper("${", "}", ":", true); + configuration = new ConfigurationWrapper(applicationModel); + } + if (context != null && context.containsBean("mvcConversionService")) { + conversionService = context.getBean(ConversionService.class, "mvcConversionService"); + } else { + conversionService = DefaultConversionService.getSharedInstance(); + } + typeConverter = frameworkModel.getBeanFactory().getOrRegisterBean(GeneralTypeConverter.class); + discoverer = new DefaultParameterNameDiscoverer(); + argumentBinder = new BeanArgumentBinder(frameworkModel, conversionService); + } + + @Override + public int getDialect() { + return RestConstants.DIALECT_SPRING_MVC; + } + + @Override + public String resolvePlaceholders(String text) { + if (!RestUtils.hasPlaceholder(text)) { + return text; + } + if (beanFactory != null) { + return beanFactory.resolveEmbeddedValue(text); + } + return placeholderHelper.replacePlaceholders(text, configuration); + } + + @Override + public Object convert(Object value, ParameterMeta parameter) { + TypeDescriptor targetType = (TypeDescriptor) parameter.getTypeDescriptor(); + if (targetType == null) { + MethodParameterMeta meta = (MethodParameterMeta) parameter; + targetType = new TypeDescriptor(new MethodParameter(meta.getMethod(), meta.getIndex())); + parameter.setTypeDescriptor(targetType); + } + TypeDescriptor sourceType = TypeDescriptor.forObject(value); + if (conversionService.canConvert(sourceType, targetType)) { + return conversionService.convert(value, sourceType, targetType); + } + return typeConverter.convert(value, parameter.getGenericType()); + } + + @Override + public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) { + return argumentBinder.bind(parameter, request, response); + } + + @Override + public String[] getParameterNames(Method method) { + return discoverer.getParameterNames(method); + } + + @Override + public Map getAttributes(AnnotatedElement element, Annotation annotation) { + return AnnotatedElementUtils.getMergedAnnotationAttributes(element, annotation.annotationType()); + } +} 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 new file mode 100644 index 00000000000..7b0136aee70 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver @@ -0,0 +1,12 @@ +spring-path-variable=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.PathVariableArgumentResolver +spring-matrix-variable=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.MatrixVariableArgumentResolver +spring-request-param=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.RequestParamArgumentResolver +spring-request-header=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.RequestHeaderArgumentResolver +spring-cookie-value=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.CookieValueArgumentResolver +spring-request-attribute=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.RequestAttributeArgumentResolver +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-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/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension b/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension new file mode 100644 index 00000000000..9c41d294306 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension @@ -0,0 +1 @@ +spring-response=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.SpringResponseRestFilter diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter b/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter new file mode 100644 index 00000000000..fd87fb6a8cf --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter @@ -0,0 +1 @@ +spring-interceptor=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.HandlerInterceptorAdapter diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver b/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver new file mode 100644 index 00000000000..968dbaf62a8 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver @@ -0,0 +1 @@ +spring=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.SpringMvcRequestMappingResolver diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java new file mode 100644 index 00000000000..bf235fed574 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringDemoServiceImpl.java @@ -0,0 +1,107 @@ +/* + * 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.compatible; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.rpc.RpcContext; + +import java.util.List; +import java.util.Map; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.bind.annotation.ExceptionHandler; + +public class SpringDemoServiceImpl implements SpringRestDemoService { + private static Map context; + private boolean called; + + @Override + public String sayHello(String name) { + called = true; + return "Hello, " + name; + } + + @Override + public boolean isCalled() { + return called; + } + + @Override + public String testFormBody(User user) { + return user.getName(); + } + + @Override + public List testFormMapBody(LinkedMultiValueMap map) { + return map.get("form"); + } + + @Override + public String testHeader(String header) { + return header; + } + + @Override + public String testHeaderInt(int header) { + return String.valueOf(header); + } + + @Override + public Integer hello(Integer a, Integer b) { + context = RpcContext.getServerAttachment().getObjectAttachments(); + return a + b; + } + + @Override + public String error() { + throw new RuntimeException(); + } + + @ExceptionHandler(RuntimeException.class) + public String onError(HttpRequest request, Throwable t) { + return "ok"; + } + + @ExceptionHandler(Exception.class) + public String onError1() { + return "ok1"; + } + + public static Map getAttachments() { + return context; + } + + @Override + public int primitiveInt(int a, int b) { + return a + b; + } + + @Override + public long primitiveLong(long a, Long b) { + return a + b; + } + + @Override + public long primitiveByte(byte a, Long b) { + return a + b; + } + + @Override + public long primitiveShort(short a, Long b, int c) { + return a + b; + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java new file mode 100644 index 00000000000..6e124fe2ae8 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringMvcRestProtocolTest.java @@ -0,0 +1,312 @@ +/* + * 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.compatible; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.rpc.Exporter; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Protocol; +import org.apache.dubbo.rpc.ProxyFactory; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.RpcInvocation; +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 java.util.Arrays; + +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.aop.framework.AdvisedSupport; +import org.springframework.aop.framework.AopProxy; +import org.springframework.aop.framework.ProxyCreatorSupport; +import org.springframework.util.LinkedMultiValueMap; + +import static org.apache.dubbo.remoting.Constants.SERVER_KEY; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class SpringMvcRestProtocolTest { + private final Protocol tProtocol = + ApplicationModel.defaultModel().getExtensionLoader(Protocol.class).getExtension("tri"); + private final Protocol protocol = + ApplicationModel.defaultModel().getExtensionLoader(Protocol.class).getExtension("rest"); + private final ProxyFactory proxy = ApplicationModel.defaultModel() + .getExtensionLoader(ProxyFactory.class) + .getAdaptiveExtension(); + + private static URL getUrl() { + return URL.valueOf("tri://127.0.0.1:" + NetUtils.getAvailablePort() + "/rest?interface=" + + SpringRestDemoService.class.getName()); + } + + private static final String SERVER = "netty4"; + + private final ModuleServiceRepository repository = + ApplicationModel.defaultModel().getDefaultModule().getServiceRepository(); + + @AfterEach + public void tearDown() { + tProtocol.destroy(); + protocol.destroy(); + FrameworkModel.destroyAll(); + } + + public SpringRestDemoService getServerImpl() { + return new SpringDemoServiceImpl(); + } + + public Class getServerClass() { + return SpringRestDemoService.class; + } + + public Exporter getExport(URL url, SpringRestDemoService server) { + url = url.addParameter(SERVER_KEY, SERVER); + return tProtocol.export(proxy.getInvoker(server, getServerClass(), url)); + } + + @Test + void testRestProtocol() { + int port = NetUtils.getAvailablePort(); + URL url = URL.valueOf( + "tri://127.0.0.1:" + port + "/?version=1.0.0&interface=" + SpringRestDemoService.class.getName()); + + SpringRestDemoService server = getServerImpl(); + + url = this.registerProvider(url, server, getServerClass()); + + Exporter exporter = getExport(url, server); + + Invoker invoker = protocol.refer(SpringRestDemoService.class, url); + Assertions.assertFalse(server.isCalled()); + + SpringRestDemoService client = proxy.getProxy(invoker); + String result = client.sayHello("haha"); + Assertions.assertTrue(server.isCalled()); + Assertions.assertEquals("Hello, haha", result); + + String header = client.testHeader("header"); + Assertions.assertEquals("header", header); + + String headerInt = client.testHeaderInt(1); + Assertions.assertEquals("1", headerInt); + invoker.destroy(); + exporter.unexport(); + } + + @Test + void testRestProtocolWithContextPath() { + SpringRestDemoService server = getServerImpl(); + Assertions.assertFalse(server.isCalled()); + int port = NetUtils.getAvailablePort(); + URL url = URL.valueOf( + "tri://127.0.0.1:" + port + "/a/b/c?version=1.0.0&interface=" + SpringRestDemoService.class.getName()); + + url = this.registerProvider(url, server, SpringRestDemoService.class); + + Exporter exporter = getExport(url, server); + + url = URL.valueOf("rest://127.0.0.1:" + port + "/a/b/c/?version=1.0.0&interface=" + + SpringRestDemoService.class.getName()); + Invoker invoker = protocol.refer(SpringRestDemoService.class, url); + SpringRestDemoService client = proxy.getProxy(invoker); + String result = client.sayHello("haha"); + Assertions.assertTrue(server.isCalled()); + Assertions.assertEquals("Hello, haha", result); + invoker.destroy(); + exporter.unexport(); + } + + @Test + void testExport() { + SpringRestDemoService server = getServerImpl(); + + URL url = this.registerProvider(getUrl(), server, SpringRestDemoService.class); + + RpcContext.getClientAttachment().setAttachment("timeout", "200"); + Exporter exporter = getExport(url, server); + + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, url)); + + Integer echoString = demoService.hello(1, 2); + assertThat(echoString, is(3)); + + exporter.unexport(); + } + + @Test + void testNettyServer() { + SpringRestDemoService server = getServerImpl(); + + URL nettyUrl = this.registerProvider(getUrl(), server, SpringRestDemoService.class); + + Exporter exporter = getExport(nettyUrl, server); + + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, nettyUrl)); + + Integer echoString = demoService.hello(10, 10); + assertThat(echoString, is(20)); + + exporter.unexport(); + } + + @Test + void testInvoke() { + SpringRestDemoService server = getServerImpl(); + + URL url = this.registerProvider(getUrl(), server, SpringRestDemoService.class); + + Exporter exporter = getExport(url, server); + + RpcInvocation rpcInvocation = new RpcInvocation( + "hello", + SpringRestDemoService.class.getName(), + "", + new Class[] {Integer.class, Integer.class}, + new Integer[] {2, 3}); + + Result result = exporter.getInvoker().invoke(rpcInvocation); + assertThat(result.getValue(), CoreMatchers.is(5)); + } + + @Test + void testFilter() { + SpringRestDemoService server = getServerImpl(); + + URL url = this.registerProvider(getUrl(), server, SpringRestDemoService.class); + + Exporter exporter = getExport(url, server); + + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, url)); + + Integer result = demoService.hello(1, 2); + + assertThat(result, is(3)); + + exporter.unexport(); + } + + @Test + void testDefaultPort() { + assertThat(protocol.getDefaultPort(), is(80)); + } + + @Test + void testFormConsumerParser() { + SpringRestDemoService server = getServerImpl(); + + URL nettyUrl = this.registerProvider(getUrl(), server, SpringRestDemoService.class); + + Exporter exporter = getExport(nettyUrl, server); + + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, nettyUrl)); + + User user = new User(); + user.setAge(18); + user.setName("dubbo"); + user.setId(404l); + String name = demoService.testFormBody(user); + Assertions.assertEquals("dubbo", name); + + LinkedMultiValueMap forms = new LinkedMultiValueMap<>(); + forms.put("form", Arrays.asList("F1")); + + Assertions.assertEquals(Arrays.asList("F1"), demoService.testFormMapBody(forms)); + + exporter.unexport(); + } + + @Test + void testPrimitive() { + SpringRestDemoService server = getServerImpl(); + + URL nettyUrl = this.registerProvider(getUrl(), server, SpringRestDemoService.class); + + Exporter exporter = getExport(nettyUrl, server); + + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, nettyUrl)); + + Integer result = demoService.primitiveInt(1, 2); + Long resultLong = demoService.primitiveLong(1, 2l); + long resultByte = demoService.primitiveByte((byte) 1, 2l); + long resultShort = demoService.primitiveShort((short) 1, 2l, 1); + + assertThat(result, is(3)); + assertThat(resultShort, is(3l)); + assertThat(resultLong, is(3l)); + assertThat(resultByte, is(3l)); + + exporter.unexport(); + } + + @Test + void testExceptionHandler() { + SpringRestDemoService server = getServerImpl(); + + URL nettyUrl = registerProvider(getUrl(), server, SpringRestDemoService.class); + Exporter exporter = getExport(nettyUrl, server); + SpringRestDemoService demoService = proxy.getProxy(protocol.refer(SpringRestDemoService.class, nettyUrl)); + + String result = demoService.error(); + + assertThat(result, is("ok")); + + exporter.unexport(); + } + + @Test + void testProxyDoubleCheck() { + + ProxyCreatorSupport proxyCreatorSupport = new ProxyCreatorSupport(); + AdvisedSupport advisedSupport = new AdvisedSupport(); + advisedSupport.setTarget(getServerImpl()); + AopProxy aopProxy = proxyCreatorSupport.getAopProxyFactory().createAopProxy(advisedSupport); + Object proxy = aopProxy.getProxy(); + SpringRestDemoService server = (SpringRestDemoService) proxy; + + URL nettyUrl = this.registerProvider(getUrl(), server, SpringRestDemoService.class); + + Exporter exporter = getExport(nettyUrl, server); + + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, nettyUrl)); + + Integer result = demoService.primitiveInt(1, 2); + Long resultLong = demoService.primitiveLong(1, 2l); + long resultByte = demoService.primitiveByte((byte) 1, 2l); + long resultShort = demoService.primitiveShort((short) 1, 2l, 1); + + assertThat(result, is(3)); + assertThat(resultShort, is(3l)); + assertThat(resultLong, is(3l)); + assertThat(resultByte, is(3l)); + + exporter.unexport(); + } + + private URL registerProvider(URL url, Object impl, Class interfaceClass) { + ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass); + ProviderModel providerModel = new ProviderModel(url.getServiceKey(), impl, serviceDescriptor, null, null); + repository.registerProvider(providerModel); + return url.setServiceModel(providerModel); + } +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java new file mode 100644 index 00000000000..4696ac3971b --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/SpringRestDemoService.java @@ -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.compatible; + +import java.util.List; + +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +@RequestMapping("/demoService") +public interface SpringRestDemoService { + @RequestMapping(value = "/hello", method = RequestMethod.GET) + Integer hello(@RequestParam Integer a, @RequestParam Integer b); + + @RequestMapping(value = "/error", method = RequestMethod.GET, consumes = MediaType.TEXT_PLAIN_VALUE) + String error(); + + @RequestMapping(value = "/sayHello", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE) + String sayHello(String name); + + boolean isCalled(); + + @RequestMapping( + value = "/testFormBody", + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.TEXT_PLAIN_VALUE) + String testFormBody(@RequestBody User user); + + @RequestMapping( + value = "/testFormMapBody", + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + List testFormMapBody(@RequestBody LinkedMultiValueMap map); + + @RequestMapping(value = "/testHeader", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE) + String testHeader(@RequestHeader String header); + + @RequestMapping(value = "/testHeaderInt", method = RequestMethod.GET, consumes = MediaType.TEXT_PLAIN_VALUE) + String testHeaderInt(@RequestHeader int header); + + @RequestMapping(method = RequestMethod.GET, value = "/primitive") + int primitiveInt(@RequestParam("a") int a, @RequestParam("b") int b); + + @RequestMapping(method = RequestMethod.GET, value = "/primitiveLong") + long primitiveLong(@RequestParam("a") long a, @RequestParam("b") Long b); + + @RequestMapping(method = RequestMethod.GET, value = "/primitiveByte") + long primitiveByte(@RequestParam("a") byte a, @RequestParam("b") Long b); + + @RequestMapping(method = RequestMethod.POST, value = "/primitiveShort") + long primitiveShort(@RequestParam("a") short a, @RequestParam("b") Long b, @RequestBody int c); +} diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/User.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/User.java new file mode 100644 index 00000000000..492a0413807 --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/compatible/User.java @@ -0,0 +1,88 @@ +/* + * 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.compatible; + +import java.io.Serializable; +import java.util.Objects; + +/** + * User Entity + * + * @since 2.7.6 + */ +public class User implements Serializable { + + private Long id; + + private String name; + + private Integer age; + + 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 Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public static User getInstance() { + User user = new User(); + user.setAge(18); + user.setName("dubbo"); + user.setId(404l); + return user; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + User user = (User) o; + return Objects.equals(id, user.id) && Objects.equals(name, user.name) && Objects.equals(age, user.age); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, age); + } + + @Override + public String toString() { + return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; + } +} 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 new file mode 100644 index 00000000000..34d1c53e88a --- /dev/null +++ b/dubbo-plugin/dubbo-rest-spring/src/test/resources/log4j2-test.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/dubbo-plugin/pom.xml b/dubbo-plugin/pom.xml index af13d2673b5..a345ab5db26 100644 --- a/dubbo-plugin/pom.xml +++ b/dubbo-plugin/pom.xml @@ -28,7 +28,7 @@ pom dubbo-plugin - The registry module of dubbo project + The plugins of dubbo project dubbo-qos dubbo-auth @@ -38,9 +38,12 @@ dubbo-qos-api dubbo-native dubbo-compiler - dubbo-plugin-proxy-bytebuddy dubbo-filter-cache dubbo-filter-validation + dubbo-plugin-proxy-bytebuddy + dubbo-rest-jaxrs + dubbo-rest-servlet + dubbo-rest-spring false diff --git a/dubbo-remoting/dubbo-remoting-http12/pom.xml b/dubbo-remoting/dubbo-remoting-http12/pom.xml index 5549f939937..c81f5ae601a 100644 --- a/dubbo-remoting/dubbo-remoting-http12/pom.xml +++ b/dubbo-remoting/dubbo-remoting-http12/pom.xml @@ -63,11 +63,25 @@ javax.xml.bind jaxb-api + provided org.glassfish.jaxb jaxb-runtime + provided + + + + org.yaml + snakeyaml + provided + + + + org.apache.logging.log4j + log4j-slf4j-impl + test diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java index d13c4feeec0..a4086cc08f2 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java @@ -17,9 +17,13 @@ package org.apache.dubbo.remoting.http12; import org.apache.dubbo.remoting.http12.exception.EncodeException; +import org.apache.dubbo.remoting.http12.exception.HttpResultPayloadException; import org.apache.dubbo.remoting.http12.exception.HttpStatusException; import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; +import java.util.List; +import java.util.Map; + public abstract class AbstractServerHttpChannelObserver implements CustomizableHttpChannelObserver { private HeadersCustomizer headersCustomizer = HeadersCustomizer.NO_OP; @@ -72,12 +76,18 @@ protected TrailersCustomizer getTrailersCustomizer() { @Override public void onNext(Object data) { try { - if (!headerSent) { - doSendHeaders(HttpStatus.OK.getStatusString()); + if (data instanceof HttpResult) { + HttpResult result = (HttpResult) data; + if (!headerSent) { + doSendHeaders(String.valueOf(result.getStatus()), result.getHeaders()); + } + data = result.getBody(); + } else if (!headerSent) { + doSendHeaders(HttpStatus.OK.getStatusString(), null); } HttpOutputMessage outputMessage = encodeHttpOutputMessage(data); preOutputMessage(outputMessage); - this.responseEncoder.encode(outputMessage.getBody(), data); + responseEncoder.encode(outputMessage.getBody(), data); getHttpChannel().writeMessage(outputMessage); postOutputMessage(outputMessage); } catch (Throwable e) { @@ -101,20 +111,24 @@ protected HttpMetadata encodeTrailers(Throwable throwable) { @Override public void onError(Throwable throwable) { + if (throwable instanceof HttpResultPayloadException) { + onNext(((HttpResultPayloadException) throwable).getResult()); + return; + } int httpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR.getCode(); if (throwable instanceof HttpStatusException) { httpStatusCode = ((HttpStatusException) throwable).getStatusCode(); } if (!headerSent) { - doSendHeaders(String.valueOf(httpStatusCode)); + doSendHeaders(String.valueOf(httpStatusCode), null); } try { ErrorResponse errorResponse = new ErrorResponse(); errorResponse.setStatus(String.valueOf(httpStatusCode)); errorResponse.setMessage(throwable.getMessage()); - this.errorResponseCustomizer.accept(errorResponse, throwable); + errorResponseCustomizer.accept(errorResponse, throwable); HttpOutputMessage httpOutputMessage = encodeHttpOutputMessage(errorResponse); - this.responseEncoder.encode(httpOutputMessage.getBody(), errorResponse); + responseEncoder.encode(httpOutputMessage.getBody(), errorResponse); getHttpChannel().writeMessage(httpOutputMessage); } catch (Throwable ex) { throwable = new EncodeException(ex); @@ -133,17 +147,17 @@ public HttpChannel getHttpChannel() { return httpChannel; } - private void doSendHeaders(String statusCode) { + private void doSendHeaders(String statusCode, Map> additionalHeaders) { HttpMetadata httpMetadata = encodeHttpMetadata(); - httpMetadata.headers().set(HttpHeaderNames.STATUS.getName(), statusCode); - httpMetadata - .headers() - .set( - HttpHeaderNames.CONTENT_TYPE.getName(), - responseEncoder.mediaType().getName()); - this.headersCustomizer.accept(httpMetadata.headers()); + HttpHeaders headers = httpMetadata.headers(); + headers.set(HttpHeaderNames.STATUS.getName(), statusCode); + headers.set(HttpHeaderNames.CONTENT_TYPE.getName(), responseEncoder.contentType()); + headersCustomizer.accept(headers); + if (additionalHeaders != null) { + headers.putAll(additionalHeaders); + } getHttpChannel().writeHeader(httpMetadata); - this.headerSent = true; + headerSent = true; } protected void doOnCompleted(Throwable throwable) { @@ -151,7 +165,7 @@ protected void doOnCompleted(Throwable throwable) { if (httpMetadata == null) { return; } - this.trailersCustomizer.accept(httpMetadata.headers(), throwable); + trailersCustomizer.accept(httpMetadata.headers(), throwable); getHttpChannel().writeHeader(httpMetadata); } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/ErrorResponse.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/ErrorResponse.java index efe5c2bb4d6..4de2bc58264 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/ErrorResponse.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/ErrorResponse.java @@ -16,10 +16,14 @@ */ package org.apache.dubbo.remoting.http12; +import org.apache.dubbo.common.utils.ToStringUtils; + import java.io.Serializable; public class ErrorResponse implements Serializable { + private static final long serialVersionUID = 6828386002146790334L; + private String status; private String message; @@ -39,4 +43,9 @@ public String getMessage() { public void setMessage(String message) { this.message = message; } + + @Override + public String toString() { + return ToStringUtils.printToString(this); + } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpChannel.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpChannel.java index 795bf8ee377..67c2a1cfebb 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpChannel.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpChannel.java @@ -29,5 +29,7 @@ public interface HttpChannel { SocketAddress remoteAddress(); + SocketAddress localAddress(); + void flush(); } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpCookie.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpCookie.java new file mode 100644 index 00000000000..76f3bdb8e54 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpCookie.java @@ -0,0 +1,123 @@ +/* + * 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; + +import org.apache.dubbo.common.utils.Assert; +import org.apache.dubbo.common.utils.StringUtils; + +public final class HttpCookie { + + private final String name; + private String value; + private String domain; + private String path; + private long maxAge = Long.MIN_VALUE; + private boolean secure; + private boolean httpOnly; + private String sameSite; + + public HttpCookie(String name, String value) { + name = StringUtils.trim(name); + Assert.notEmptyString(name, "name is required"); + this.name = name; + setValue(value); + } + + public String name() { + return name; + } + + public String value() { + return value; + } + + public void setValue(String value) { + Assert.notNull(name, "value can not be null"); + this.value = value; + } + + public String domain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public String path() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public long maxAge() { + return maxAge; + } + + public void setMaxAge(long maxAge) { + this.maxAge = maxAge; + } + + public boolean secure() { + return secure; + } + + public void setSecure(boolean secure) { + this.secure = secure; + } + + public boolean httpOnly() { + return httpOnly; + } + + public void setHttpOnly(boolean httpOnly) { + this.httpOnly = httpOnly; + } + + public String sameSite() { + return sameSite; + } + + public void setSameSite(String sameSite) { + this.sameSite = sameSite; + } + + public String toString() { + StringBuilder buf = new StringBuilder(name).append('=').append(value); + if (domain != null) { + buf.append(", domain=").append(domain); + } + if (path != null) { + buf.append(", path=").append(path); + } + if (maxAge >= 0) { + buf.append(", maxAge=").append(maxAge).append('s'); + } + if (secure) { + buf.append(", secure"); + } + if (httpOnly) { + buf.append(", HTTPOnly"); + } + if (sameSite != null) { + buf.append(", SameSite=").append(sameSite); + } + return buf.toString(); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java new file mode 100644 index 00000000000..8cdf0e9f735 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java @@ -0,0 +1,64 @@ +/* + * 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; + +import java.nio.charset.StandardCharsets; + +public enum HttpMethods { + GET, + POST, + PUT, + DELETE, + HEAD, + OPTIONS, + PATCH, + TRACE; + + public static final byte[][] HTTP_METHODS_BYTES; + + static { + HttpMethods[] methods = values(); + int len = methods.length; + HTTP_METHODS_BYTES = new byte[len][]; + for (int i = 0; i < len; i++) { + HTTP_METHODS_BYTES[i] = methods[i].name().getBytes(StandardCharsets.ISO_8859_1); + } + } + + @SuppressWarnings("StringEquality") + public static HttpMethods of(String name) { + // fast-path + if (name == GET.name()) { + return GET; + } else if (name == POST.name()) { + return POST; + } + return valueOf(name); + } + + public static boolean isGet(String name) { + return GET.name().equals(name); + } + + public static boolean isPost(String name) { + return POST.name().equals(name); + } + + public static boolean supportBody(String name) { + return name.charAt(0) == 'P'; + } +} 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 new file mode 100644 index 00000000000..50fcc001d73 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java @@ -0,0 +1,161 @@ +/* + * 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; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public interface HttpRequest extends RequestMetadata { + + boolean isHttp2(); + + String method(); + + void setMethod(String method); + + String uri(); + + void setUri(String uri); + + String path(); + + String rawPath(); + + String query(); + + String header(String name); + + List headerValues(String name); + + Date dateHeader(String name); + + boolean hasHeader(String name); + + Collection headerNames(); + + HttpHeaders headers(); + + void setHeader(String name, String value); + + void setHeader(String name, List values); + + void setHeader(String name, Date value); + + Collection cookies(); + + HttpCookie cookie(String name); + + int contentLength(); + + String contentType(); + + void setContentType(String contentType); + + String mediaType(); + + String charset(); + + Charset charsetOrDefault(); + + void setCharset(String charset); + + String accept(); + + Locale locale(); + + List locales(); + + String scheme(); + + String serverHost(); + + String serverName(); + + int serverPort(); + + String remoteHost(); + + String remoteAddr(); + + int remotePort(); + + String localHost(); + + String localAddr(); + + int localPort(); + + String parameter(String name); + + String parameter(String name, String defaultValue); + + List parameterValues(String name); + + String queryParameter(String name); + + List queryParameterValues(String name); + + Collection queryParameterNames(); + + String formParameter(String name); + + List formParameterValues(String name); + + Collection formParameterNames(); + + boolean hasParameter(String name); + + Collection parameterNames(); + + Collection parts(); + + FileUpload part(String name); + + T attribute(String name); + + void removeAttribute(String name); + + void setAttribute(String name, Object value); + + boolean hasAttribute(String name); + + Collection attributeNames(); + + Map attributes(); + + InputStream inputStream(); + + void setInputStream(InputStream is); + + interface FileUpload { + + String name(); + + String filename(); + + String contentType(); + + int size(); + + InputStream inputStream(); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpResponse.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpResponse.java new file mode 100644 index 00000000000..f52bb9ac8cd --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpResponse.java @@ -0,0 +1,98 @@ +/* + * 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; + +import java.io.OutputStream; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public interface HttpResponse { + + int status(); + + void setStatus(int status); + + String header(String name); + + Date dateHeader(String name); + + List headerValues(String name); + + boolean hasHeader(String name); + + Collection headerNames(); + + Map> headers(); + + void addHeader(String name, String value); + + void addHeader(String name, Date value); + + void setHeader(String name, String value); + + void setHeader(String name, Date value); + + void setHeader(String name, List value); + + void addCookie(HttpCookie cookie); + + String contentType(); + + void setContentType(String contentType); + + String mediaType(); + + String charset(); + + void setCharset(String charset); + + String locale(); + + void setLocale(String locale); + + Object body(); + + void setBody(Object body); + + OutputStream outputStream(); + + void setOutputStream(OutputStream os); + + void sendRedirect(String location); + + void sendError(int status); + + void sendError(int status, String message); + + boolean isEmpty(); + + boolean isContentEmpty(); + + boolean isCommitted(); + + void commit(); + + void setCommitted(boolean committed); + + void reset(); + + void resetBuffer(); + + HttpResult toHttpResult(); +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpResult.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpResult.java new file mode 100644 index 00000000000..d47dd56b16e --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpResult.java @@ -0,0 +1,79 @@ +/* + * 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; + +import org.apache.dubbo.remoting.http12.message.DefaultHttpResult.Builder; + +import java.util.List; +import java.util.Map; + +public interface HttpResult { + + int getStatus(); + + Map> getHeaders(); + + T getBody(); + + static Builder builder() { + return new Builder<>(); + } + + static Builder builder(T body) { + return new Builder().body(body); + } + + static HttpResult status(int status) { + return new Builder().status(status).build(); + } + + static HttpResult status(HttpStatus status) { + return new Builder().status(status).build(); + } + + static HttpResult ok() { + return new Builder().status(HttpStatus.OK).build(); + } + + static HttpResult found(String url) { + return new Builder().found(url).build(); + } + + static HttpResult badRequest() { + return new Builder().status(HttpStatus.BAD_REQUEST).build(); + } + + static HttpResult notFound() { + return new Builder().status(HttpStatus.NOT_FOUND).build(); + } + + static HttpResult error() { + return new Builder().status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + + static HttpResult error(String message) { + return error(HttpStatus.INTERNAL_SERVER_ERROR, message); + } + + static HttpResult error(int status, String message) { + return new Builder().status(status).body(message).build(); + } + + static HttpResult error(HttpStatus status, String message) { + return new Builder().status(status).body(message).build(); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java index a5c2e2c9332..f91b6280280 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpStatus.java @@ -18,7 +18,17 @@ public enum HttpStatus { OK(200), + CREATED(201), + ACCEPTED(202), + FOUND(302), + BAD_REQUEST(400), + UNAUTHORIZED(401), + FORBIDDEN(403), + NOT_FOUND(404), + METHOD_NOT_ALLOWED(405), + PRECONDITION_FAILED(412), REQUEST_TIMEOUT(408), + CONFLICT(409), UNSUPPORTED_MEDIA_TYPE(415), INTERNAL_SERVER_ERROR(500); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpUtils.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpUtils.java new file mode 100644 index 00000000000..128c57854a9 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpUtils.java @@ -0,0 +1,255 @@ +/* + * 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; + +import org.apache.dubbo.common.io.StreamUtils; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.exception.DecodeException; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.handler.codec.http.cookie.CookieHeaderNames.SameSite; +import io.netty.handler.codec.http.cookie.DefaultCookie; +import io.netty.handler.codec.http.cookie.ServerCookieDecoder; +import io.netty.handler.codec.http.cookie.ServerCookieEncoder; +import io.netty.handler.codec.http.multipart.Attribute; +import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; +import io.netty.handler.codec.http.multipart.FileUpload; +import io.netty.handler.codec.http.multipart.HttpDataFactory; +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; +import io.netty.handler.codec.http.multipart.InterfaceHttpData; + +public final class HttpUtils { + + public static final HttpDataFactory DATA_FACTORY = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); + public static final String CHARSET_PREFIX = "charset="; + + private HttpUtils() {} + + public static List decodeCookies(String value) { + List cookies = new ArrayList<>(); + for (Cookie c : ServerCookieDecoder.LAX.decodeAll(value)) { + cookies.add(new HttpCookie(c.name(), c.value())); + } + return cookies; + } + + public static String encodeCookie(HttpCookie cookie) { + DefaultCookie c = new DefaultCookie(cookie.name(), cookie.value()); + c.setPath(cookie.path()); + c.setDomain(cookie.domain()); + c.setMaxAge(cookie.maxAge()); + c.setSecure(cookie.secure()); + c.setHttpOnly(cookie.httpOnly()); + c.setSameSite(SameSite.valueOf(cookie.sameSite())); + return ServerCookieEncoder.LAX.encode(c); + } + + public static List parseAccept(String header) { + List> mediaTypes = new ArrayList<>(); + if (header == null) { + return Collections.emptyList(); + } + for (String item : StringUtils.tokenize(header, ',')) { + int index = item.indexOf(';'); + mediaTypes.add(new Item<>(StringUtils.substring(item, 0, index), parseQuality(item, index))); + } + return Item.sortAndGet(mediaTypes); + } + + public static float parseQuality(String expr, int index) { + float quality = 1.0F; + if (index != -1) { + int qStart = expr.indexOf("q=", index + 1); + if (qStart != -1) { + qStart += 2; + int qEnd = expr.indexOf(',', qStart); + String qString = qEnd == -1 + ? expr.substring(qStart) + : expr.substring(qStart, qEnd).trim(); + try { + quality = Float.parseFloat(qString); + } catch (NumberFormatException ignored) { + } + } + } + return quality; + } + + public static List parseAcceptLanguage(String header) { + List> locales = new ArrayList<>(); + if (header == null) { + return Collections.emptyList(); + } + for (String item : StringUtils.tokenize(header, ',')) { + String[] pair = StringUtils.tokenize(item, ';'); + locales.add(new Item<>(parseLocale(pair[0]), pair.length > 1 ? Float.parseFloat(pair[1]) : 1.0F)); + } + return Item.sortAndGet(locales); + } + + public static List parseContentLanguage(String header) { + List locales = new ArrayList<>(); + if (header == null) { + return Collections.emptyList(); + } + for (String item : StringUtils.tokenize(header, ',')) { + locales.add(parseLocale(item)); + } + return locales; + } + + public static Locale parseLocale(String locale) { + String[] parts = StringUtils.tokenize(locale, '-', '_'); + switch (parts.length) { + case 2: + return new Locale(parts[0], parts[1]); + case 3: + return new Locale(parts[0], parts[1], parts[2]); + default: + return new Locale(parts[0]); + } + } + + @SuppressWarnings("deprecation") + public static HttpPostRequestDecoder createPostRequestDecoder( + HttpRequest request, InputStream inputStream, String charset) { + ByteBuf data; + boolean canMark = inputStream.markSupported(); + try { + if (canMark) { + inputStream.mark(Integer.MAX_VALUE); + } + data = Unpooled.wrappedBuffer(StreamUtils.readBytes(inputStream)); + } catch (IOException e) { + throw new DecodeException("Error while reading post data: " + e.getMessage(), e); + } finally { + try { + if (canMark) { + inputStream.reset(); + } else { + inputStream.close(); + } + } catch (IOException ignored) { + } + } + DefaultFullHttpRequest nRequest = new DefaultFullHttpRequest( + HttpVersion.HTTP_1_1, + HttpMethod.POST, + request.uri(), + data, + new DefaultHttpHeaders(), + new DefaultHttpHeaders(false)); + request.headers().forEach(nRequest.headers()::set); + if (charset == null) { + return new HttpPostRequestDecoder(DATA_FACTORY, nRequest); + } else { + return new HttpPostRequestDecoder(DATA_FACTORY, nRequest, Charset.forName(charset)); + } + } + + public static String readPostValue(InterfaceHttpData item) { + try { + return ((Attribute) item).getValue(); + } catch (IOException e) { + throw new DecodeException("Error while reading post value: " + e.getMessage(), e); + } + } + + public static HttpRequest.FileUpload readUpload(InterfaceHttpData item) { + return new DefaultFileUploadAdaptee((FileUpload) item); + } + + private static class DefaultFileUploadAdaptee implements HttpRequest.FileUpload { + private final FileUpload fu; + private InputStream inputStream; + + DefaultFileUploadAdaptee(FileUpload fu) { + this.fu = fu; + } + + @Override + public String name() { + return fu.getName(); + } + + @Override + public String filename() { + return fu.getFilename(); + } + + @Override + public String contentType() { + return fu.getContentType(); + } + + @Override + public int size() { + return (int) fu.length(); + } + + @Override + public InputStream inputStream() { + if (inputStream == null) { + inputStream = new ByteBufInputStream(fu.content()); + } + return inputStream; + } + } + + private static final class Item implements Comparable> { + private final V value; + private final float q; + + public Item(V value, float q) { + this.value = value; + this.q = q; + } + + @Override + public int compareTo(Item o) { + return Float.compare(o.q, q); + } + + public static List sortAndGet(List> items) { + int size = items.size(); + if (size == 1) { + return Collections.singletonList(items.get(0).value); + } + Collections.sort(items); + List values = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + values.add(items.get(i).value); + } + return values; + } + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/DecodeException.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/DecodeException.java index af426da47d4..78dfa937347 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/DecodeException.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/DecodeException.java @@ -29,4 +29,8 @@ public DecodeException(String message) { public DecodeException(Throwable cause) { super(500, cause); } + + public DecodeException(String message, Throwable cause) { + super(500, message, cause); + } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/HttpResultPayloadException.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/HttpResultPayloadException.java new file mode 100644 index 00000000000..66253da9438 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/HttpResultPayloadException.java @@ -0,0 +1,51 @@ +/* + * 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.exception; + +import org.apache.dubbo.remoting.http12.HttpResult; +import org.apache.dubbo.remoting.http12.HttpStatus; + +public class HttpResultPayloadException extends HttpStatusException { + + private static final long serialVersionUID = 1L; + + private final HttpResult result; + + public HttpResultPayloadException(HttpResult result) { + super(result.getStatus()); + this.result = result; + } + + public HttpResultPayloadException(int statusCode, Object body) { + super(statusCode); + result = HttpResult.builder(body).status(statusCode).build(); + } + + public HttpResultPayloadException(Object body) { + super(HttpStatus.OK.getCode()); + result = HttpResult.builder(body).ok().build(); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + + public HttpResult getResult() { + return result; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/HttpStatusException.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/HttpStatusException.java index dd9104aa5fc..bf86615da11 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/HttpStatusException.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/HttpStatusException.java @@ -34,6 +34,11 @@ public HttpStatusException(int statusCode, Throwable cause) { this.statusCode = statusCode; } + public HttpStatusException(int statusCode, String message, Throwable cause) { + super(message, cause); + this.statusCode = statusCode; + } + public int getStatusCode() { return statusCode; } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2ChannelDelegate.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2ChannelDelegate.java index 9c5aaea1042..994ceaee965 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2ChannelDelegate.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2ChannelDelegate.java @@ -49,6 +49,11 @@ public SocketAddress remoteAddress() { return h2StreamChannel.remoteAddress(); } + @Override + public SocketAddress localAddress() { + return h2StreamChannel.localAddress(); + } + @Override public void flush() { h2StreamChannel.flush(); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpMessageAdapterFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpMessageAdapterFactory.java new file mode 100644 index 00000000000..009832c533c --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpMessageAdapterFactory.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.remoting.http12.message; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.http12.HttpChannel; +import org.apache.dubbo.remoting.http12.HttpMetadata; +import org.apache.dubbo.remoting.http12.HttpResponse; + +@Activate +public final class DefaultHttpMessageAdapterFactory + implements HttpMessageAdapterFactory { + + @Override + public DefaultHttpRequest adaptRequest(HttpMetadata rawRequest, HttpChannel channel) { + return new DefaultHttpRequest(rawRequest, channel); + } + + @Override + public HttpResponse adaptResponse(DefaultHttpRequest request, HttpMetadata rawRequest, Void rawResponse) { + return new DefaultHttpResponse(); + } +} 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 new file mode 100644 index 00000000000..41dea99f99f --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java @@ -0,0 +1,679 @@ +/* + * 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.message; + +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpChannel; +import org.apache.dubbo.remoting.http12.HttpCookie; +import org.apache.dubbo.remoting.http12.HttpHeaderNames; +import org.apache.dubbo.remoting.http12.HttpHeaders; +import org.apache.dubbo.remoting.http12.HttpMetadata; +import org.apache.dubbo.remoting.http12.HttpMethods; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpUtils; +import org.apache.dubbo.remoting.http12.RequestMetadata; +import org.apache.dubbo.remoting.http12.h2.Http2Header; + +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import io.netty.handler.codec.DateFormatter; +import io.netty.handler.codec.http.QueryStringDecoder; +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; +import io.netty.handler.codec.http.multipart.InterfaceHttpData; +import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType; + +public class DefaultHttpRequest implements HttpRequest { + + private final HttpMetadata metadata; + private final HttpChannel channel; + private final HttpHeaders headers; + + private String method; + private String uri; + private String contentType; + private String charset; + private List cookies; + private List locales; + private QueryStringDecoder decoder; + private HttpPostRequestDecoder postDecoder; + private boolean postParsed; + private Map attributes; + private InputStream inputStream; + + public DefaultHttpRequest(HttpMetadata metadata, HttpChannel channel) { + this.metadata = metadata; + this.channel = channel; + headers = metadata.headers(); + if (metadata instanceof RequestMetadata) { + RequestMetadata requestMetadata = (RequestMetadata) metadata; + method = requestMetadata.method(); + uri = requestMetadata.path(); + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + public boolean isHttp2() { + return metadata instanceof Http2Header; + } + + @Override + public String method() { + return method; + } + + @Override + public void setMethod(String method) { + this.method = method; + } + + @Override + public String uri() { + return uri; + } + + @Override + public void setUri(String uri) { + this.uri = uri; + decoder = null; + } + + @Override + public String path() { + return getDecoder().path(); + } + + @Override + public String rawPath() { + return getDecoder().rawPath(); + } + + @Override + public String query() { + return getDecoder().rawQuery(); + } + + @Override + public String header(String name) { + return headers.getFirst(name); + } + + @Override + public List headerValues(String name) { + return headers.get(name); + } + + @Override + public Date dateHeader(String name) { + String value = headers.getFirst(name); + return StringUtils.isEmpty(value) ? null : DateFormatter.parseHttpDate(value); + } + + @Override + public boolean hasHeader(String name) { + return headers.containsKey(name); + } + + @Override + public Collection headerNames() { + return headers.keySet(); + } + + @Override + public HttpHeaders headers() { + return headers; + } + + @Override + public void setHeader(String name, String value) { + headers.set(name, value); + } + + @Override + public void setHeader(String name, Date value) { + headers.set(name, DateFormatter.format(value)); + } + + @Override + public void setHeader(String name, List values) { + headers.put(name, values); + } + + @Override + public Collection cookies() { + List cookies = this.cookies; + if (cookies == null) { + cookies = HttpUtils.decodeCookies(header("cookie")); + this.cookies = cookies; + } + return cookies; + } + + @Override + public HttpCookie cookie(String name) { + List cookies = this.cookies; + if (cookies == null) { + cookies = HttpUtils.decodeCookies(header("cookie")); + this.cookies = cookies; + } + for (int i = 0, size = cookies.size(); i < size; i++) { + HttpCookie cookie = cookies.get(i); + if (cookie.name().equals(name)) { + return cookie; + } + } + return null; + } + + @Override + public int contentLength() { + String value = headers.getFirst(HttpHeaderNames.CONTENT_LENGTH.getName()); + return value == null ? 0 : Integer.parseInt(value); + } + + @Override + public String contentType() { + String contentType = this.contentType; + if (contentType == null) { + contentType = headers.getFirst(HttpHeaderNames.CONTENT_TYPE.getName()); + contentType = contentType == null ? StringUtils.EMPTY_STRING : contentType.trim(); + this.contentType = contentType; + } + return contentType.isEmpty() ? null : contentType; + } + + @Override + public void setContentType(String contentType) { + setContentType0(contentType == null ? StringUtils.EMPTY_STRING : contentType.trim()); + charset = null; + } + + private void setContentType0(String contentType) { + this.contentType = contentType; + headers.set(HttpHeaderNames.CONTENT_TYPE.getName(), contentType()); + } + + @Override + public String mediaType() { + String contentType = contentType(); + if (contentType == null) { + return null; + } + int index = contentType.indexOf(';'); + return index == -1 ? contentType : contentType.substring(0, index); + } + + @Override + public String charset() { + String charset = this.charset; + if (charset == null) { + String contentType = contentType(); + if (contentType == null) { + charset = StringUtils.EMPTY_STRING; + } else { + int index = contentType.lastIndexOf(HttpUtils.CHARSET_PREFIX); + charset = index == -1 + ? StringUtils.EMPTY_STRING + : contentType.substring(index + 8).trim(); + } + this.charset = charset; + } + return charset.isEmpty() ? null : charset; + } + + @Override + public Charset charsetOrDefault() { + String charset = charset(); + return charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset); + } + + @Override + public void setCharset(String charset) { + String contentType = contentType(); + if (contentType != null) { + setContentType0(contentType + "; " + HttpUtils.CHARSET_PREFIX + charset); + } + this.charset = charset; + } + + @Override + public String accept() { + return headers.getFirst(HttpHeaderNames.ACCEPT.getName()); + } + + @Override + public Locale locale() { + return locales().get(0); + } + + @Override + public List locales() { + List locales = this.locales; + if (locales == null) { + locales = HttpUtils.parseAcceptLanguage(headers.getFirst("accept-language")); + if (locales.isEmpty()) { + locales.add(Locale.getDefault()); + } + this.locales = locales; + } + return locales; + } + + @Override + public String scheme() { + String scheme = headers.getFirst("x-forwarded-proto"); + if (isHttp2()) { + scheme = headers.getFirst(":scheme"); + } + return scheme == null ? "https" : scheme; + } + + @Override + public String serverHost() { + String host = getHost0(); + return host == null ? localHost() + ':' + localPort() : host; + } + + @Override + public String serverName() { + String host = getHost0(); + if (host != null) { + int index = host.lastIndexOf(':'); + return index == -1 ? host : host.substring(0, index); + } + return localHost(); + } + + @Override + public int serverPort() { + String port = headers.getFirst("x-forwarded-port"); + if (port != null) { + return Integer.parseInt(port); + } + String host = getHost0(); + if (host != null) { + int index = host.lastIndexOf(':'); + return index == -1 ? -1 : Integer.parseInt(host.substring(0, index)); + } + return localPort(); + } + + private String getHost0() { + return headers.getFirst(isHttp2() ? ":authority" : "host"); + } + + @Override + public String remoteHost() { + return getRemoteAddress().getHostString(); + } + + @Override + public String remoteAddr() { + return getRemoteAddress().getAddress().getHostAddress(); + } + + @Override + public int remotePort() { + return getRemoteAddress().getPort(); + } + + private InetSocketAddress getRemoteAddress() { + return (InetSocketAddress) channel.remoteAddress(); + } + + @Override + public String localHost() { + return getLocalAddress().getHostString(); + } + + @Override + public String localAddr() { + return getLocalAddress().getAddress().getHostAddress(); + } + + @Override + public int localPort() { + return getLocalAddress().getPort(); + } + + private InetSocketAddress getLocalAddress() { + return (InetSocketAddress) channel.localAddress(); + } + + @Override + public String parameter(String name) { + List values = getDecoder().parameters().get(name); + if (CollectionUtils.isNotEmpty(values)) { + return values.get(0); + } + HttpPostRequestDecoder postDecoder = getPostDecoder(); + if (postDecoder == null) { + return null; + } + List items = postDecoder.getBodyHttpDatas(name); + if (items == null) { + return null; + } + for (int i = 0, size = items.size(); i < size; i++) { + InterfaceHttpData item = items.get(i); + if (item.getHttpDataType() == HttpDataType.Attribute) { + return HttpUtils.readPostValue(item); + } + } + return formParameter(name); + } + + @Override + public String parameter(String name, String defaultValue) { + String value = parameter(name); + return value == null ? defaultValue : value; + } + + @Override + public List parameterValues(String name) { + List values = getDecoder().parameters().get(name); + HttpPostRequestDecoder postDecoder = getPostDecoder(); + if (postDecoder == null) { + return values; + } + List items = postDecoder.getBodyHttpDatas(name); + if (items == null) { + return values; + } + for (int i = 0, size = items.size(); i < size; i++) { + InterfaceHttpData item = items.get(i); + if (item.getHttpDataType() == HttpDataType.Attribute) { + if (values == null) { + values = new ArrayList<>(); + } + values.add(HttpUtils.readPostValue(item)); + } + } + return values; + } + + @Override + public String queryParameter(String name) { + return CollectionUtils.first(queryParameterValues(name)); + } + + @Override + public List queryParameterValues(String name) { + return getDecoder().parameters().get(name); + } + + @Override + public Collection queryParameterNames() { + return getDecoder().parameters().keySet(); + } + + @Override + public String formParameter(String name) { + HttpPostRequestDecoder postDecoder = getPostDecoder(); + if (postDecoder == null) { + return null; + } + List items = postDecoder.getBodyHttpDatas(name); + if (items == null) { + return null; + } + for (int i = 0, size = items.size(); i < size; i++) { + InterfaceHttpData item = items.get(i); + if (item.getHttpDataType() == HttpDataType.Attribute) { + return HttpUtils.readPostValue(item); + } + } + return null; + } + + @Override + public List formParameterValues(String name) { + HttpPostRequestDecoder postDecoder = getPostDecoder(); + if (postDecoder == null) { + return null; + } + List items = postDecoder.getBodyHttpDatas(name); + if (items == null) { + return null; + } + List values = null; + for (int i = 0, size = items.size(); i < size; i++) { + InterfaceHttpData item = items.get(i); + if (item.getHttpDataType() == HttpDataType.Attribute) { + if (values == null) { + values = new ArrayList<>(); + } + values.add(HttpUtils.readPostValue(item)); + } + } + return values; + } + + @Override + public Collection formParameterNames() { + HttpPostRequestDecoder postDecoder = getPostDecoder(); + if (postDecoder == null) { + return Collections.emptyList(); + } + List items = postDecoder.getBodyHttpDatas(); + if (items == null) { + return Collections.emptyList(); + } + Set names = null; + for (int i = 0, size = items.size(); i < size; i++) { + InterfaceHttpData item = items.get(i); + if (item.getHttpDataType() == HttpDataType.Attribute) { + if (names == null) { + names = new LinkedHashSet<>(); + } + names.add(item.getName()); + } + } + return names; + } + + @Override + public boolean hasParameter(String name) { + if (getDecoder().parameters().containsKey(name)) { + return true; + } + HttpPostRequestDecoder postDecoder = getPostDecoder(); + if (postDecoder == null) { + return false; + } + List items = postDecoder.getBodyHttpDatas(name); + if (items == null) { + return false; + } + for (int i = 0, size = items.size(); i < size; i++) { + InterfaceHttpData item = items.get(i); + if (item.getHttpDataType() == HttpDataType.Attribute) { + return true; + } + } + return false; + } + + @Override + public Collection parameterNames() { + Set names = getDecoder().parameters().keySet(); + HttpPostRequestDecoder postDecoder = getPostDecoder(); + if (postDecoder == null) { + return names; + } + List items = postDecoder.getBodyHttpDatas(); + if (items == null) { + return names; + } + Set allNames = null; + for (int i = 0, size = items.size(); i < size; i++) { + InterfaceHttpData item = items.get(i); + if (item.getHttpDataType() == HttpDataType.Attribute) { + if (allNames == null) { + allNames = new LinkedHashSet<>(names); + } + allNames.add(item.getName()); + } + } + return allNames; + } + + @Override + public Collection parts() { + HttpPostRequestDecoder postDecoder = getPostDecoder(); + if (postDecoder == null) { + return Collections.emptyList(); + } + List items = postDecoder.getBodyHttpDatas(); + if (items == null) { + return Collections.emptyList(); + } + List fileUploads = new ArrayList<>(); + for (int i = 0, size = items.size(); i < size; i++) { + InterfaceHttpData item = items.get(i); + if (item.getHttpDataType() == HttpDataType.FileUpload) { + fileUploads.add(HttpUtils.readUpload(item)); + } + } + return fileUploads; + } + + @Override + public FileUpload part(String name) { + HttpPostRequestDecoder postDecoder = getPostDecoder(); + if (postDecoder == null) { + return null; + } + List items = postDecoder.getBodyHttpDatas(name); + if (items == null) { + return null; + } + + for (int i = 0, size = items.size(); i < size; i++) { + InterfaceHttpData item = items.get(i); + if (item.getHttpDataType() == HttpDataType.FileUpload) { + return HttpUtils.readUpload(item); + } + } + return null; + } + + private QueryStringDecoder getDecoder() { + if (decoder == null) { + String charset = charset(); + if (charset == null) { + decoder = new QueryStringDecoder(uri); + } else { + decoder = new QueryStringDecoder(uri, Charset.forName(charset)); + } + } + return decoder; + } + + private HttpPostRequestDecoder getPostDecoder() { + HttpPostRequestDecoder postDecoder = this.postDecoder; + if (postDecoder == null) { + if (postParsed) { + return null; + } + if (inputStream != null && HttpMethods.supportBody(method)) { + postDecoder = HttpUtils.createPostRequestDecoder(this, inputStream, charset()); + this.postDecoder = postDecoder; + } + postParsed = true; + } + return postDecoder; + } + + @Override + @SuppressWarnings("unchecked") + public T attribute(String name) { + return (T) getAttributes().get(name); + } + + @Override + public void removeAttribute(String name) { + getAttributes().remove(name); + } + + @Override + public void setAttribute(String name, Object value) { + getAttributes().put(name, value); + } + + @Override + public boolean hasAttribute(String name) { + return attributes != null && attributes.containsKey(name); + } + + @Override + public Collection attributeNames() { + return getAttributes().keySet(); + } + + @Override + public Map attributes() { + return getAttributes(); + } + + private Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + attributes = new HashMap<>(); + this.attributes = attributes; + } + return attributes; + } + + @Override + public InputStream inputStream() { + return inputStream; + } + + @Override + public void setInputStream(InputStream is) { + inputStream = is; + if (HttpMethods.isPost(method)) { + postDecoder = null; + postParsed = false; + } + } + + @Override + public String toString() { + return "DefaultHttpRequest{" + fieldToString() + '}'; + } + + protected final String fieldToString() { + return "method='" + method + '\'' + ", uri='" + uri + '\'' + ", contentType='" + contentType() + '\''; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpResponse.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpResponse.java new file mode 100644 index 00000000000..db59a2c6f68 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpResponse.java @@ -0,0 +1,368 @@ +/* + * 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.message; + +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpCookie; +import org.apache.dubbo.remoting.http12.HttpHeaderNames; +import org.apache.dubbo.remoting.http12.HttpHeaders; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.HttpResult; +import org.apache.dubbo.remoting.http12.HttpStatus; +import org.apache.dubbo.remoting.http12.HttpUtils; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import io.netty.handler.codec.DateFormatter; + +public class DefaultHttpResponse implements HttpResponse { + + private final HttpHeaders headers = new HttpHeaders(); + + private int status; + private String contentType; + private String charset; + private Object body; + private OutputStream outputStream; + private volatile boolean committed; + + @Override + public int status() { + return status; + } + + @Override + public void setStatus(int status) { + if (committed) { + return; + } + this.status = status; + } + + @Override + public String header(String name) { + return headers.getFirst(name); + } + + @Override + public Date dateHeader(String name) { + String value = headers.getFirst(name); + return StringUtils.isEmpty(value) ? null : DateFormatter.parseHttpDate(value); + } + + @Override + public List headerValues(String name) { + return headers.get(name); + } + + @Override + public boolean hasHeader(String name) { + return headers.containsKey(name); + } + + @Override + public Collection headerNames() { + return headers.keySet(); + } + + @Override + public Map> headers() { + return Collections.unmodifiableMap(headers); + } + + @Override + public void addHeader(String name, String value) { + if (committed) { + return; + } + headers.computeIfAbsent(name, k -> new ArrayList<>()).add(value); + } + + @Override + public void addHeader(String name, Date value) { + addHeader(name, DateFormatter.format(value)); + } + + @Override + public void setHeader(String name, String value) { + if (committed) { + return; + } + headers.set(name, value); + } + + @Override + public void setHeader(String name, Date value) { + setHeader(name, DateFormatter.format(value)); + } + + @Override + public void setHeader(String name, List values) { + if (committed) { + return; + } + headers.put(name, values); + } + + @Override + public void addCookie(HttpCookie cookie) { + addHeader("set-cookie", HttpUtils.encodeCookie(cookie)); + } + + @Override + public String contentType() { + String contentType = this.contentType; + if (contentType == null) { + contentType = headers.getFirst(HttpHeaderNames.CONTENT_TYPE.getName()); + contentType = contentType == null ? StringUtils.EMPTY_STRING : contentType.trim(); + this.contentType = contentType; + } + return contentType.isEmpty() ? null : contentType; + } + + @Override + public void setContentType(String contentType) { + if (committed) { + return; + } + this.contentType = contentType; + charset = null; + } + + @Override + public String mediaType() { + String contentType = contentType(); + if (contentType == null) { + return null; + } + int index = contentType.indexOf(';'); + return index == -1 ? contentType : contentType.substring(0, index); + } + + @Override + public String charset() { + String charset = this.charset; + if (charset == null) { + String contentType = contentType(); + if (contentType == null) { + charset = StringUtils.EMPTY_STRING; + } else { + int index = contentType.lastIndexOf(HttpUtils.CHARSET_PREFIX); + charset = index == -1 + ? StringUtils.EMPTY_STRING + : contentType.substring(index + 8).trim(); + } + this.charset = charset; + } + return charset.isEmpty() ? null : charset; + } + + @Override + public void setCharset(String charset) { + if (committed) { + return; + } + String contentType = contentType(); + if (contentType != null) { + this.contentType = contentType + "; " + HttpUtils.CHARSET_PREFIX + charset; + } + this.charset = charset; + } + + @Override + public String locale() { + return headers.getFirst("content-language"); + } + + @Override + public void setLocale(String locale) { + if (committed) { + return; + } + headers.set("content-language", locale); + } + + @Override + public Object body() { + return body; + } + + @Override + public void setBody(Object body) { + if (committed) { + return; + } + this.body = body; + } + + @Override + public OutputStream outputStream() { + if (outputStream == null) { + outputStream = new ByteArrayOutputStream(1024); + } + return outputStream; + } + + @Override + public void setOutputStream(OutputStream os) { + if (committed) { + return; + } + outputStream = os; + } + + @Override + public void sendRedirect(String location) { + check(); + setStatus(HttpStatus.FOUND.getCode()); + setHeader("location", location); + commit(); + } + + @Override + public void sendError(int status) { + check(); + setStatus(status); + commit(); + } + + @Override + public void sendError(int status, String message) { + check(); + setStatus(status); + setBody(message); + commit(); + } + + @Override + public boolean isEmpty() { + if (status != 0) { + return false; + } + if (!headers.isEmpty()) { + return false; + } + return isContentEmpty(); + } + + @Override + public boolean isContentEmpty() { + if (body != null) { + return false; + } + if (outputStream != null && outputStream instanceof ByteArrayOutputStream) { + return ((ByteArrayOutputStream) outputStream).size() == 0; + } + return true; + } + + @Override + public boolean isCommitted() { + return committed; + } + + @Override + public void commit() { + committed = true; + } + + @Override + public void setCommitted(boolean committed) { + this.committed = committed; + } + + @Override + public void reset() { + check(); + status = 0; + headers.clear(); + contentType = null; + body = null; + resetBuffer(); + } + + @Override + public void resetBuffer() { + check(); + if (outputStream == null) { + return; + } + if (outputStream instanceof ByteArrayOutputStream) { + ((ByteArrayOutputStream) outputStream).reset(); + return; + } + String name = outputStream.getClass().getName(); + throw new UnsupportedOperationException("The outputStream type [" + name + "] is not supported to reset"); + } + + private void check() { + if (committed) { + throw new IllegalStateException("Response already committed"); + } + } + + @Override + @SuppressWarnings("unchecked") + public HttpResult toHttpResult() { + int status = this.status; + Map> headers = this.headers; + Object body = this.body; + if (body instanceof HttpResult) { + HttpResult result = (HttpResult) body; + if (result.getStatus() != 0) { + status = result.getStatus(); + } + Map> rHeaders = result.getHeaders(); + if (rHeaders != null && !rHeaders.isEmpty()) { + headers = new HttpHeaders(); + headers.putAll(this.headers); + for (Map.Entry> entry : rHeaders.entrySet()) { + String key = entry.getKey(); + if ("set-cookie".equalsIgnoreCase(key)) { + headers.computeIfAbsent(key, k -> new ArrayList<>()).addAll(entry.getValue()); + } else { + headers.put(key, entry.getValue()); + } + } + } + body = result.getBody(); + } + if (status == 0) { + status = HttpStatus.OK.getCode(); + } + if (body == null) { + body = outputStream; + } + return HttpResult.builder(body).status(status).headers(headers).build(); + } + + @Override + public String toString() { + return "DefaultHttpResponse{" + fieldToString() + '}'; + } + + protected final String fieldToString() { + return "status=" + status + ", contentType='" + contentType() + '\'' + ", body=" + body; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpResult.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpResult.java new file mode 100644 index 00000000000..699cee9e4e4 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpResult.java @@ -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.remoting.http12.message; + +import org.apache.dubbo.common.utils.DateUtils; +import org.apache.dubbo.remoting.http12.HttpResult; +import org.apache.dubbo.remoting.http12.HttpStatus; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class DefaultHttpResult implements HttpResult { + + private int status; + private Map> headers; + private T body; + + @Override + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + @Override + public Map> getHeaders() { + return headers; + } + + public void setHeaders(Map> headers) { + this.headers = headers; + } + + @Override + public T getBody() { + return body; + } + + public void setBody(T body) { + this.body = body; + } + + @Override + public String toString() { + return "DefaultHttpResult{" + "status=" + status + ", headers=" + headers + ", body=" + body + '}'; + } + + public static final class Builder { + private int status; + private Map> headers; + private T body; + + public Builder status(int status) { + this.status = status; + return this; + } + + public Builder status(HttpStatus status) { + this.status = status.getCode(); + return this; + } + + public Builder ok() { + return status(HttpStatus.OK.getCode()); + } + + public Builder found(String url) { + return status(HttpStatus.FOUND).header("location", url); + } + + public Builder error() { + return status(HttpStatus.INTERNAL_SERVER_ERROR); + } + + public Builder headers(Map> headers) { + this.headers = headers; + return this; + } + + public Builder header(String key, List values) { + getHeaders().put(key, values); + return this; + } + + public Builder header(String key, String... values) { + getHeaders().put(key, Arrays.asList(values)); + return this; + } + + public Builder header(String key, String value) { + getHeaders().put(key, Collections.singletonList(value)); + return this; + } + + public Builder header(String key, Date value) { + return header(key, DateUtils.formatHeader(value)); + } + + public Builder addHeader(String key, String value) { + getHeaders().computeIfAbsent(key, k -> new ArrayList<>()).add(value); + return this; + } + + private Map> getHeaders() { + Map> headers = this.headers; + if (headers == null) { + headers = new LinkedHashMap<>(); + this.headers = headers; + } + return headers; + } + + public Builder body(T body) { + this.body = body; + return this; + } + + public DefaultHttpResult build() { + DefaultHttpResult result = new DefaultHttpResult<>(); + result.setStatus(status); + result.setHeaders(headers); + result.setBody(body); + return result; + } + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageAdapterFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageAdapterFactory.java new file mode 100644 index 00000000000..fb23be5ee29 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageAdapterFactory.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.remoting.http12.message; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.remoting.http12.HttpChannel; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface HttpMessageAdapterFactory
{ + + HR adaptRequest(REQUEST rawRequest, HttpChannel channel); + + HttpResponse adaptResponse(HR request, REQUEST rawRequest, RESPONSE rawResponse); + + default HttpResponse adaptResponse(HR request, REQUEST rawRequest) { + return adaptResponse(request, rawRequest, null); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java index 94e247a57df..448646bb368 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java @@ -16,20 +16,27 @@ */ package org.apache.dubbo.remoting.http12.message; +import org.apache.dubbo.common.utils.ArrayUtils; import org.apache.dubbo.remoting.http12.exception.DecodeException; import java.io.InputStream; +import java.nio.charset.Charset; + +import static java.nio.charset.StandardCharsets.UTF_8; public interface HttpMessageDecoder extends CodecMediaType { - Object decode(InputStream inputStream, Class targetType) throws DecodeException; + Object decode(InputStream inputStream, Class targetType, Charset charset) throws DecodeException; - default Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { - // default decode first target type - return new Object[] { - this.decode(inputStream, targetTypes == null || targetTypes.length == 0 ? null : targetTypes[0]) - }; + default Object[] decode(InputStream inputStream, Class[] targetTypes, Charset charset) throws DecodeException { + return new Object[] {decode(inputStream, ArrayUtils.isEmpty(targetTypes) ? null : targetTypes[0], charset)}; + } + + default Object decode(InputStream inputStream, Class targetType) throws DecodeException { + return decode(inputStream, targetType, UTF_8); } - MediaType mediaType(); + default Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { + return decode(inputStream, targetTypes, UTF_8); + } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageEncoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageEncoder.java index d4701ce5012..f4890bcb3b9 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageEncoder.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageEncoder.java @@ -16,16 +16,31 @@ */ package org.apache.dubbo.remoting.http12.message; +import org.apache.dubbo.common.utils.ArrayUtils; import org.apache.dubbo.remoting.http12.exception.EncodeException; import java.io.OutputStream; +import java.nio.charset.Charset; + +import static java.nio.charset.StandardCharsets.UTF_8; public interface HttpMessageEncoder extends CodecMediaType { - void encode(OutputStream outputStream, Object data) throws EncodeException; + void encode(OutputStream outputStream, Object data, Charset charset) throws EncodeException; + + default void encode(OutputStream outputStream, Object[] data, Charset charset) throws EncodeException { + encode(outputStream, ArrayUtils.first(data), charset); + } + + default void encode(OutputStream outputStream, Object data) throws EncodeException { + encode(outputStream, data, UTF_8); + } default void encode(OutputStream outputStream, Object[] data) throws EncodeException { - // default encode first data - this.encode(outputStream, data == null || data.length == 0 ? null : data[0]); + encode(outputStream, ArrayUtils.first(data), UTF_8); + } + + default String contentType() { + return mediaType().getName(); } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/MediaType.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/MediaType.java index 1f35951b875..fd7af730042 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/MediaType.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/MediaType.java @@ -16,44 +16,56 @@ */ package org.apache.dubbo.remoting.http12.message; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.Map; +public final class MediaType { -public class MediaType { + public static final String WILDCARD = "*"; - public static final MediaType ALL_VALUE = new MediaType("*", "*"); + public static final MediaType ALL = new MediaType(WILDCARD, WILDCARD); - public static final MediaType APPLICATION_JSON_VALUE = new MediaType("application", "json"); + public static final MediaType APPLICATION_JSON = new MediaType("application", "json"); - public static final MediaType TEXT_EVENT_STREAM_VALUE = new MediaType("text", "event-stream"); + public static final MediaType APPLICATION_XML = new MediaType("application", "xml"); + + public static final MediaType APPLICATION_YAML = new MediaType("application", "yaml"); + + public static final MediaType APPLICATION_JAVASCRIPT = new MediaType("application", "javascript"); + + public static final MediaType APPLICATION_OCTET_STREAM = new MediaType("application", "octet-stream"); + + public static final MediaType APPLICATION_GRPC = new MediaType("application", "grpc"); + + public static final MediaType APPLICATION_GRPC_PROTO = new MediaType("application", "grpc+proto"); + + public static final MediaType APPLICATION_FROM_URLENCODED = new MediaType("application", "x-www-form-urlencoded"); public static final MediaType MULTIPART_FORM_DATA = new MediaType("multipart", "form-data"); - public static final MediaType APPLICATION_X_WWW_FROM_URLENCODED = - new MediaType("application", "x-www-form-urlencoded"); + public static final MediaType TEXT_JSON = new MediaType("text", "json"); - public static final MediaType APPLICATION_XML = new MediaType("application", "xml"); + public static final MediaType TEXT_XML = new MediaType("text", "xml"); + + public static final MediaType TEXT_YAML = new MediaType("text", "yaml"); + + public static final MediaType TEXT_CSS = new MediaType("text", "css"); + + public static final MediaType TEXT_JAVASCRIPT = new MediaType("text", "javascript"); + + public static final MediaType TEXT_HTML = new MediaType("text", "html"); public static final MediaType TEXT_PLAIN = new MediaType("text", "plain"); + public static final MediaType TEXT_EVENT_STREAM = new MediaType("text", "event-stream"); + private final String name; private final String type; private final String subType; - private final Charset charset; - public MediaType(String type, String subType) { - this(type, subType, Collections.singletonMap("charset", "UTF-8")); - } - - public MediaType(String type, String subType, Map parameters) { this.type = type; this.subType = subType; - this.name = type + "/" + subType; - this.charset = Charset.forName(parameters.getOrDefault("charset", "UTF-8")); + this.name = type + '/' + subType; } public String getName() { @@ -68,7 +80,7 @@ public String getSubType() { return subType; } - public Charset getCharset() { - return charset; + public boolean isPureText() { + return this == TEXT_HTML || this == TEXT_PLAIN; } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/BinaryCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/BinaryCodec.java new file mode 100644 index 00000000000..bb3ee1af5c4 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/BinaryCodec.java @@ -0,0 +1,61 @@ +/* + * 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.message.codec; + +import org.apache.dubbo.common.io.StreamUtils; +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.exception.EncodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.MediaType; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +public class BinaryCodec implements HttpMessageCodec { + + @Override + public void encode(OutputStream os, Object data, Charset charset) throws EncodeException { + if (data == null) { + return; + } + try { + if (data instanceof byte[]) { + os.write((byte[]) data); + return; + } + } catch (IOException e) { + throw new EncodeException(e); + } + throw new EncodeException("'application/octet-stream' media-type only supports byte[] return type."); + } + + @Override + public Object decode(InputStream is, Class targetType, Charset charset) throws DecodeException { + try { + return StreamUtils.readBytes(is); + } catch (Exception e) { + throw new DecodeException(e); + } + } + + @Override + public MediaType mediaType() { + return MediaType.APPLICATION_OCTET_STREAM; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/BinaryCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/BinaryCodecFactory.java new file mode 100644 index 00000000000..7a38c11a08e --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/BinaryCodecFactory.java @@ -0,0 +1,41 @@ +/* + * 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.message.codec; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; + +@Activate +public final class BinaryCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { + + private final BinaryCodec instance = new BinaryCodec(); + + @Override + public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { + return instance; + } + + @Override + public MediaType mediaType() { + return MediaType.APPLICATION_OCTET_STREAM; + } +} 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 601d82c5797..7c28b62f712 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 @@ -17,132 +17,74 @@ package org.apache.dubbo.remoting.http12.message.codec; import org.apache.dubbo.common.URL; -import org.apache.dubbo.remoting.http12.HttpHeaders; +import org.apache.dubbo.common.utils.Assert; import org.apache.dubbo.remoting.http12.exception.UnsupportedMediaTypeException; -import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; -import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.rpc.model.FrameworkModel; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import io.netty.handler.codec.CodecException; +public final class CodecUtils { -public class CodecUtils { - - private final FrameworkModel frameworkModel; - - private final List decoders; - - private final List encoders; - - private final Map encoderCache; - - private final Map decoderCache; + private final List decoderFactories; + private final List encoderFactories; + private final Map> encoderCache = new ConcurrentHashMap<>(); + private final Map> decoderCache = new ConcurrentHashMap<>(); public CodecUtils(FrameworkModel frameworkModel) { - this.encoderCache = new ConcurrentHashMap<>(1); - this.decoderCache = new ConcurrentHashMap<>(1); - this.frameworkModel = frameworkModel; - this.decoders = frameworkModel - .getExtensionLoader(HttpMessageDecoderFactory.class) - .getActivateExtensions(); - this.encoders = frameworkModel - .getExtensionLoader(HttpMessageEncoderFactory.class) - .getActivateExtensions(); - decoders.forEach( - decoderFactory -> decoderCache.put(decoderFactory.mediaType().getName(), decoderFactory)); - encoders.forEach( - encoderFactory -> encoderCache.put(encoderFactory.mediaType().getName(), encoderFactory)); + decoderFactories = frameworkModel.getActivateExtensions(HttpMessageDecoderFactory.class); + encoderFactories = frameworkModel.getActivateExtensions(HttpMessageEncoderFactory.class); + decoderFactories.forEach(factory -> decoderCache.put(factory.mediaType().getName(), Optional.of(factory))); + encoderFactories.forEach(factory -> encoderCache.put(factory.mediaType().getName(), Optional.of(factory))); } - public HttpMessageDecoder determineHttpMessageDecoder(FrameworkModel frameworkModel, String contentType, URL url) { - return determineHttpMessageDecoderFactory(contentType).createCodec(url, frameworkModel, contentType); + public HttpMessageDecoder determineHttpMessageDecoder(URL url, FrameworkModel frameworkModel, String mediaType) { + return determineHttpMessageDecoderFactory(mediaType) + .orElseThrow(() -> new UnsupportedMediaTypeException(mediaType)) + .createCodec(url, frameworkModel, mediaType); } - public HttpMessageEncoder determineHttpMessageEncoder(FrameworkModel frameworkModel, HttpHeaders headers, URL url) { - String mediaType = getEncodeMediaType(headers); - return determineHttpMessageEncoderFactory(mediaType).createCodec(url, frameworkModel, mediaType); + public HttpMessageEncoder determineHttpMessageEncoder(URL url, FrameworkModel frameworkModel, String mediaType) { + return determineHttpMessageEncoderFactory(mediaType) + .orElseThrow(() -> new UnsupportedMediaTypeException(mediaType)) + .createCodec(url, frameworkModel, mediaType); } - public HttpMessageDecoderFactory determineHttpMessageDecoderFactory(String mediaType) { - HttpMessageDecoderFactory factory = decoderCache.computeIfAbsent(mediaType, k -> { - for (HttpMessageDecoderFactory decoderFactory : decoders) { - if (decoderFactory.supports(mediaType)) { - return decoderFactory; + public Optional determineHttpMessageDecoderFactory(String mediaType) { + Assert.notNull(mediaType, "mediaType must not be null"); + return decoderCache.computeIfAbsent(mediaType, k -> { + for (HttpMessageDecoderFactory decoderFactory : decoderFactories) { + if (decoderFactory.supports(k)) { + return Optional.of(decoderFactory); } } - return new UnsupportedCodecFactory(); + return Optional.empty(); }); - if (factory instanceof UnsupportedCodecFactory) { - throw new UnsupportedMediaTypeException(mediaType); - } - return factory; } - public HttpMessageEncoderFactory determineHttpMessageEncoderFactory(String mediaType) { - HttpMessageEncoderFactory factory = encoderCache.computeIfAbsent(mediaType, k -> { - for (HttpMessageEncoderFactory encoderFactory : encoders) { - if (encoderFactory.supports(mediaType)) { - return encoderFactory; + public Optional determineHttpMessageEncoderFactory(String mediaType) { + Assert.notNull(mediaType, "mediaType must not be null"); + return encoderCache.computeIfAbsent(mediaType, k -> { + for (HttpMessageEncoderFactory encoderFactory : encoderFactories) { + if (encoderFactory.supports(k)) { + return Optional.of(encoderFactory); } } - return new UnsupportedCodecFactory(); + return Optional.empty(); }); - if (factory instanceof UnsupportedCodecFactory) { - throw new UnsupportedMediaTypeException(mediaType); - } - return factory; - } - - public List getDecoders() { - return decoders; - } - - public List getEncoders() { - return encoders; - } - - static class UnsupportedCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { - @Override - public MediaType mediaType() { - throw new CodecException(); - } - - @Override - public boolean supports(String mediaType) { - throw new CodecException(); - } - - @Override - public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { - throw new CodecException(); - } } - public static String getEncodeMediaType(HttpHeaders headers) { - String mediaType = headers.getAccept(); - if (mediaType == null) { - mediaType = headers.getContentType(); - } - return mediaType; + public List getDecoderFactories() { + return decoderFactories; } - public static ByteArrayOutputStream toByteArrayStream(InputStream in) throws IOException { - ByteArrayOutputStream result = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int length; - while ((length = in.read(buffer)) != -1) { - result.write(buffer, 0, length); - } - return result; + public List getEncoderFactories() { + return encoderFactories; } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/HtmlCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/HtmlCodec.java new file mode 100644 index 00000000000..ac1ae805abe --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/HtmlCodec.java @@ -0,0 +1,61 @@ +/* + * 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.message.codec; + +import org.apache.dubbo.common.io.StreamUtils; +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.exception.EncodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.MediaType; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +public class HtmlCodec implements HttpMessageCodec { + + @Override + public void encode(OutputStream os, Object data, Charset charset) throws EncodeException { + try { + if (data instanceof CharSequence) { + os.write((data.toString()).getBytes(charset)); + return; + } + } catch (IOException e) { + throw new EncodeException(e); + } + throw new EncodeException("'text/html' media-type only supports String as return type."); + } + + @Override + public Object decode(InputStream is, Class targetType, Charset charset) throws DecodeException { + try { + if (targetType == String.class) { + return StreamUtils.toString(is, charset); + } + } catch (Exception e) { + throw new DecodeException(e); + } + throw new DecodeException("'text/html' media-type only supports String as method param."); + } + + @Override + public MediaType mediaType() { + return MediaType.TEXT_HTML; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/HtmlCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/HtmlCodecFactory.java new file mode 100644 index 00000000000..ee029a20fe9 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/HtmlCodecFactory.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; + +@Activate +public final class HtmlCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { + + private final HtmlCodec instance = new HtmlCodec(); + + @Override + public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { + return instance; + } + + @Override + public MediaType mediaType() { + return MediaType.TEXT_HTML; + } + + @Override + public boolean supports(String mediaType) { + return mediaType.startsWith(mediaType().getName()) || mediaType.startsWith("application/xhtml"); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java index da38387df0d..aea3fdb66a8 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java @@ -16,6 +16,7 @@ */ package org.apache.dubbo.remoting.http12.message.codec; +import org.apache.dubbo.common.io.StreamUtils; import org.apache.dubbo.common.utils.JsonUtils; import org.apache.dubbo.remoting.http12.exception.DecodeException; import org.apache.dubbo.remoting.http12.exception.EncodeException; @@ -24,94 +25,68 @@ import java.io.InputStream; import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; +import java.nio.charset.Charset; import java.util.List; -import com.alibaba.fastjson2.JSONObject; - public class JsonCodec implements HttpMessageCodec { - @Override - public MediaType mediaType() { - return MediaType.APPLICATION_JSON_VALUE; - } + public static final JsonCodec INSTANCE = new JsonCodec(); - public void encode(OutputStream outputStream, Object unSerializedBody) throws EncodeException { + public void encode(OutputStream os, Object data, Charset charset) throws EncodeException { try { - try { - String jsonString = JsonUtils.toJson(unSerializedBody); - outputStream.write(jsonString.getBytes(StandardCharsets.UTF_8)); - } finally { - outputStream.flush(); - } - } catch (Throwable e) { - throw new EncodeException(e); + os.write(JsonUtils.toJson(data).getBytes(charset)); + } catch (Throwable t) { + throw new EncodeException("Error encoding json", t); } } - public void encode(OutputStream outputStream, Object[] data) throws EncodeException { + public void encode(OutputStream os, Object[] data, Charset charset) throws EncodeException { try { - try { - String jsonString = JsonUtils.toJson(data); - outputStream.write(jsonString.getBytes(StandardCharsets.UTF_8)); - } finally { - outputStream.flush(); - } - } catch (Throwable e) { - throw new EncodeException(e); + os.write(JsonUtils.toJson(data).getBytes(charset)); + } catch (Throwable t) { + throw new EncodeException("Error encoding json", t); } } @Override - public Object decode(InputStream body, Class targetType) throws DecodeException { + public Object decode(InputStream is, Class targetType, Charset charset) throws DecodeException { try { - try { - int len; - byte[] data = new byte[4096]; - StringBuilder builder = new StringBuilder(4096); - while ((len = body.read(data)) != -1) { - builder.append(new String(data, 0, len)); - } - return JsonUtils.toJavaObject(builder.toString(), targetType); - } finally { - body.close(); - } - } catch (Throwable e) { - throw new DecodeException(e); + return JsonUtils.toJavaObject(StreamUtils.toString(is, charset), targetType); + } catch (Throwable t) { + throw new DecodeException("Error decoding json", t); } } @Override - public Object[] decode(InputStream dataInputStream, Class[] targetTypes) throws DecodeException { - List result = new ArrayList<>(); + public Object[] decode(InputStream is, Class[] targetTypes, Charset charset) throws DecodeException { try { - try { - int len; - byte[] data = new byte[4096]; - StringBuilder builder = new StringBuilder(4096); - while ((len = dataInputStream.read(data)) != -1) { - builder.append(new String(data, 0, len)); - } - String jsonString = builder.toString(); - List jsonObjects = JsonUtils.toJavaList(jsonString, Object.class); - - for (int i = 0; i < targetTypes.length; i++) { - Object jsonObject = jsonObjects.get(i); - Class type = targetTypes[i]; - if (jsonObject instanceof JSONObject) { - Object o = ((JSONObject) jsonObject).toJavaObject(type); - result.add(o); - } else { - result.add(jsonObject); + int len = targetTypes.length; + Object obj = JsonUtils.toJavaObject(StreamUtils.toString(is, charset), Object.class); + if (obj instanceof List) { + List list = (List) obj; + if (list.size() == len) { + Object[] results = new Object[len]; + for (int i = 0; i < len; i++) { + results[i] = JsonUtils.convertObject(list.get(i), targetTypes[i]); } + return results; } - return result.toArray(); - } finally { - dataInputStream.close(); + throw new DecodeException( + "Json array size [" + list.size() + "] must equals arguments count [" + len + "]"); } - } catch (Throwable e) { - throw new DecodeException(e); + if (len == 1) { + return new Object[] {JsonUtils.convertObject(obj, targetTypes[0])}; + } + throw new DecodeException("Json must be array"); + } catch (DecodeException e) { + throw e; + } catch (Throwable t) { + throw new DecodeException("Error decoding json", t); } } + + @Override + public MediaType mediaType() { + return MediaType.APPLICATION_JSON; + } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodecFactory.java index 25fdcdb3e65..9f59f92451d 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodecFactory.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodecFactory.java @@ -17,23 +17,28 @@ package org.apache.dubbo.remoting.http12.message.codec; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.rpc.model.FrameworkModel; -public class JsonCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { - - private final JsonCodec instance = new JsonCodec(); +@Activate +public final class JsonCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { @Override public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { - return instance; + return JsonCodec.INSTANCE; } @Override public MediaType mediaType() { - return MediaType.APPLICATION_JSON_VALUE; + return MediaType.APPLICATION_JSON; + } + + @Override + public boolean supports(String mediaType) { + return mediaType.startsWith(mediaType().getName()) || mediaType.startsWith(MediaType.TEXT_JSON.getName()); } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java index caac2c3fb46..86131abb996 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java @@ -16,77 +16,61 @@ */ package org.apache.dubbo.remoting.http12.message.codec; +import org.apache.dubbo.common.io.StreamUtils; import org.apache.dubbo.common.utils.MethodUtils; import org.apache.dubbo.remoting.http12.exception.DecodeException; import org.apache.dubbo.remoting.http12.exception.EncodeException; -import org.apache.dubbo.remoting.http12.message.MediaType; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import com.google.protobuf.Message; import com.google.protobuf.util.JsonFormat; -public class JsonPbCodec extends JsonCodec { +public final class JsonPbCodec extends JsonCodec { @Override - public MediaType mediaType() { - return MediaType.APPLICATION_JSON_VALUE; - } - - @Override - public void encode(OutputStream outputStream, Object unSerializedBody) throws EncodeException { + public void encode(OutputStream os, Object data, Charset charset) throws EncodeException { try { - if (unSerializedBody instanceof Message) { - String jsonString = JsonFormat.printer().print((Message) unSerializedBody); - outputStream.write(jsonString.getBytes(StandardCharsets.UTF_8)); + if (data instanceof Message) { + String jsonString = JsonFormat.printer().print((Message) data); + os.write(jsonString.getBytes(charset)); return; } } catch (IOException e) { - throw new EncodeException(e); + throw new EncodeException("Error encoding jsonPb", e); } - super.encode(outputStream, unSerializedBody); - } - - @Override - public void encode(OutputStream outputStream, Object[] data) throws EncodeException { - super.encode(outputStream, data); + super.encode(os, data, charset); } @Override - public Object decode(InputStream body, Class targetType) throws DecodeException { + public Object decode(InputStream is, Class targetType, Charset charset) throws DecodeException { try { if (isProtobuf(targetType)) { - int len; - byte[] data = new byte[4096]; - StringBuilder builder = new StringBuilder(4096); - while ((len = body.read(data)) != -1) { - builder.append(new String(data, 0, len)); - } Message.Builder newBuilder = (Message.Builder) MethodUtils.findMethod(targetType, "newBuilder").invoke(null); - JsonFormat.parser().ignoringUnknownFields().merge(builder.toString(), newBuilder); + JsonFormat.parser().ignoringUnknownFields().merge(StreamUtils.toString(is, charset), newBuilder); return newBuilder.build(); } } catch (Throwable e) { - throw new DecodeException(e); + throw new DecodeException("Error decoding jsonPb", e); } - return super.decode(body, targetType); + return super.decode(is, targetType, charset); } @Override - public Object[] decode(InputStream dataInputStream, Class[] targetTypes) throws DecodeException { + public Object[] decode(InputStream is, Class[] targetTypes, Charset charset) throws DecodeException { try { if (hasProtobuf(targetTypes)) { // protobuf only support one parameter - return new Object[] {decode(dataInputStream, targetTypes[0])}; + return new Object[] {decode(is, targetTypes[0], charset)}; } } catch (Throwable e) { - throw new DecodeException(e); + throw new DecodeException("Error decoding jsonPb", e); } - return super.decode(dataInputStream, targetTypes); + return super.decode(is, targetTypes, charset); } private static boolean isProtobuf(Class targetType) { diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodecFactory.java index e4b01f06dc8..b92bd76f391 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodecFactory.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodecFactory.java @@ -24,8 +24,8 @@ import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.rpc.model.FrameworkModel; -@Activate -public class JsonPbCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { +@Activate(order = -100, onClass = "com.google.protobuf.Message") +public final class JsonPbCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { private final JsonPbCodec instance = new JsonPbCodec(); @@ -36,6 +36,11 @@ public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, Stri @Override public MediaType mediaType() { - return MediaType.APPLICATION_JSON_VALUE; + return MediaType.APPLICATION_JSON; + } + + @Override + public boolean supports(String mediaType) { + return mediaType.startsWith(mediaType().getName()) || mediaType.startsWith(MediaType.TEXT_JSON.getName()); } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoder.java index 560e5db1a78..2ac26183527 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoder.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoder.java @@ -29,6 +29,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; @@ -55,13 +56,13 @@ public MultipartDecoder(URL url, FrameworkModel frameworkModel, String contentTy } @Override - public Object decode(InputStream inputStream, Class targetType) throws DecodeException { - Object[] res = decode(inputStream, new Class[] {targetType}); + public Object decode(InputStream inputStream, Class targetType, Charset charset) throws DecodeException { + Object[] res = decode(inputStream, new Class[] {targetType}, charset); return res.length > 1 ? res : res[0]; } @Override - public Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { + public Object[] decode(InputStream inputStream, Class[] targetTypes, Charset charset) throws DecodeException { try { List parts = transferToParts(inputStream, headerContentType); if (parts.size() != targetTypes.length) { @@ -77,8 +78,8 @@ public Object[] decode(InputStream inputStream, Class[] targetTypes) throws D continue; } res[i] = codecUtils - .determineHttpMessageDecoder(frameworkModel, part.headers.getContentType(), url) - .decode(new ByteArrayInputStream(part.content), targetTypes[i]); + .determineHttpMessageDecoder(url, frameworkModel, part.headers.getContentType()) + .decode(new ByteArrayInputStream(part.content), targetTypes[i], charset); } return res; } catch (IOException ioException) { diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoderFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoderFactory.java index 2e55e0129fa..3cf2972548b 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoderFactory.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoderFactory.java @@ -24,7 +24,7 @@ import org.apache.dubbo.rpc.model.FrameworkModel; @Activate -public class MultipartDecoderFactory implements HttpMessageDecoderFactory { +public final class MultipartDecoderFactory implements HttpMessageDecoderFactory { private CodecUtils codecUtils; diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodec.java index 3516c749dc1..33d067686ed 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodec.java @@ -16,6 +16,7 @@ */ package org.apache.dubbo.remoting.http12.message.codec; +import org.apache.dubbo.common.io.StreamUtils; import org.apache.dubbo.remoting.http12.exception.DecodeException; import org.apache.dubbo.remoting.http12.exception.EncodeException; import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; @@ -25,54 +26,39 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -public class PlainTextCodec implements HttpMessageCodec { - - private final String contentType; - - public PlainTextCodec(String contentType) { - this.contentType = contentType; - } +public final class PlainTextCodec implements HttpMessageCodec { @Override - public void encode(OutputStream outputStream, Object data) throws EncodeException { - if (!(data instanceof String)) { - throw new EncodeException("PlainText media-type only supports String as return type."); + public void encode(OutputStream os, Object data, Charset charset) throws EncodeException { + if (data == null) { + return; } try { - outputStream.write(((String) data).getBytes()); + if (data instanceof CharSequence) { + os.write((data.toString()).getBytes(charset)); + return; + } } catch (IOException e) { throw new EncodeException(e); } + throw new EncodeException("'text/plain' media-type only supports String as return type."); } @Override - public MediaType mediaType() { - return MediaType.TEXT_PLAIN; - } - - @Override - public Object decode(InputStream inputStream, Class targetType) throws DecodeException { + public Object decode(InputStream is, Class targetType, Charset charset) throws DecodeException { try { - if (!String.class.equals(targetType)) { - throw new DecodeException("Plain text content only supports String as method param."); - } - Charset charset; - if (contentType.contains("charset=")) { - try { - charset = Charset.forName(contentType.substring(contentType.indexOf("charset=") + 8)); - } catch (Exception e) { - throw new DecodeException("Unsupported charset:" + e.getMessage()); - } - if (!charset.equals(StandardCharsets.UTF_8) && !charset.equals(StandardCharsets.US_ASCII)) { - String origin = CodecUtils.toByteArrayStream(inputStream).toString(charset.name()); - return new String(origin.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); - } + if (targetType == String.class) { + return StreamUtils.toString(is, charset); } - return CodecUtils.toByteArrayStream(inputStream).toString(StandardCharsets.UTF_8.name()); } catch (Exception e) { throw new DecodeException(e); } + throw new DecodeException("'text/plain' media-type only supports String as method param."); + } + + @Override + public MediaType mediaType() { + return MediaType.TEXT_PLAIN; } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodecFactory.java index b8c400a7a75..c2de2102b62 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodecFactory.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodecFactory.java @@ -24,16 +24,23 @@ import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.rpc.model.FrameworkModel; -@Activate -public class PlainTextCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { +@Activate(order = 10000) +public final class PlainTextCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { + + private final PlainTextCodec instance = new PlainTextCodec(); @Override public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { - return new PlainTextCodec(mediaType); + return instance; } @Override public MediaType mediaType() { return MediaType.TEXT_PLAIN; } + + @Override + public boolean supports(String mediaType) { + return mediaType.startsWith("text/"); + } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodec.java index 2e16b6d3828..bd51a2c91e6 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodec.java @@ -17,6 +17,7 @@ package org.apache.dubbo.remoting.http12.message.codec; import org.apache.dubbo.common.convert.ConverterUtil; +import org.apache.dubbo.common.io.StreamUtils; import org.apache.dubbo.remoting.http12.exception.DecodeException; import org.apache.dubbo.remoting.http12.exception.EncodeException; import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; @@ -25,6 +26,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; @@ -39,7 +42,7 @@ public UrlEncodeFormCodec(ConverterUtil converterUtil) { } @Override - public void encode(OutputStream outputStream, Object data) throws EncodeException { + public void encode(OutputStream outputStream, Object data, Charset charset) throws EncodeException { try { if (data instanceof String) { outputStream.write(((String) data).getBytes()); @@ -48,11 +51,14 @@ public void encode(OutputStream outputStream, Object data) throws EncodeExceptio for (Map.Entry e : ((Map) data).entrySet()) { String k = e.getKey().toString(); String v = e.getValue().toString(); - toWrite.append(k).append("=").append(v).append("&"); + toWrite.append(k) + .append("=") + .append(URLEncoder.encode(v, StandardCharsets.UTF_8.name())) + .append("&"); } if (toWrite.length() > 1) { outputStream.write( - toWrite.substring(0, toWrite.length() - 1).getBytes()); + toWrite.substring(0, toWrite.length() - 1).getBytes(charset)); } } else { throw new EncodeException("UrlEncodeFrom media-type only supports String or Map as return type."); @@ -63,13 +69,13 @@ public void encode(OutputStream outputStream, Object data) throws EncodeExceptio } @Override - public Object decode(InputStream inputStream, Class targetType) throws DecodeException { - Object[] res = decode(inputStream, new Class[] {targetType}); + public Object decode(InputStream inputStream, Class targetType, Charset charset) throws DecodeException { + Object[] res = decode(inputStream, new Class[] {targetType}, charset); return res.length > 1 ? res : res[0]; } @Override - public Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { + public Object[] decode(InputStream inputStream, Class[] targetTypes, Charset charset) throws DecodeException { try { boolean toMap; // key=value&key2=value2 -> method(map) @@ -85,7 +91,7 @@ else if (Arrays.stream(targetTypes) "For x-www-form-urlencoded MIME type, please use Map/String/base-types as method param."); } String decoded = URLDecoder.decode( - CodecUtils.toByteArrayStream(inputStream).toString(), StandardCharsets.UTF_8.name()) + StreamUtils.toString(inputStream, charset), StandardCharsets.UTF_8.name()) .trim(); Map res = toMap(decoded, targetTypes, toMap); if (toMap) { @@ -123,6 +129,6 @@ private Map toMap(String formString, Class[] targetTypes, boo @Override public MediaType mediaType() { - return MediaType.APPLICATION_X_WWW_FROM_URLENCODED; + return MediaType.APPLICATION_FROM_URLENCODED; } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodecFactory.java index 0b657ffd09b..fc54cb3603a 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodecFactory.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodecFactory.java @@ -18,21 +18,20 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.convert.ConverterUtil; +import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.rpc.model.FrameworkModel; -public class UrlEncodeFormCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { - - private final ConverterUtil converterUtil; +@Activate +public final class UrlEncodeFormCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { private final UrlEncodeFormCodec instance; public UrlEncodeFormCodecFactory(FrameworkModel frameworkModel) { - this.converterUtil = frameworkModel.getBeanFactory().getBean(ConverterUtil.class); - this.instance = new UrlEncodeFormCodec(this.converterUtil); + instance = new UrlEncodeFormCodec(frameworkModel.getBeanFactory().getBean(ConverterUtil.class)); } @Override @@ -42,6 +41,6 @@ public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, Stri @Override public MediaType mediaType() { - return MediaType.APPLICATION_X_WWW_FROM_URLENCODED; + return MediaType.APPLICATION_FROM_URLENCODED; } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodec.java index 9d2bd8f0204..d63728083a3 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodec.java @@ -21,52 +21,64 @@ import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; import org.apache.dubbo.remoting.http12.message.MediaType; +import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; +import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.Source; import javax.xml.transform.sax.SAXSource; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.StringReader; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; import org.xml.sax.InputSource; public class XmlCodec implements HttpMessageCodec { @Override - public void encode(OutputStream outputStream, Object data) throws EncodeException { + public void encode(OutputStream os, Object data, Charset charset) throws EncodeException { try { Marshaller marshaller = JAXBContext.newInstance(data.getClass()).createMarshaller(); - marshaller.marshal(data, outputStream); + try (OutputStreamWriter writer = new OutputStreamWriter(os, charset)) { + marshaller.marshal(data, writer); + } } catch (Exception e) { - throw new EncodeException(e); + throw new EncodeException("Error encoding xml", e); } } @Override - public Object decode(InputStream inputStream, Class targetType) throws DecodeException { + public Object decode(InputStream is, Class targetType, Charset charset) throws DecodeException { try { - SAXParserFactory spf = SAXParserFactory.newInstance(); - spf.setFeature("http://xml.org/sax/features/external-general-entities", false); - spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - - // Do unmarshall operation - Source xmlSource = new SAXSource( - spf.newSAXParser().getXMLReader(), - new InputSource(new StringReader( - CodecUtils.toByteArrayStream(inputStream).toString()))); - JAXBContext context = JAXBContext.newInstance(targetType); - Unmarshaller unmarshaller = context.createUnmarshaller(); - return unmarshaller.unmarshal(xmlSource); + try (InputStreamReader reader = new InputStreamReader(is, charset)) { + InputSource inputSource = new InputSource(reader); + inputSource.setEncoding(charset.name()); + Source xmlSource = new SAXSource(newSAXParser().getXMLReader(), inputSource); + JAXBContext context = JAXBContext.newInstance(targetType); + Unmarshaller unmarshaller = context.createUnmarshaller(); + return unmarshaller.unmarshal(xmlSource); + } } catch (Exception e) { - throw new DecodeException(e); + throw new DecodeException("Error decoding xml", e); } } + private SAXParser newSAXParser() throws Exception { + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setFeature("http://xml.org/sax/features/external-general-entities", false); + spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + spf.setXIncludeAware(false); + return spf.newSAXParser(); + } + @Override public MediaType mediaType() { return MediaType.APPLICATION_XML; diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodecFactory.java index 3738c45cfb8..566894190f7 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodecFactory.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodecFactory.java @@ -17,13 +17,15 @@ package org.apache.dubbo.remoting.http12.message.codec; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.rpc.model.FrameworkModel; -public class XmlCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { +@Activate(onClass = "javax.xml.bind.Marshaller") +public final class XmlCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { private final HttpMessageCodec instance = new XmlCodec(); @@ -36,4 +38,9 @@ public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, Stri public MediaType mediaType() { return MediaType.APPLICATION_XML; } + + @Override + public boolean supports(String mediaType) { + return mediaType.startsWith(mediaType().getName()) || mediaType.startsWith(MediaType.TEXT_XML.getName()); + } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java new file mode 100644 index 00000000000..2b3aacfb5cb --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java @@ -0,0 +1,117 @@ +/* + * 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.message.codec; + +import org.apache.dubbo.common.utils.ClassUtils; +import org.apache.dubbo.common.utils.DefaultSerializeClassChecker; +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.exception.EncodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.MediaType; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; +import java.util.Iterator; + +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; +import org.yaml.snakeyaml.representer.Representer; + +public class YamlCodec implements HttpMessageCodec { + + @Override + public Object decode(InputStream is, Class targetType, Charset charset) throws DecodeException { + try (InputStreamReader reader = new InputStreamReader(is, charset)) { + return createYaml().loadAs(reader, targetType); + } catch (Throwable t) { + throw new DecodeException("Error decoding yaml", t); + } + } + + @Override + public Object[] decode(InputStream is, Class[] targetTypes, Charset charset) throws DecodeException { + try (InputStreamReader reader = new InputStreamReader(is, charset)) { + Yaml yaml = new Yaml(); + Iterator iterator = yaml.loadAll(reader).iterator(); + int len = targetTypes.length; + Object[] results = new Object[len]; + for (int i = 0; i < len; i++) { + if (iterator.hasNext()) { + Object result = iterator.next(); + Class targetType = targetTypes[i]; + if (targetType.isInstance(result)) { + results[i] = result; + } else { + results[i] = yaml.loadAs(yaml.dump(result), targetType); + } + } else { + throw new DecodeException("Not enough yaml documents in the stream"); + } + } + return results; + } catch (Throwable t) { + throw new DecodeException("Error decoding yaml", t); + } + } + + @Override + public void encode(OutputStream os, Object data, Charset charset) throws EncodeException { + try (OutputStreamWriter writer = new OutputStreamWriter(os, charset)) { + createYaml().dump(data, writer); + } catch (Throwable t) { + throw new EncodeException("Error encoding yaml", t); + } + } + + @Override + public void encode(OutputStream os, Object[] data, Charset charset) throws EncodeException { + try (OutputStreamWriter writer = new OutputStreamWriter(os, charset)) { + createYaml().dump(data, writer); + } catch (Throwable t) { + throw new EncodeException("Error encoding yaml", t); + } + } + + @Override + public MediaType mediaType() { + return MediaType.APPLICATION_YAML; + } + + private Yaml createYaml() { + LoaderOptions options = new LoaderOptions(); + options.setAllowDuplicateKeys(false); + DumperOptions dumperOptions = new DumperOptions(); + return new Yaml(new FilteringConstructor(options), new Representer(dumperOptions), dumperOptions, options); + } + + private static final class FilteringConstructor extends Constructor { + + FilteringConstructor(LoaderOptions loaderOptions) { + super(loaderOptions); + } + + @Override + protected Class getClassForName(String name) throws ClassNotFoundException { + return DefaultSerializeClassChecker.getInstance().loadClass(ClassUtils.getClassLoader(), name); + } + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodecFactory.java new file mode 100644 index 00000000000..7312ed0d942 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodecFactory.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; + +@Activate(onClass = "org.yaml.snakeyaml.Yaml") +public final class YamlCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { + + private final YamlCodec instance = new YamlCodec(); + + @Override + public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { + return instance; + } + + @Override + public MediaType mediaType() { + return MediaType.APPLICATION_YAML; + } + + @Override + public boolean supports(String mediaType) { + return mediaType.startsWith(mediaType().getName()) || mediaType.startsWith(MediaType.TEXT_YAML.getName()); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1Channel.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1Channel.java index c71cd1cee99..443bd21aec0 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1Channel.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1Channel.java @@ -60,6 +60,11 @@ public SocketAddress remoteAddress() { return channel.remoteAddress(); } + @Override + public SocketAddress localAddress() { + return channel.localAddress(); + } + @Override public void flush() {} } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1ConnectionHandler.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1ConnectionHandler.java index 6df2d6ec60d..7380024f7c7 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1ConnectionHandler.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1ConnectionHandler.java @@ -17,47 +17,21 @@ package org.apache.dubbo.remoting.http12.netty4.h1; import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.threadpool.ThreadPool; -import org.apache.dubbo.common.utils.Assert; -import org.apache.dubbo.common.utils.StringUtils; -import org.apache.dubbo.remoting.http12.HttpHeaderNames; -import org.apache.dubbo.remoting.http12.HttpHeaders; -import org.apache.dubbo.remoting.http12.exception.UnsupportedMediaTypeException; import org.apache.dubbo.remoting.http12.h1.Http1Request; -import org.apache.dubbo.remoting.http12.h1.Http1ServerChannelObserver; import org.apache.dubbo.remoting.http12.h1.Http1ServerTransportListener; import org.apache.dubbo.remoting.http12.h1.Http1ServerTransportListenerFactory; -import org.apache.dubbo.remoting.http12.message.codec.CodecUtils; import org.apache.dubbo.rpc.model.FrameworkModel; -import java.util.concurrent.Executor; - import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; public class NettyHttp1ConnectionHandler extends SimpleChannelInboundHandler { - private Http1ServerTransportListenerFactory http1ServerTransportListenerFactory; - - private final FrameworkModel frameworkModel; - private final URL url; - private final Executor executor; - - private final CodecUtils codecUtils; - - private Http1ServerChannelObserver errorResponseObserver; + private final FrameworkModel frameworkModel; - public NettyHttp1ConnectionHandler(URL url, FrameworkModel frameworkModel) { - this.url = url; - this.frameworkModel = frameworkModel; - this.executor = url.getOrDefaultFrameworkModel() - .getExtensionLoader(ThreadPool.class) - .getAdaptiveExtension() - .getExecutor(url); - this.codecUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CodecUtils.class); - } + private final Http1ServerTransportListenerFactory http1ServerTransportListenerFactory; public NettyHttp1ConnectionHandler( URL url, @@ -65,55 +39,16 @@ public NettyHttp1ConnectionHandler( Http1ServerTransportListenerFactory http1ServerTransportListenerFactory) { this.url = url; this.frameworkModel = frameworkModel; - this.executor = url.getOrDefaultFrameworkModel() - .getExtensionLoader(ThreadPool.class) - .getAdaptiveExtension() - .getExecutor(url); - this.codecUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CodecUtils.class); - this.http1ServerTransportListenerFactory = http1ServerTransportListenerFactory; - } - - public void setHttp1ServerTransportListenerFactory( - Http1ServerTransportListenerFactory http1ServerTransportListenerFactory) { this.http1ServerTransportListenerFactory = http1ServerTransportListenerFactory; } + /** + * process h1 request + */ protected void channelRead0(ChannelHandlerContext ctx, Http1Request http1Request) { - // process h1 request - Http1ServerTransportListener http1TransportListener = initTransportListenerIfNecessary(ctx, http1Request); - initErrorResponseObserver(ctx, http1Request); - executor.execute(() -> { - try { - http1TransportListener.onMetadata(http1Request); - http1TransportListener.onData(http1Request); - } catch (Exception e) { - errorResponseObserver.onError(e); - } - }); - } - - private Http1ServerTransportListener initTransportListenerIfNecessary( - ChannelHandlerContext ctx, Http1Request http1Request) { - // each h1 request create http1TransportListener instance - Http1ServerTransportListenerFactory http1ServerTransportListenerFactory = - this.http1ServerTransportListenerFactory; - Assert.notNull(http1ServerTransportListenerFactory, "http1ServerTransportListenerFactory must be not null."); Http1ServerTransportListener http1TransportListener = http1ServerTransportListenerFactory.newInstance( new NettyHttp1Channel(ctx.channel()), url, frameworkModel); - - HttpHeaders headers = http1Request.headers(); - String contentType = headers.getFirst(HttpHeaderNames.CONTENT_TYPE.getName()); - if (!StringUtils.hasText(contentType)) { - throw new UnsupportedMediaTypeException(contentType); - } - // check ContentType - codecUtils.determineHttpMessageDecoder(frameworkModel, headers.getContentType(), url); - return http1TransportListener; - } - - private void initErrorResponseObserver(ChannelHandlerContext ctx, Http1Request request) { - this.errorResponseObserver = new Http1ServerChannelObserver(new NettyHttp1Channel(ctx.channel())); - this.errorResponseObserver.setResponseEncoder( - codecUtils.determineHttpMessageEncoder(frameworkModel, request.headers(), url)); + http1TransportListener.onMetadata(http1Request); + http1TransportListener.onData(http1Request); } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h2/NettyH2StreamChannel.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h2/NettyH2StreamChannel.java index 05941658cfa..f85b1276343 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h2/NettyH2StreamChannel.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h2/NettyH2StreamChannel.java @@ -66,6 +66,11 @@ public SocketAddress remoteAddress() { return this.http2StreamChannel.remoteAddress(); } + @Override + public SocketAddress localAddress() { + return this.http2StreamChannel.localAddress(); + } + @Override public void flush() { this.http2StreamChannel.flush(); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h2/NettyHttp2ProtocolSelectorHandler.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h2/NettyHttp2ProtocolSelectorHandler.java index f102a873244..cc82019a7e4 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h2/NettyHttp2ProtocolSelectorHandler.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h2/NettyHttp2ProtocolSelectorHandler.java @@ -17,7 +17,6 @@ package org.apache.dubbo.remoting.http12.netty4.h2; import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http12.HttpHeaderNames; import org.apache.dubbo.remoting.http12.HttpHeaders; import org.apache.dubbo.remoting.http12.HttpMetadata; @@ -38,16 +37,11 @@ public class NettyHttp2ProtocolSelectorHandler extends SimpleChannelInboundHandler { - private Http2ServerTransportListenerFactory defaultHttp2ServerTransportListenerFactory; - private final URL url; private final FrameworkModel frameworkModel; - public NettyHttp2ProtocolSelectorHandler(URL url, FrameworkModel frameworkModel) { - this.url = url; - this.frameworkModel = frameworkModel; - } + private final Http2ServerTransportListenerFactory defaultHttp2ServerTransportListenerFactory; public NettyHttp2ProtocolSelectorHandler( URL url, @@ -62,11 +56,7 @@ public NettyHttp2ProtocolSelectorHandler( protected void channelRead0(ChannelHandlerContext ctx, HttpMetadata metadata) { HttpHeaders headers = metadata.headers(); String contentType = headers.getFirst(HttpHeaderNames.CONTENT_TYPE.getName()); - // 415 - if (!StringUtils.hasText(contentType)) { - throw new UnsupportedMediaTypeException(contentType); - } - Http2ServerTransportListenerFactory factory = adaptHttp2ServerTransportListenerFactory(contentType); + Http2ServerTransportListenerFactory factory = determineHttp2ServerTransportListenerFactory(contentType); if (factory == null) { throw new UnsupportedMediaTypeException(contentType); } @@ -84,7 +74,7 @@ protected void channelRead0(ChannelHandlerContext ctx, HttpMetadata metadata) { ctx.fireChannelRead(metadata); } - private Http2ServerTransportListenerFactory adaptHttp2ServerTransportListenerFactory(String contentType) { + private Http2ServerTransportListenerFactory determineHttp2ServerTransportListenerFactory(String contentType) { Set http2ServerTransportListenerFactories = frameworkModel .getExtensionLoader(Http2ServerTransportListenerFactory.class) .getSupportedExtensionInstances(); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory new file mode 100644 index 00000000000..eba1cf03497 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory @@ -0,0 +1 @@ +default=org.apache.dubbo.remoting.http12.message.DefaultHttpMessageAdapterFactory \ No newline at end of file diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory index 5637aa59753..54d3c089ccb 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory @@ -1,5 +1,8 @@ +json=org.apache.dubbo.remoting.http12.message.codec.JsonCodecFactory jsonpb=org.apache.dubbo.remoting.http12.message.codec.JsonPbCodecFactory -multipart=org.apache.dubbo.remoting.http12.message.codec.MultipartDecoderFactory -plaintext=org.apache.dubbo.remoting.http12.message.codec.PlainTextCodecFactory +yaml=org.apache.dubbo.remoting.http12.message.codec.YamlCodecFactory xml=org.apache.dubbo.remoting.http12.message.codec.XmlCodecFactory -urlencoded=org.apache.dubbo.remoting.http12.message.codec.UrlEncodeFormCodecFactory \ No newline at end of file +html=org.apache.dubbo.remoting.http12.message.codec.HtmlCodecFactory +plaintext=org.apache.dubbo.remoting.http12.message.codec.PlainTextCodecFactory +multipart=org.apache.dubbo.remoting.http12.message.codec.MultipartDecoderFactory +urlencoded=org.apache.dubbo.remoting.http12.message.codec.UrlEncodeFormCodecFactory diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory index 2243b9e0322..656b8eff63c 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory @@ -1,4 +1,7 @@ +json=org.apache.dubbo.remoting.http12.message.codec.JsonCodecFactory jsonpb=org.apache.dubbo.remoting.http12.message.codec.JsonPbCodecFactory -plaintext=org.apache.dubbo.remoting.http12.message.codec.PlainTextCodecFactory +yaml=org.apache.dubbo.remoting.http12.message.codec.YamlCodecFactory xml=org.apache.dubbo.remoting.http12.message.codec.XmlCodecFactory -urlencoded=org.apache.dubbo.remoting.http12.message.codec.UrlEncodeFormCodecFactory \ No newline at end of file +html=org.apache.dubbo.remoting.http12.message.codec.HtmlCodecFactory +plaintext=org.apache.dubbo.remoting.http12.message.codec.PlainTextCodecFactory +urlencoded=org.apache.dubbo.remoting.http12.message.codec.UrlEncodeFormCodecFactory diff --git a/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java index b8436c1c171..3c1484ce7ec 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java @@ -37,35 +37,36 @@ void testDetermineHttpCodec() { HttpHeaders headers = new HttpHeaders(); headers.put( HttpHeaderNames.CONTENT_TYPE.getName(), - Collections.singletonList(MediaType.APPLICATION_JSON_VALUE.getName())); + Collections.singletonList(MediaType.APPLICATION_JSON.getName())); HttpMessageDecoder decoder = - codecUtils.determineHttpMessageDecoder(FrameworkModel.defaultModel(), headers.getContentType(), null); + codecUtils.determineHttpMessageDecoder(null, FrameworkModel.defaultModel(), headers.getContentType()); Assertions.assertNotNull(decoder); - Assertions.assertEquals(JsonPbCodec.class, decoder.getClass()); + Assertions.assertEquals(JsonCodec.class, decoder.getClass()); HttpMessageEncoder encoder; // If no Accept header provided, use Content-Type to find encoder - encoder = codecUtils.determineHttpMessageEncoder(FrameworkModel.defaultModel(), headers, null); + encoder = codecUtils.determineHttpMessageEncoder( + null, FrameworkModel.defaultModel(), MediaType.APPLICATION_JSON.getName()); Assertions.assertNotNull(encoder); - Assertions.assertEquals(JsonPbCodec.class, encoder.getClass()); + Assertions.assertEquals(JsonCodec.class, encoder.getClass()); HttpHeaders headers1 = new HttpHeaders(); headers1.put( HttpHeaderNames.CONTENT_TYPE.getName(), Collections.singletonList(MediaType.MULTIPART_FORM_DATA.getName())); decoder = - codecUtils.determineHttpMessageDecoder(FrameworkModel.defaultModel(), headers1.getContentType(), null); + codecUtils.determineHttpMessageDecoder(null, FrameworkModel.defaultModel(), headers1.getContentType()); Assertions.assertNotNull(decoder); Assertions.assertEquals(MultipartDecoder.class, decoder.getClass()); Assertions.assertThrows( UnsupportedMediaTypeException.class, - () -> codecUtils.determineHttpMessageEncoder(FrameworkModel.defaultModel(), headers1, null)); + () -> codecUtils.determineHttpMessageEncoder( + null, FrameworkModel.defaultModel(), headers1.getContentType())); - headers1.put( - HttpHeaderNames.ACCEPT.getName(), - Collections.singletonList(MediaType.APPLICATION_JSON_VALUE.getName())); - encoder = codecUtils.determineHttpMessageEncoder(FrameworkModel.defaultModel(), headers1, null); + headers1.put(HttpHeaderNames.ACCEPT.getName(), Collections.singletonList(MediaType.APPLICATION_JSON.getName())); + encoder = codecUtils.determineHttpMessageEncoder( + null, FrameworkModel.defaultModel(), MediaType.APPLICATION_JSON.getName()); Assertions.assertNotNull(encoder); - Assertions.assertEquals(JsonPbCodec.class, encoder.getClass()); + Assertions.assertEquals(JsonCodec.class, encoder.getClass()); } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodecTest.java b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodecTest.java index f36f6cc76fe..2902052e528 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodecTest.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodecTest.java @@ -16,7 +16,6 @@ */ package org.apache.dubbo.remoting.http12.message.codec; -import org.apache.dubbo.remoting.http12.exception.DecodeException; import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; import org.apache.dubbo.rpc.model.FrameworkModel; @@ -380,20 +379,12 @@ void testPlainText() { Assertions.assertEquals("Hello, world", res); in = new ByteArrayInputStream(utf8Bytes); - codec = new PlainTextCodec("text/plain; charset=UTF-8"); - res = (String) codec.decode(in, String.class); + codec = new PlainTextCodec(); + res = (String) codec.decode(in, String.class, Charsets.UTF_8); Assertions.assertEquals("你好,世界", res); in = new ByteArrayInputStream(utf16Bytes); - codec = new PlainTextCodec("text/plain; charset=UTF-16"); - res = (String) codec.decode(in, String.class); + res = (String) codec.decode(in, String.class, Charsets.UTF_16); Assertions.assertEquals("你好,世界", res); } - - @Test - void testUnsupportedCharset() { - HttpMessageCodec codec = new PlainTextCodec("text/plain; charset=unsupported"); - Assertions.assertThrows( - DecodeException.class, () -> codec.decode(new ByteArrayInputStream(new byte[] {}), String.class)); - } } 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 dd557c61477..afad51c598b 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 @@ -103,13 +103,12 @@ public interface Constants { String H2_SETTINGS_INITIAL_WINDOW_SIZE_KEY = "dubbo.rpc.tri.initial-window-size"; String H2_SETTINGS_MAX_FRAME_SIZE_KEY = "dubbo.rpc.tri.max-frame-size"; String H2_SETTINGS_MAX_HEADER_LIST_SIZE_KEY = "dubbo.rpc.tri.max-header-list-size"; + String H2_SETTINGS_SUPPORT_NO_LOWER_HEADER_KEY = "dubbo.rpc.tri.support-no-lower-header"; + String H2_SETTINGS_IGNORE_1_0_0_KEY = "dubbo.rpc.tri.ignore-1.0.0-version"; + String H2_SETTINGS_RESOLVE_FALLBACK_TO_DEFAULT_KEY = "dubbo.rpc.tri.resolve-fallback-to-default"; + 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 ADAPTIVE_LOADBALANCE_ATTACHMENT_KEY = "lb_adaptive"; String ADAPTIVE_LOADBALANCE_START_TIME = "adaptive_startTime"; - String H2_SUPPORT_NO_LOWER_HEADER_KEY = "dubbo.rpc.tri.support-no-lower-header"; - String TRI_BUILTIN_SERVICE_INIT = "dubbo.tri.builtin.service.init"; - - String H2_IGNORE_1_0_0_KEY = "dubbo.rpc.tri.ignore-1.0.0-version"; - - String H2_RESOLVE_FALLBACK_TO_DEFAULT_KEY = "dubbo.rpc.tri.resolve-fallback-to-default"; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java new file mode 100644 index 00000000000..64f25fff4c5 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java @@ -0,0 +1,155 @@ +/* + * 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; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.remoting.http12.exception.UnimplementedException; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.model.MethodDescriptor; +import org.apache.dubbo.rpc.model.ProviderModel; +import org.apache.dubbo.rpc.model.ServiceDescriptor; +import org.apache.dubbo.rpc.protocol.tri.TripleCustomerProtocolWapper.TripleRequestWrapper; +import org.apache.dubbo.rpc.service.ServiceDescriptorInternalCache; +import org.apache.dubbo.rpc.stub.StubSuppliers; + +import java.util.Arrays; +import java.util.List; + +/** + * The MetaUtils provides utility methods for working with service descriptors and method descriptors. + */ +public final class DescriptorUtils { + + private DescriptorUtils() {} + + public static ServiceDescriptor findServiceDescriptor(Invoker invoker, String serviceName, boolean hasStub) + throws UnimplementedException { + ServiceDescriptor result; + if (hasStub) { + result = getStubServiceDescriptor(invoker.getUrl(), serviceName); + } else { + result = getReflectionServiceDescriptor(invoker.getUrl()); + } + if (result == null) { + throw new UnimplementedException("service:" + serviceName); + } + return result; + } + + public static ServiceDescriptor getStubServiceDescriptor(URL url, String serviceName) { + ServiceDescriptor serviceDescriptor; + if (url.getServiceModel() != null) { + serviceDescriptor = url.getServiceModel().getServiceModel(); + } else { + serviceDescriptor = StubSuppliers.getServiceDescriptor(serviceName); + } + return serviceDescriptor; + } + + public static ServiceDescriptor getReflectionServiceDescriptor(URL url) { + ProviderModel providerModel = (ProviderModel) url.getServiceModel(); + if (providerModel == null || providerModel.getServiceModel() == null) { + return null; + } + return providerModel.getServiceModel(); + } + + public static MethodDescriptor findMethodDescriptor( + ServiceDescriptor serviceDescriptor, String originalMethodName, boolean hasStub) + throws UnimplementedException { + MethodDescriptor result; + if (hasStub) { + result = serviceDescriptor.getMethods(originalMethodName).get(0); + } else { + result = findReflectionMethodDescriptor(serviceDescriptor, originalMethodName); + } + return result; + } + + public static MethodDescriptor findReflectionMethodDescriptor( + ServiceDescriptor serviceDescriptor, String methodName) { + MethodDescriptor methodDescriptor = null; + if (isGeneric(methodName)) { + // There should be one and only one + methodDescriptor = ServiceDescriptorInternalCache.genericService() + .getMethods(methodName) + .get(0); + } else if (isEcho(methodName)) { + // There should be one and only one + return ServiceDescriptorInternalCache.echoService() + .getMethods(methodName) + .get(0); + } else { + List methodDescriptors = serviceDescriptor.getMethods(methodName); + // try lower-case method + if (CollectionUtils.isEmpty(methodDescriptors)) { + String lowerMethod = Character.toLowerCase(methodName.charAt(0)) + methodName.substring(1); + methodDescriptors = serviceDescriptor.getMethods(lowerMethod); + } + if (CollectionUtils.isEmpty(methodDescriptors)) { + return null; + } + // In most cases there is only one method + if (methodDescriptors.size() == 1) { + methodDescriptor = methodDescriptors.get(0); + } + // generated unary method ,use unary type + // Response foo(Request) + // void foo(Request,StreamObserver) + if (methodDescriptors.size() == 2) { + if (methodDescriptors.get(1).getRpcType() == MethodDescriptor.RpcType.SERVER_STREAM) { + methodDescriptor = methodDescriptors.get(0); + } else if (methodDescriptors.get(0).getRpcType() == MethodDescriptor.RpcType.SERVER_STREAM) { + methodDescriptor = methodDescriptors.get(1); + } + } + } + return methodDescriptor; + } + + public static MethodDescriptor findTripleMethodDescriptor( + ServiceDescriptor serviceDescriptor, String methodName, byte[] data) { + MethodDescriptor methodDescriptor = findReflectionMethodDescriptor(serviceDescriptor, methodName); + if (methodDescriptor == null) { + List methodDescriptors = serviceDescriptor.getMethods(methodName); + TripleRequestWrapper request = TripleRequestWrapper.parseFrom(data); + String[] paramTypes = request.getArgTypes().toArray(new String[0]); + // wrapper mode the method can overload so maybe list + for (MethodDescriptor descriptor : methodDescriptors) { + // params type is array + if (Arrays.equals(descriptor.getCompatibleParamSignatures(), paramTypes)) { + methodDescriptor = descriptor; + break; + } + } + if (methodDescriptor == null) { + throw new UnimplementedException("method:" + methodName); + } + } + return methodDescriptor; + } + + private static boolean isGeneric(String methodName) { + return CommonConstants.$INVOKE.equals(methodName) || CommonConstants.$INVOKE_ASYNC.equals(methodName); + } + + private static boolean isEcho(String methodName) { + return CommonConstants.$ECHO.equals(methodName); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/HttpContextFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/HttpContextFilter.java new file mode 100644 index 00000000000..db1d46b8b7e --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/HttpContextFilter.java @@ -0,0 +1,82 @@ +/* + * 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; + +import org.apache.dubbo.common.constants.CommonConstants; +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.HttpResult; +import org.apache.dubbo.rpc.BaseFilter; +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.RpcServiceContext; + +@Activate(group = CommonConstants.PROVIDER, order = -29000) +public class HttpContextFilter implements Filter, BaseFilter.Listener { + + @Override + public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + if (invocation.get(TripleConstant.HANDLER_TYPE_KEY) == null) { + return invoker.invoke(invocation); + } + + HttpRequest request = (HttpRequest) invocation.get(TripleConstant.HTTP_REQUEST_KEY); + HttpResponse response = (HttpResponse) invocation.get(TripleConstant.HTTP_RESPONSE_KEY); + RpcServiceContext context = RpcContext.getServiceContext(); + context.setRemoteAddress(request.remoteHost(), request.remotePort()); + if (context.getLocalAddress() == null) { + context.setLocalAddress(request.localHost(), request.localPort()); + } + context.setRequest(request); + context.setResponse(response); + return invoker.invoke(invocation); + } + + @Override + public void onResponse(Result appResponse, Invoker invoker, Invocation invocation) { + if (invocation.get(TripleConstant.HANDLER_TYPE_KEY) == null) { + return; + } + + HttpResponse response = (HttpResponse) invocation.get(TripleConstant.HTTP_RESPONSE_KEY); + if (response.isEmpty()) { + return; + } + if (response.isContentEmpty()) { + if (appResponse.hasException()) { + return; + } + response.setBody(appResponse.getValue()); + } + response.commit(); + HttpResult result = response.toHttpResult(); + if (result.getBody() instanceof Throwable) { + appResponse.setException((Throwable) result.getBody()); + return; + } + appResponse.setValue(result); + appResponse.setException(null); + } + + @Override + public void onError(Throwable t, Invoker invoker, Invocation invocation) {} +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RequestMetadata.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RequestMetadata.java index 90477e7336e..63e13396252 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RequestMetadata.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RequestMetadata.java @@ -16,6 +16,7 @@ */ package org.apache.dubbo.rpc.protocol.tri; +import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.rpc.CancellationContext; import org.apache.dubbo.rpc.model.MethodDescriptor; import org.apache.dubbo.rpc.model.PackableMethod; @@ -55,7 +56,7 @@ public DefaultHttp2Headers toHeaders() { .authority(address) .method(HttpMethod.POST.asciiName()) .path("/" + service + "/" + method.getMethodName()) - .set(TripleHeaderEnum.CONTENT_TYPE_KEY.getHeader(), TripleConstant.CONTENT_PROTO) + .set(TripleHeaderEnum.CONTENT_TYPE_KEY.getHeader(), MediaType.APPLICATION_GRPC_PROTO.getName()) .set(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS); setIfNotNull(header, TripleHeaderEnum.TIMEOUT.getHeader(), timeout); if (!ignoreDefaultVersion || !"1.0.0".equals(version)) { @@ -67,7 +68,7 @@ public DefaultHttp2Headers toHeaders() { if (!Identity.MESSAGE_ENCODING.equals(compressor.getMessageEncoding())) { setIfNotNull(header, TripleHeaderEnum.GRPC_ENCODING.getHeader(), compressor.getMessageEncoding()); } - StreamUtils.convertAttachment(header, attachments, convertNoLowerHeader); + StreamUtils.putHeaders(header, attachments, convertNoLowerHeader); return header; } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/IllegalPathException.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RestProtocol.java similarity index 73% rename from dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/IllegalPathException.java rename to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RestProtocol.java index 1a8e2a265e5..88986368087 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/exception/IllegalPathException.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RestProtocol.java @@ -14,17 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.remoting.http12.exception; +package org.apache.dubbo.rpc.protocol.tri; -public class IllegalPathException extends RuntimeException { +import org.apache.dubbo.rpc.model.FrameworkModel; - public IllegalPathException() {} +public class RestProtocol extends TripleProtocol { - public IllegalPathException(String message) { - super(message); - } - - public String getPath() { - return super.getMessage(); + public RestProtocol(FrameworkModel frameworkModel) { + super(frameworkModel); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RpcInvocationBuildContext.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RpcInvocationBuildContext.java new file mode 100644 index 00000000000..4348fe14967 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/RpcInvocationBuildContext.java @@ -0,0 +1,51 @@ +/* + * 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; + +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; +import org.apache.dubbo.remoting.http12.message.MethodMetadata; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.model.MethodDescriptor; +import org.apache.dubbo.rpc.model.ServiceDescriptor; + +import java.util.Map; + +public interface RpcInvocationBuildContext { + + Invoker getInvoker(); + + boolean isHasStub(); + + String getMethodName(); + + MethodMetadata getMethodMetadata(); + + void setMethodMetadata(MethodMetadata methodMetadata); + + MethodDescriptor getMethodDescriptor(); + + void setMethodDescriptor(MethodDescriptor methodDescriptor); + + ServiceDescriptor getServiceDescriptor(); + + HttpMessageDecoder getHttpMessageDecoder(); + + HttpMessageEncoder getHttpMessageEncoder(); + + Map getAttributes(); +} 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 6ca934255fd..f36b78e6ebc 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 @@ -20,10 +20,7 @@ public class TripleConstant { - public static final String CONTENT_PROTO = "application/grpc+proto"; - public static final String APPLICATION_GRPC = "application/grpc"; - public static final String TEXT_PLAIN_UTF8 = "text/plain; encoding=utf-8"; - public static final String TRI_VERSION = "3.0-TRI"; + public static final String DEFAULT_VERSION = "1.0.0"; public static final String SERIALIZATION_KEY = "serialization"; public static final String TE_KEY = "te"; @@ -35,4 +32,13 @@ public class TripleConstant { public static final AsciiString HTTPS_SCHEME = AsciiString.of("https"); public static final AsciiString HTTP_SCHEME = AsciiString.of("http"); + + public static final String REMOTE_ADDRESS_KEY = "tri.remote.address"; + public static final String HANDLER_TYPE_KEY = "tri.handler.type"; + public static final String HTTP_REQUEST_KEY = "tri.http.request"; + 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/TripleHeaderEnum.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHeaderEnum.java index db1018e2d57..2c42b3f7bdf 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHeaderEnum.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHeaderEnum.java @@ -17,7 +17,9 @@ package org.apache.dubbo.rpc.protocol.tri; import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -41,9 +43,8 @@ public enum TripleHeaderEnum { CONSUMER_APP_NAME_KEY("tri-consumer-appname"), SERVICE_VERSION("tri-service-version"), SERVICE_GROUP("tri-service-group"), - + SERVICE_TIMEOUT("tri-service-timeout"), TRI_HEADER_CONVERT("tri-header-convert"), - TRI_EXCEPTION_CODE("tri-exception-code"), ; @@ -52,20 +53,95 @@ public enum TripleHeaderEnum { static final Set excludeAttachmentsSet = new HashSet<>(); static { - for (TripleHeaderEnum item : TripleHeaderEnum.values()) { + for (TripleHeaderEnum item : values()) { enumMap.put(item.getHeader(), item); } - excludeAttachmentsSet.add(CommonConstants.GROUP_KEY); - excludeAttachmentsSet.add(CommonConstants.INTERFACE_KEY); - excludeAttachmentsSet.add(CommonConstants.PATH_KEY); - excludeAttachmentsSet.add(CommonConstants.REMOTE_APPLICATION_KEY); - excludeAttachmentsSet.add(CommonConstants.APPLICATION_KEY); - excludeAttachmentsSet.add(TripleConstant.SERIALIZATION_KEY); - excludeAttachmentsSet.add(TripleConstant.TE_KEY); for (Http2Headers.PseudoHeaderName value : Http2Headers.PseudoHeaderName.values()) { excludeAttachmentsSet.add(value.value().toString()); } + + String[] internalHttpHeaders = new String[] { + CommonConstants.GROUP_KEY, + CommonConstants.INTERFACE_KEY, + CommonConstants.PATH_KEY, + CommonConstants.REMOTE_APPLICATION_KEY, + CommonConstants.APPLICATION_KEY, + TripleConstant.SERIALIZATION_KEY, + RestConstants.HEADER_SERVICE_VERSION, + RestConstants.HEADER_SERVICE_GROUP + }; + Collections.addAll(excludeAttachmentsSet, internalHttpHeaders); + + String[] excludeStandardHttpHeaders; + if (TripleProtocol.PASS_THROUGH_STANDARD_HTTP_HEADERS) { + excludeStandardHttpHeaders = new String[] { + "accept", + "accept-charset", + "accept-encoding", + "accept-language", + "cache-control", + "connection", + "content-length", + "content-md5", + "content-type", + "host" + }; + } else { + excludeStandardHttpHeaders = new String[] { + "accept", + "accept-charset", + "accept-datetime", + "accept-encoding", + "accept-language", + "access-control-request-headers", + "access-control-request-method", + "authorization", + "cache-control", + "connection", + "content-length", + "content-md5", + "content-type", + "cookie", + "date", + "dnt", + "expect", + "forwarded", + "from", + "host", + "http2-settings", + "if-match", + "if-modified-since", + "if-none-match", + "if-range", + "if-unmodified-since", + "max-forwards", + "origin", + "pragma", + "proxy-authorization", + "range", + "referer", + "sec-fetch-dest", + "sec-fetch-mode", + "sec-fetch-site", + "sec-fetch-user", + "te", + "trailer", + "upgrade", + "upgrade-insecure-requests", + "user-agent", + "x-csrf-token", + "x-forwarded-for", + "x-forwarded-host", + "x-forwarded-proto", + "x-http-method-override", + "x-real-ip", + "x-request-id", + "x-requested-with" + }; + } + + Collections.addAll(excludeAttachmentsSet, excludeStandardHttpHeaders); } private final String header; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java index 1a82e343a3c..ea088c9af19 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java @@ -20,7 +20,6 @@ import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.ConfigurationUtils; import org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.common.extension.ExtensionLoader; import org.apache.dubbo.remoting.ChannelHandler; import org.apache.dubbo.remoting.api.AbstractWireProtocol; import org.apache.dubbo.remoting.api.pu.ChannelHandlerPretender; @@ -32,7 +31,6 @@ import org.apache.dubbo.remoting.http12.netty4.h2.NettyHttp2FrameCodec; import org.apache.dubbo.remoting.http12.netty4.h2.NettyHttp2ProtocolSelectorHandler; import org.apache.dubbo.remoting.utils.UrlUtils; -import org.apache.dubbo.rpc.HeaderFilter; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.model.ScopeModelAware; import org.apache.dubbo.rpc.protocol.tri.h12.TripleProtocolDetector; @@ -43,7 +41,6 @@ import org.apache.dubbo.rpc.protocol.tri.transport.TripleTailHandler; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import io.netty.channel.ChannelDuplexHandler; @@ -60,7 +57,6 @@ import io.netty.handler.flush.FlushConsolidationHandler; import io.netty.handler.logging.LogLevel; -import static org.apache.dubbo.common.constants.CommonConstants.HEADER_FILTER_KEY; import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_ENABLE_PUSH_KEY; import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_HEADER_TABLE_SIZE_KEY; import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_INITIAL_WINDOW_SIZE_KEY; @@ -84,7 +80,6 @@ public class TripleHttp2Protocol extends AbstractWireProtocol implements ScopeMo public static final Http2FrameLogger SERVER_LOGGER = new Http2FrameLogger(LogLevel.DEBUG, "H2_SERVER"); - private ExtensionLoader filtersLoader; private FrameworkModel frameworkModel; public TripleHttp2Protocol() { @@ -94,7 +89,6 @@ public TripleHttp2Protocol() { @Override public void setFrameworkModel(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; - this.filtersLoader = frameworkModel.getExtensionLoader(HeaderFilter.class); } @Override @@ -158,12 +152,6 @@ private void configurerHttp1Handlers(URL url, List handlers) { private void configurerHttp2Handlers(URL url, List handlers) { Configuration config = ConfigurationUtils.getGlobalConfiguration(url.getOrDefaultApplicationModel()); - final List headFilters; - if (filtersLoader != null) { - headFilters = filtersLoader.getActivateExtension(url, HEADER_FILTER_KEY); - } else { - headFilters = Collections.emptyList(); - } final Http2FrameCodec codec = TripleHttp2FrameCodecBuilder.forServer() .customizeConnection((connection) -> connection .remote() diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java index 4754d2bf416..3da5e99cbf3 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java @@ -76,6 +76,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY; import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_DESTROY_INVOKER; import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_REQUEST; +import static org.apache.dubbo.remoting.http12.message.MediaType.APPLICATION_GRPC_PROTO; import static org.apache.dubbo.rpc.Constants.COMPRESSOR_KEY; import static org.apache.dubbo.rpc.Constants.TOKEN_KEY; import static org.apache.dubbo.rpc.model.MethodDescriptor.RpcType.UNARY; @@ -289,7 +290,7 @@ RequestMetadata createRequest(MethodDescriptor methodDescriptor, Invocation invo meta.packableMethod = (PackableMethod) methodDescriptor; } else { meta.packableMethod = packableMethodCache.computeIfAbsent( - methodDescriptor, (md) -> packableMethodFactory.create(md, url, TripleConstant.CONTENT_PROTO)); + methodDescriptor, (md) -> packableMethodFactory.create(md, url, APPLICATION_GRPC_PROTO.getName())); } meta.convertNoLowerHeader = TripleProtocol.CONVERT_NO_LOWER_HEADER; meta.ignoreDefaultVersion = TripleProtocol.IGNORE_1_0_0_VERSION; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java index f5d0223467d..fcc85f391ef 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java @@ -17,6 +17,7 @@ package org.apache.dubbo.rpc.protocol.tri; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.ConfigurationUtils; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; @@ -34,6 +35,8 @@ import org.apache.dubbo.rpc.protocol.AbstractExporter; import org.apache.dubbo.rpc.protocol.AbstractProtocol; import org.apache.dubbo.rpc.protocol.tri.compressor.DeCompressor; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.DefaultRequestMappingRegistry; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingRegistry; import org.apache.dubbo.rpc.protocol.tri.service.TriBuiltinService; import java.util.Objects; @@ -48,40 +51,38 @@ import static org.apache.dubbo.common.constants.CommonConstants.THREAD_NAME_KEY; import static org.apache.dubbo.config.Constants.CLIENT_THREAD_POOL_NAME; import static org.apache.dubbo.config.Constants.SERVER_THREAD_POOL_NAME; -import static org.apache.dubbo.rpc.Constants.H2_IGNORE_1_0_0_KEY; -import static org.apache.dubbo.rpc.Constants.H2_RESOLVE_FALLBACK_TO_DEFAULT_KEY; -import static org.apache.dubbo.rpc.Constants.H2_SUPPORT_NO_LOWER_HEADER_KEY; +import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_IGNORE_1_0_0_KEY; +import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_PASS_THROUGH_STANDARD_HTTP_HEADERS; +import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_RESOLVE_FALLBACK_TO_DEFAULT_KEY; +import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_SUPPORT_NO_LOWER_HEADER_KEY; public class TripleProtocol extends AbstractProtocol { private static final Logger logger = LoggerFactory.getLogger(TripleProtocol.class); + private final PathResolver pathResolver; + private final RequestMappingRegistry mappingRegistry; private final TriBuiltinService triBuiltinService; private final String acceptEncodings; - /** - * There is only one - */ public static boolean CONVERT_NO_LOWER_HEADER = false; - public static boolean IGNORE_1_0_0_VERSION = false; - public static boolean RESOLVE_FALLBACK_TO_DEFAULT = true; + public static boolean PASS_THROUGH_STANDARD_HTTP_HEADERS = false; public TripleProtocol(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; - this.triBuiltinService = new TriBuiltinService(frameworkModel); - this.pathResolver = - frameworkModel.getExtensionLoader(PathResolver.class).getDefaultExtension(); - CONVERT_NO_LOWER_HEADER = ConfigurationUtils.getEnvConfiguration(ApplicationModel.defaultModel()) - .getBoolean(H2_SUPPORT_NO_LOWER_HEADER_KEY, true); - IGNORE_1_0_0_VERSION = ConfigurationUtils.getEnvConfiguration(ApplicationModel.defaultModel()) - .getBoolean(H2_IGNORE_1_0_0_KEY, false); - RESOLVE_FALLBACK_TO_DEFAULT = ConfigurationUtils.getEnvConfiguration(ApplicationModel.defaultModel()) - .getBoolean(H2_RESOLVE_FALLBACK_TO_DEFAULT_KEY, true); + triBuiltinService = new TriBuiltinService(frameworkModel); + pathResolver = frameworkModel.getDefaultExtension(PathResolver.class); + mappingRegistry = frameworkModel.getBeanFactory().getOrRegisterBean(DefaultRequestMappingRegistry.class); Set supported = frameworkModel.getExtensionLoader(DeCompressor.class).getSupportedExtensions(); - this.acceptEncodings = String.join(",", supported); + acceptEncodings = String.join(",", supported); + Configuration conf = ConfigurationUtils.getEnvConfiguration(ApplicationModel.defaultModel()); + CONVERT_NO_LOWER_HEADER = conf.getBoolean(H2_SETTINGS_SUPPORT_NO_LOWER_HEADER_KEY, true); + IGNORE_1_0_0_VERSION = conf.getBoolean(H2_SETTINGS_IGNORE_1_0_0_KEY, false); + RESOLVE_FALLBACK_TO_DEFAULT = conf.getBoolean(H2_SETTINGS_RESOLVE_FALLBACK_TO_DEFAULT_KEY, true); + PASS_THROUGH_STANDARD_HTTP_HEADERS = conf.getBoolean(H2_SETTINGS_PASS_THROUGH_STANDARD_HTTP_HEADERS, false); } @Override @@ -93,11 +94,13 @@ public int getDefaultPort() { public Exporter export(Invoker invoker) throws RpcException { URL url = invoker.getUrl(); String key = serviceKey(url); - final AbstractExporter exporter = new AbstractExporter(invoker) { + AbstractExporter exporter = new AbstractExporter(invoker) { @Override public void afterUnExport() { pathResolver.remove(url.getServiceKey()); pathResolver.remove(url.getServiceModel().getServiceModel().getInterfaceName()); + // unregister rest request mapping + mappingRegistry.unregister(invoker); // set service status if (triBuiltinService.enable()) { triBuiltinService @@ -133,14 +136,16 @@ public void afterUnExport() { if (previous != null) { logger.info("Already exists an invoker[" + previous.getUrl() + "] on path[" + url.getServiceModel().getServiceModel().getInterfaceName() - + "], dubbo will skip override with invoker[" - + url + "]"); + + "], dubbo will skip override with invoker[" + url + "]"); } else { logger.info("Add fallback triple invoker[" + url + "] to path[" + url.getServiceModel().getServiceModel().getInterfaceName() + "] with invoker[" + url + "]"); } } + // register rest request mapping + mappingRegistry.register(invoker); + // set service status if (triBuiltinService.enable()) { triBuiltinService @@ -187,10 +192,11 @@ protected Invoker protocolBindingRefer(Class type, URL url) throws Rpc @Override public void destroy() { if (logger.isInfoEnabled()) { - logger.info("Destroying protocol [" + this.getClass().getSimpleName() + "] ..."); + logger.info("Destroying protocol [" + getClass().getSimpleName() + "] ..."); } PortUnificationExchanger.close(); pathResolver.destroy(); + mappingRegistry.destroy(); super.destroy(); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java index 1b8d5090888..d3bf0ffb5ec 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java @@ -20,50 +20,30 @@ import org.apache.dubbo.common.constants.CommonConstants; 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.JsonUtils; import org.apache.dubbo.remoting.http12.HttpChannel; -import org.apache.dubbo.remoting.http12.HttpHeaderNames; -import org.apache.dubbo.remoting.http12.HttpHeaders; import org.apache.dubbo.remoting.http12.HttpInputMessage; -import org.apache.dubbo.remoting.http12.HttpMetadata; import org.apache.dubbo.remoting.http12.HttpStatus; import org.apache.dubbo.remoting.http12.HttpTransportListener; import org.apache.dubbo.remoting.http12.RequestMetadata; import org.apache.dubbo.remoting.http12.exception.HttpStatusException; -import org.apache.dubbo.remoting.http12.exception.IllegalPathException; -import org.apache.dubbo.remoting.http12.exception.UnimplementedException; -import org.apache.dubbo.remoting.http12.exception.UnsupportedMediaTypeException; -import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; import org.apache.dubbo.remoting.http12.message.MethodMetadata; -import org.apache.dubbo.remoting.http12.message.codec.CodecUtils; import org.apache.dubbo.rpc.HeaderFilter; import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.PathResolver; import org.apache.dubbo.rpc.RpcInvocation; -import org.apache.dubbo.rpc.TriRpcStatus; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.model.MethodDescriptor; -import org.apache.dubbo.rpc.model.ProviderModel; -import org.apache.dubbo.rpc.model.ServiceDescriptor; +import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils; +import org.apache.dubbo.rpc.protocol.tri.RpcInvocationBuildContext; 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.DefaultRequestRouter; +import org.apache.dubbo.rpc.protocol.tri.route.RequestRouter; import org.apache.dubbo.rpc.protocol.tri.stream.StreamUtils; -import org.apache.dubbo.rpc.service.ServiceDescriptorInternalCache; -import org.apache.dubbo.rpc.stub.StubSuppliers; -import java.io.InputStream; import java.lang.reflect.InvocationTargetException; -import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.concurrent.Executor; -import java.util.function.Supplier; -import static org.apache.dubbo.common.constants.CommonConstants.HEADER_FILTER_KEY; import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_ERROR_USE_THREAD_POOL; import static org.apache.dubbo.common.constants.LoggerCodeConstants.INTERNAL_ERROR; import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_PARSE; @@ -74,65 +54,37 @@ public abstract class AbstractServerTransportListener
headerFilters; - private HttpMessageDecoder httpMessageDecoder; - - private Invoker invoker; - - private ServiceDescriptor serviceDescriptor; - - private MethodDescriptor methodDescriptor; - - private RpcInvocation rpcInvocation; - - private MethodMetadata methodMetadata; - - private HEADER httpMetadata; - private Executor executor; - - private boolean hasStub; - + private HEADER httpMetadata; + private RpcInvocationBuildContext context; private HttpMessageListener httpMessageListener; - protected CodecUtils codecUtils; - public AbstractServerTransportListener(FrameworkModel frameworkModel, URL url, HttpChannel httpChannel) { this.frameworkModel = frameworkModel; this.url = url; this.httpChannel = httpChannel; - this.pathResolver = - frameworkModel.getExtensionLoader(PathResolver.class).getDefaultExtension(); - this.headerFilters = - frameworkModel.getExtensionLoader(HeaderFilter.class).getActivateExtension(url, HEADER_FILTER_KEY); - this.codecUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CodecUtils.class); - } - - protected Executor initializeExecutor(HEADER metadata) { - // default direct executor - return Runnable::run; + requestRouter = frameworkModel.getBeanFactory().getOrRegisterBean(DefaultRequestRouter.class); + headerFilters = frameworkModel + .getExtensionLoader(HeaderFilter.class) + .getActivateExtension(url, CommonConstants.HEADER_FILTER_KEY); } @Override public void onMetadata(HEADER metadata) { - try { - this.executor = initializeExecutor(metadata); + executor = initializeExecutor(metadata); } catch (Throwable throwable) { LOGGER.error(COMMON_ERROR_USE_THREAD_POOL, "", "", "initialize executor fail.", throwable); onError(throwable); return; } - if (this.executor == null) { + if (executor == null) { LOGGER.error(INTERNAL_ERROR, "", "", "executor must be not null."); onError(new NullPointerException("initializeExecutor return null")); return; @@ -140,62 +92,52 @@ public void onMetadata(HEADER metadata) { executor.execute(() -> { try { doOnMetadata(metadata); - } catch (Throwable throwable) { - LOGGER.error(INTERNAL_ERROR, "", "", "server internal error", throwable); - onError(throwable); + } catch (Throwable t) { + logError(t); + onError(t); } }); } + protected Executor initializeExecutor(HEADER metadata) { + // default direct executor + return Runnable::run; + } + protected void doOnMetadata(HEADER metadata) { onPrepareMetadata(metadata); - this.httpMetadata = metadata; - String path = metadata.path(); - HttpHeaders headers = metadata.headers(); - // 1.check necessary header - String contentType = headers.getFirst(HttpHeaderNames.CONTENT_TYPE.getName()); - if (contentType == null) { - throw new UnsupportedMediaTypeException( - "'" + HttpHeaderNames.CONTENT_TYPE.getName() + "' must be not null."); - } + httpMetadata = metadata; - // 2. check service - String[] parts = path.split("/"); - if (parts.length != 3) { - throw new IllegalPathException(path); - } - String serviceName = parts[1]; - this.hasStub = pathResolver.hasNativeStub(path); - this.invoker = getInvoker(metadata, serviceName); - if (invoker == null) { - throw new UnimplementedException(serviceName); + context = requestRouter.route(url, metadata, httpChannel); + if (context == null) { + throw new HttpStatusException(HttpStatus.NOT_FOUND.getCode(), "Invoker not found"); } - this.httpMessageDecoder = - codecUtils.determineHttpMessageDecoder(getFrameworkModel(), headers.getContentType(), getUrl()); - setServiceDescriptor(findServiceDescriptor(invoker, serviceName, hasStub)); - setHttpMessageListener(newHttpMessageListener()); + + setHttpMessageListener(buildHttpMessageListener()); onMetadataCompletion(metadata); } - protected abstract HttpMessageListener newHttpMessageListener(); + protected abstract HttpMessageListener buildHttpMessageListener(); @Override public void onData(MESSAGE message) { - this.executor.execute(() -> { + executor.execute(() -> { try { doOnData(message); - } catch (Throwable e) { - LOGGER.error(INTERNAL_ERROR, "", "", "server internal error", e); - onError(e); + } catch (Throwable t) { + logError(t); + onError(t); } }); } protected void doOnData(MESSAGE message) { - // decode message + if (httpMessageListener == null) { + return; + } onPrepareData(message); - InputStream body = message.getBody(); - httpMessageListener.onMessage(body); + // decode message + httpMessageListener.onMessage(message.getBody()); onDataCompletion(message); } @@ -215,6 +157,17 @@ protected void onDataCompletion(MESSAGE message) { // default no op } + protected void logError(Throwable t) { + if (t instanceof HttpStatusException) { + HttpStatusException e = (HttpStatusException) t; + if (e.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR.getCode()) { + LOGGER.debug("http status exception", e); + return; + } + } + LOGGER.error(INTERNAL_ERROR, "", "", "server internal error", t); + } + protected void onError(Throwable throwable) { // default rethrow if (throwable instanceof RuntimeException) { @@ -231,266 +184,76 @@ protected void onError(Throwable throwable) { throw new HttpStatusException(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), throwable); } - private Invoker getInvoker(HEADER metadata, String serviceName) { - HttpHeaders headers = metadata.headers(); - final String version = headers.containsKey(TripleHeaderEnum.SERVICE_VERSION.getHeader()) - ? headers.get(TripleHeaderEnum.SERVICE_VERSION.getHeader()).toString() - : null; - final String group = headers.containsKey(TripleHeaderEnum.SERVICE_GROUP.getHeader()) - ? headers.get(TripleHeaderEnum.SERVICE_GROUP.getHeader()).toString() - : null; - final 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, "1.0.0")); - } - if (invoker == null && TripleProtocol.RESOLVE_FALLBACK_TO_DEFAULT) { - invoker = pathResolver.resolve(serviceName); - } - return invoker; - } - - private static ServiceDescriptor findServiceDescriptor(Invoker invoker, String serviceName, boolean hasStub) - throws UnimplementedException { - ServiceDescriptor result; - if (hasStub) { - result = getStubServiceDescriptor(invoker.getUrl(), serviceName); - } else { - result = getReflectionServiceDescriptor(invoker.getUrl()); + protected RpcInvocation buildRpcInvocation(RpcInvocationBuildContext context) { + MethodDescriptor methodDescriptor = context.getMethodDescriptor(); + if (methodDescriptor == null) { + methodDescriptor = DescriptorUtils.findMethodDescriptor( + context.getServiceDescriptor(), context.getMethodName(), context.isHasStub()); + context.setMethodDescriptor(methodDescriptor); } - if (result == null) { - throw new UnimplementedException("service:" + serviceName); + MethodMetadata methodMetadata = context.getMethodMetadata(); + if (methodMetadata == null) { + methodMetadata = MethodMetadata.fromMethodDescriptor(methodDescriptor); + context.setMethodMetadata(methodMetadata); } - return result; - } - - protected static MethodDescriptor findMethodDescriptor( - ServiceDescriptor serviceDescriptor, String originalMethodName, boolean hasStub) - throws UnimplementedException { - MethodDescriptor result; - if (hasStub) { - result = serviceDescriptor.getMethods(originalMethodName).get(0); - } else { - result = findReflectionMethodDescriptor(serviceDescriptor, originalMethodName); - } - return result; - } - protected RpcInvocation buildRpcInvocation( - Invoker invoker, ServiceDescriptor serviceDescriptor, MethodDescriptor methodDescriptor) { - final URL url = invoker.getUrl(); + Invoker invoker = context.getInvoker(); + URL url = invoker.getUrl(); RpcInvocation inv = new RpcInvocation( url.getServiceModel(), methodDescriptor.getMethodName(), - serviceDescriptor.getInterfaceName(), + context.getServiceDescriptor().getInterfaceName(), url.getProtocolServiceKey(), methodDescriptor.getParameterClasses(), new Object[0]); inv.setTargetServiceUniqueName(url.getServiceKey()); inv.setReturnTypes(methodDescriptor.getReturnTypes()); - Map headers = getHttpMetadata().headers().toSingleValueMap(); - Map requestMetadata = headersToMap(headers, () -> { - return Optional.ofNullable(headers.get(TripleHeaderEnum.TRI_HEADER_CONVERT.getHeader())) - .map(CharSequence::toString) - .orElse(null); - }); - inv.setObjectAttachments(StreamUtils.toAttachments(requestMetadata)); + inv.setObjectAttachments(StreamUtils.toAttachments(httpMetadata.headers())); + inv.put(TripleConstant.REMOTE_ADDRESS_KEY, httpChannel.remoteAddress()); + inv.getAttributes().putAll(context.getAttributes()); - inv.put("tri.remote.address", httpChannel.remoteAddress()); // customizer RpcInvocation headerFilters.forEach(f -> f.invoke(invoker, inv)); - return inv; - } - - protected static ServiceDescriptor getStubServiceDescriptor(URL url, String serviceName) { - ServiceDescriptor serviceDescriptor; - if (url.getServiceModel() != null) { - serviceDescriptor = url.getServiceModel().getServiceModel(); - } else { - serviceDescriptor = StubSuppliers.getServiceDescriptor(serviceName); - } - return serviceDescriptor; - } - protected static ServiceDescriptor getReflectionServiceDescriptor(URL url) { - ProviderModel providerModel = (ProviderModel) url.getServiceModel(); - if (providerModel == null || providerModel.getServiceModel() == null) { - return null; - } - return providerModel.getServiceModel(); + return onBuildRpcInvocationCompletion(inv); } - protected static boolean isEcho(String methodName) { - return CommonConstants.$ECHO.equals(methodName); - } - - protected static boolean isGeneric(String methodName) { - return CommonConstants.$INVOKE.equals(methodName) || CommonConstants.$INVOKE_ASYNC.equals(methodName); - } - - protected static MethodDescriptor findReflectionMethodDescriptor( - ServiceDescriptor serviceDescriptor, String methodName) { - MethodDescriptor methodDescriptor = null; - if (isGeneric(methodName)) { - // There should be one and only one - methodDescriptor = ServiceDescriptorInternalCache.genericService() - .getMethods(methodName) - .get(0); - } else if (isEcho(methodName)) { - // There should be one and only one - return ServiceDescriptorInternalCache.echoService() - .getMethods(methodName) - .get(0); - } else { - List methodDescriptors = serviceDescriptor.getMethods(methodName); - // try lower-case method - if (CollectionUtils.isEmpty(methodDescriptors)) { - final String lowerMethod = Character.toLowerCase(methodName.charAt(0)) + methodName.substring(1); - methodDescriptors = serviceDescriptor.getMethods(lowerMethod); - } - if (CollectionUtils.isEmpty(methodDescriptors)) { - return null; - } - // In most cases there is only one method - if (methodDescriptors.size() == 1) { - methodDescriptor = methodDescriptors.get(0); - } - // generated unary method ,use unary type - // Response foo(Request) - // void foo(Request,StreamObserver) - if (methodDescriptors.size() == 2) { - if (methodDescriptors.get(1).getRpcType() == MethodDescriptor.RpcType.SERVER_STREAM) { - methodDescriptor = methodDescriptors.get(0); - } else if (methodDescriptors.get(0).getRpcType() == MethodDescriptor.RpcType.SERVER_STREAM) { - methodDescriptor = methodDescriptors.get(1); - } + protected RpcInvocation onBuildRpcInvocationCompletion(RpcInvocation invocation) { + String timeoutString = httpMetadata.headers().getFirst(TripleHeaderEnum.SERVICE_TIMEOUT.getHeader()); + try { + if (null != timeoutString) { + Long timeout = Long.parseLong(timeoutString); + invocation.put(CommonConstants.TIMEOUT_KEY, timeout); } + } catch (Throwable t) { + LOGGER.warn( + PROTOCOL_FAILED_PARSE, + "", + "", + String.format( + "Failed to parse request timeout set from:%s, service=%s " + "method=%s", + timeoutString, context.getServiceDescriptor().getInterfaceName(), context.getMethodName())); } - return methodDescriptor; + return invocation; } - protected FrameworkModel getFrameworkModel() { + protected final FrameworkModel getFrameworkModel() { return frameworkModel; } - protected HEADER getHttpMetadata() { + protected final HEADER getHttpMetadata() { return httpMetadata; } - protected Invoker getInvoker() { - return invoker; - } - - protected ServiceDescriptor getServiceDescriptor() { - return serviceDescriptor; - } - - protected MethodDescriptor getMethodDescriptor() { - return methodDescriptor; - } - - public void setServiceDescriptor(ServiceDescriptor serviceDescriptor) { - this.serviceDescriptor = serviceDescriptor; - } - - public void setMethodDescriptor(MethodDescriptor methodDescriptor) { - this.methodDescriptor = methodDescriptor; - } - - public void setMethodMetadata(MethodMetadata methodMetadata) { - this.methodMetadata = methodMetadata; - } - - protected RpcInvocation getRpcInvocation() { - return rpcInvocation; - } - - public void setRpcInvocation(RpcInvocation rpcInvocation) { - this.rpcInvocation = rpcInvocation; - } - - protected MethodMetadata getMethodMetadata() { - return methodMetadata; + public final RpcInvocationBuildContext getContext() { + return context; } - protected HttpMessageDecoder getHttpMessageDecoder() { - return this.httpMessageDecoder; - } - - protected CodecUtils getCodecUtils() { - return this.codecUtils; + protected final HttpMessageListener getHttpMessageListener() { + return httpMessageListener; } protected void setHttpMessageListener(HttpMessageListener httpMessageListener) { this.httpMessageListener = httpMessageListener; } - - protected HttpMessageListener getHttpMessageListener() { - return httpMessageListener; - } - - protected PathResolver getPathResolver() { - return pathResolver; - } - - protected final URL getUrl() { - return url; - } - - protected HttpMetadata getMetadata() { - return httpMetadata; - } - - public boolean isHasStub() { - return hasStub; - } - - protected Map headersToMap( - Map headers, Supplier convertUpperHeaderSupplier) { - if (headers == null) { - return Collections.emptyMap(); - } - Map attachments = new HashMap<>(headers.size()); - for (Map.Entry header : headers.entrySet()) { - String key = header.getKey(); - if (key.endsWith(TripleConstant.HEADER_BIN_SUFFIX) - && key.length() > TripleConstant.HEADER_BIN_SUFFIX.length()) { - try { - String realKey = key.substring(0, key.length() - TripleConstant.HEADER_BIN_SUFFIX.length()); - byte[] value = StreamUtils.decodeASCIIByte(header.getValue()); - attachments.put(realKey, value); - } catch (Exception e) { - LOGGER.error(PROTOCOL_FAILED_PARSE, "", "", "Failed to parse response attachment key=" + key, e); - } - } else { - attachments.put(key, header.getValue()); - } - } - - // try converting upper key - Object obj = convertUpperHeaderSupplier.get(); - if (obj == null) { - return attachments; - } - if (obj instanceof String) { - String json = TriRpcStatus.decodeMessage((String) obj); - Map map = JsonUtils.toJavaObject(json, Map.class); - for (Map.Entry entry : map.entrySet()) { - Object val = attachments.remove(entry.getKey()); - if (val != null) { - attachments.put(entry.getValue(), val); - } - } - } else { - // If convertUpperHeaderSupplier does not return String, just fail... - // Internal invocation, use INTERNAL_ERROR instead. - - LOGGER.error( - INTERNAL_ERROR, - "wrong internal invocation", - "", - "Triple convertNoLowerCaseHeader error, obj is not String"); - } - return attachments; - } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/CompressibleEncoder.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/CompressibleEncoder.java index 60c9caf5291..0da3810af25 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/CompressibleEncoder.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/CompressibleEncoder.java @@ -22,6 +22,7 @@ import org.apache.dubbo.rpc.protocol.tri.compressor.Compressor; import java.io.OutputStream; +import java.nio.charset.Charset; public class CompressibleEncoder implements HttpMessageEncoder { @@ -37,12 +38,12 @@ public void setCompressor(Compressor compressor) { this.compressor = compressor; } - public void encode(OutputStream outputStream, Object data) throws EncodeException { - delegate.encode(compressor.decorate(outputStream), data); + public void encode(OutputStream outputStream, Object data, Charset charset) throws EncodeException { + delegate.encode(compressor.decorate(outputStream), data, charset); } - public void encode(OutputStream outputStream, Object[] data) throws EncodeException { - delegate.encode(outputStream, data); + public void encode(OutputStream outputStream, Object[] data, Charset charset) throws EncodeException { + delegate.encode(outputStream, data, charset); } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.java new file mode 100644 index 00000000000..b50a7c3d982 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.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.h12; + +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.remoting.http12.message.MediaType; + +import java.io.InputStream; +import java.nio.charset.Charset; + +public final class HttpMessageDecoderWrapper implements HttpMessageDecoder { + + private final Charset charset; + private final HttpMessageDecoder httpMessageDecoder; + + public HttpMessageDecoderWrapper(Charset charset, HttpMessageDecoder httpMessageDecoder) { + this.charset = charset; + this.httpMessageDecoder = httpMessageDecoder; + } + + @Override + public boolean supports(String mediaType) { + return httpMessageDecoder.supports(mediaType); + } + + @Override + public Object decode(InputStream inputStream, Class targetType, Charset charset) throws DecodeException { + return httpMessageDecoder.decode(inputStream, targetType, this.charset); + } + + @Override + public Object[] decode(InputStream inputStream, Class[] targetTypes, Charset charset) throws DecodeException { + return httpMessageDecoder.decode(inputStream, targetTypes, this.charset); + } + + @Override + public Object decode(InputStream inputStream, Class targetType) throws DecodeException { + return httpMessageDecoder.decode(inputStream, targetType, charset); + } + + @Override + public Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { + return httpMessageDecoder.decode(inputStream, targetTypes, charset); + } + + @Override + public MediaType mediaType() { + return httpMessageDecoder.mediaType(); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageEncoderWrapper.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageEncoderWrapper.java new file mode 100644 index 00000000000..0eb9f8921c7 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageEncoderWrapper.java @@ -0,0 +1,82 @@ +/* + * 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.remoting.http12.exception.EncodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; +import org.apache.dubbo.remoting.http12.message.MediaType; + +import java.io.OutputStream; +import java.nio.charset.Charset; + +public final class HttpMessageEncoderWrapper implements HttpMessageEncoder { + + private final Charset charset; + private final MediaType mediaType; + private final HttpMessageEncoder httpMessageEncoder; + + public HttpMessageEncoderWrapper(Charset charset, MediaType mediaType, HttpMessageEncoder httpMessageEncoder) { + this.charset = charset; + this.mediaType = mediaType; + this.httpMessageEncoder = httpMessageEncoder; + } + + public HttpMessageEncoderWrapper(Charset charset, HttpMessageEncoder httpMessageEncoder) { + this.charset = charset; + mediaType = httpMessageEncoder.mediaType(); + this.httpMessageEncoder = httpMessageEncoder; + } + + public Charset charset() { + return charset; + } + + @Override + public MediaType mediaType() { + return mediaType; + } + + @Override + public boolean supports(String mediaType) { + return httpMessageEncoder.supports(mediaType); + } + + @Override + public void encode(OutputStream outputStream, Object data, Charset charset) throws EncodeException { + httpMessageEncoder.encode(outputStream, data, this.charset); + } + + @Override + public void encode(OutputStream outputStream, Object[] data, Charset charset) throws EncodeException { + httpMessageEncoder.encode(outputStream, data, this.charset); + } + + @Override + public void encode(OutputStream outputStream, Object data) throws EncodeException { + httpMessageEncoder.encode(outputStream, data, charset); + } + + @Override + public void encode(OutputStream outputStream, Object[] data) throws EncodeException { + httpMessageEncoder.encode(outputStream, data, charset); + } + + @Override + public String contentType() { + return mediaType.getName() + ";charset=" + charset.name(); + } +} 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 new file mode 100644 index 00000000000..2dbfea05fa8 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpRequestHandlerMapping.java @@ -0,0 +1,121 @@ +/* + * 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/TripleProtocolDetector.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/TripleProtocolDetector.java index 9c7cd7138e6..5fa91a97f7c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/TripleProtocolDetector.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/TripleProtocolDetector.java @@ -20,16 +20,14 @@ import org.apache.dubbo.remoting.buffer.ByteBufferBackedChannelBuffer; import org.apache.dubbo.remoting.buffer.ChannelBuffer; import org.apache.dubbo.remoting.buffer.ChannelBuffers; +import org.apache.dubbo.remoting.http12.HttpMethods; import io.netty.handler.codec.http2.Http2CodecUtil; -import static java.lang.Math.min; - public class TripleProtocolDetector implements ProtocolDetector { public static final String HTTP_VERSION = "HTTP_VERSION"; - - private final ChannelBuffer clientPrefaceString = new ByteBufferBackedChannelBuffer( + private static final ChannelBuffer CLIENT_PREFACE_STRING = new ByteBufferBackedChannelBuffer( Http2CodecUtil.connectionPrefaceBuf().nioBuffer()); @Override @@ -38,8 +36,8 @@ public Result detect(ChannelBuffer in) { if (in.readableBytes() < 2) { return Result.needMoreData(); } - byte[] magics = new byte[5]; - in.getBytes(in.readerIndex(), magics, 0, 5); + byte[] magics = new byte[7]; + in.getBytes(in.readerIndex(), magics, 0, 7); if (isHttp(magics)) { Result recognized = Result.recognized(); recognized.setAttribute(HTTP_VERSION, HttpVersion.HTTP1.getVersion()); @@ -48,9 +46,9 @@ public Result detect(ChannelBuffer in) { in.resetReaderIndex(); // http2 - int prefaceLen = clientPrefaceString.readableBytes(); - int bytesRead = min(in.readableBytes(), prefaceLen); - if (bytesRead == 0 || !ChannelBuffers.prefixEquals(in, clientPrefaceString, bytesRead)) { + int prefaceLen = CLIENT_PREFACE_STRING.readableBytes(); + int bytesRead = Math.min(in.readableBytes(), prefaceLen); + if (bytesRead == 0 || !ChannelBuffers.prefixEquals(in, CLIENT_PREFACE_STRING, bytesRead)) { return Result.unrecognized(); } if (bytesRead == prefaceLen) { @@ -62,16 +60,22 @@ public Result detect(ChannelBuffer in) { } private static boolean isHttp(byte[] magic) { - if (magic[0] == 'G' && magic[1] == 'E' && magic[2] == 'T') { - return true; - } - if (magic[0] == 'P' && magic[1] == 'O' && magic[2] == 'S' && magic[3] == 'T') { - return true; + for (int i = 0; i < 8; i++) { + byte[] methodBytes = HttpMethods.HTTP_METHODS_BYTES[i]; + int end = methodBytes.length - 1; + for (int j = 0; j <= end; j++) { + if (magic[j] != methodBytes[j]) { + break; + } + if (j == end) { + return true; + } + } } return false; } - public static enum HttpVersion { + public enum HttpVersion { HTTP1("http1"), HTTP2("http2"); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodec.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodec.java index 96b18cf2ec3..df181003799 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodec.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodec.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.Charset; import com.google.protobuf.Message; @@ -31,8 +32,6 @@ public class GrpcCompositeCodec implements HttpMessageCodec { - private static final MediaType MEDIA_TYPE = new MediaType("application", "grpc"); - private final ProtobufHttpMessageCodec protobufHttpMessageCodec; private final WrapperHttpMessageCodec wrapperHttpMessageCodec; @@ -52,7 +51,7 @@ public void setDecodeTypes(Class[] decodeTypes) { } @Override - public void encode(OutputStream outputStream, Object data) throws EncodeException { + public void encode(OutputStream outputStream, Object data, Charset charset) throws EncodeException { // protobuf // TODO int compressed = Identity.MESSAGE_ENCODING.equals(requestMetadata.compressor.getMessageEncoding()) ? 0 : // 1; @@ -71,19 +70,19 @@ public void encode(OutputStream outputStream, Object data) throws EncodeExceptio } @Override - public Object decode(InputStream inputStream, Class targetType) throws DecodeException { + public Object decode(InputStream inputStream, Class targetType, Charset charset) throws DecodeException { if (isProtoClass(targetType)) { - return protobufHttpMessageCodec.decode(inputStream, targetType); + return protobufHttpMessageCodec.decode(inputStream, targetType, charset); } - return wrapperHttpMessageCodec.decode(inputStream, targetType); + return wrapperHttpMessageCodec.decode(inputStream, targetType, charset); } @Override - public Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { + public Object[] decode(InputStream inputStream, Class[] targetTypes, Charset charset) throws DecodeException { if (targetTypes.length > 1) { - return wrapperHttpMessageCodec.decode(inputStream, targetTypes); + return wrapperHttpMessageCodec.decode(inputStream, targetTypes, charset); } - return HttpMessageCodec.super.decode(inputStream, targetTypes); + return HttpMessageCodec.super.decode(inputStream, targetTypes, charset); } private static void writeLength(OutputStream outputStream, int length) { @@ -99,7 +98,7 @@ private static void writeLength(OutputStream outputStream, int length) { @Override public MediaType mediaType() { - return MEDIA_TYPE; + return MediaType.APPLICATION_GRPC; } private static boolean isProtobuf(Object data) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodecFactory.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodecFactory.java index 29a7de9dfd5..553c661cc0b 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodecFactory.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodecFactory.java @@ -28,11 +28,9 @@ @Activate public class GrpcCompositeCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { - private static final MediaType MEDIA_TYPE = new MediaType("application", "grpc"); - @Override public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { - final String serializeName = UrlUtils.serializationOrDefault(url); + String serializeName = UrlUtils.serializationOrDefault(url); WrapperHttpMessageCodec wrapperHttpMessageCodec = new WrapperHttpMessageCodec(url, frameworkModel); wrapperHttpMessageCodec.setSerializeType(serializeName); ProtobufHttpMessageCodec protobufHttpMessageCodec = new ProtobufHttpMessageCodec(); @@ -41,6 +39,6 @@ public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, Stri @Override public MediaType mediaType() { - return MEDIA_TYPE; + return MediaType.APPLICATION_GRPC; } } 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 f64f70cc7c1..9ae78813025 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 @@ -17,6 +17,8 @@ package org.apache.dubbo.rpc.protocol.tri.h12.grpc; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.constants.CommonConstants; +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.remoting.http12.HttpHeaders; @@ -27,13 +29,11 @@ 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.Invoker; import org.apache.dubbo.rpc.RpcInvocation; import org.apache.dubbo.rpc.TriRpcStatus; import org.apache.dubbo.rpc.model.FrameworkModel; -import org.apache.dubbo.rpc.model.MethodDescriptor; -import org.apache.dubbo.rpc.model.ServiceDescriptor; -import org.apache.dubbo.rpc.protocol.tri.TripleCustomerProtocolWapper; +import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils; +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; import org.apache.dubbo.rpc.protocol.tri.h12.HttpMessageListener; @@ -43,10 +43,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_PARSE; @@ -58,10 +54,6 @@ public class GrpcHttp2ServerTransportListener extends GenericHttp2ServerTranspor public GrpcHttp2ServerTransportListener(H2StreamChannel h2StreamChannel, URL url, FrameworkModel frameworkModel) { super(h2StreamChannel, url, frameworkModel); - initialize(); - } - - private void initialize() { getServerChannelObserver().setTrailersCustomizer(this::grpcTrailersCustomize); } @@ -78,44 +70,14 @@ private static String httpStatusToGrpcStatus(Throwable throwable) { return String.valueOf(TriRpcStatus.INTERNAL.code.code); } - @Override - protected RpcInvocation buildRpcInvocation( - Invoker invoker, ServiceDescriptor serviceDescriptor, MethodDescriptor methodDescriptor) { - RpcInvocation rpcInvocation = super.buildRpcInvocation(invoker, serviceDescriptor, methodDescriptor); - HttpHeaders headers = getHttpMetadata().headers(); - String timeoutString = headers.getFirst(GrpcHeaderNames.GRPC_TIMEOUT.getName()); - try { - if (Objects.nonNull(timeoutString)) { - Long timeout = GrpcUtils.parseTimeoutToMills(timeoutString); - rpcInvocation.put("timeout", timeout); - } - } catch (Throwable t) { - LOGGER.warn( - PROTOCOL_FAILED_PARSE, - "", - "", - String.format( - "Failed to parse request timeout set from:%s, service=%s " + "method=%s", - timeoutString, - serviceDescriptor.getInterfaceName(), - getMethodDescriptor().getMethodName())); - } - return rpcInvocation; - } - @Override protected StreamingDecoder newStreamingDecoder() { return new GrpcStreamingDecoder(); } @Override - protected HttpMessageListener newHttpMessageListener() { - Http2Header httpMetadata = getHttpMetadata(); - boolean hasStub = getPathResolver().hasNativeStub(httpMetadata.path()); - if (hasStub) { - return GrpcHttp2ServerTransportListener.super.newHttpMessageListener(); - } - return new LazyFindMethodListener(); + protected HttpMessageListener buildHttpMessageListener() { + return getContext().isHasStub() ? super.buildHttpMessageListener() : new LazyFindMethodListener(); } @Override @@ -138,6 +100,28 @@ private void processGrpcHeaders(Http2Header metadata) { } } + @Override + protected RpcInvocation onBuildRpcInvocationCompletion(RpcInvocation invocation) { + String timeoutString = getHttpMetadata().headers().getFirst(GrpcHeaderNames.GRPC_TIMEOUT.getName()); + try { + if (null != timeoutString) { + Long timeout = GrpcUtils.parseTimeoutToMills(timeoutString); + invocation.put(CommonConstants.TIMEOUT_KEY, timeout); + } + } catch (Throwable t) { + LOGGER.warn( + PROTOCOL_FAILED_PARSE, + "", + "", + String.format( + "Failed to parse request timeout set from:%s, service=%s " + "method=%s", + timeoutString, + getContext().getServiceDescriptor().getInterfaceName(), + getContext().getMethodName())); + } + return invocation; + } + @Override protected GrpcStreamingDecoder getStreamingDecoder() { return (GrpcStreamingDecoder) super.getStreamingDecoder(); @@ -148,9 +132,9 @@ private class LazyFindMethodListener implements HttpMessageListener { private final StreamingDecoder streamingDecoder; private LazyFindMethodListener() { - this.streamingDecoder = new GrpcStreamingDecoder(); - this.streamingDecoder.setFragmentListener(new DetermineMethodDescriptorListener()); - this.streamingDecoder.request(Integer.MAX_VALUE); + streamingDecoder = new GrpcStreamingDecoder(); + streamingDecoder.setFragmentListener(new DetermineMethodDescriptorListener()); + streamingDecoder.request(Integer.MAX_VALUE); } @Override @@ -172,66 +156,31 @@ public void onClose() { @Override public void onFragmentMessage(InputStream dataHeader, InputStream rawMessage) { try { - ByteArrayOutputStream merge = + ByteArrayOutputStream merged = new ByteArrayOutputStream(dataHeader.available() + rawMessage.available()); - transferToOutputStream(merge, dataHeader); - ByteArrayOutputStream bos = new ByteArrayOutputStream(rawMessage.available()); - transferToOutputStream(bos, rawMessage); - byte[] data = bos.toByteArray(); - MethodDescriptor methodDescriptor = getMethodDescriptor(); - if (methodDescriptor == null) { - Http2Header httpMetadata = getHttpMetadata(); - String path = httpMetadata.path(); - String[] parts = path.split("/"); - String originalMethodName = parts[2]; - methodDescriptor = findReflectionMethodDescriptor(getServiceDescriptor(), originalMethodName); - if (methodDescriptor == null) { - List methodDescriptors = - getServiceDescriptor().getMethods(originalMethodName); - final TripleCustomerProtocolWapper.TripleRequestWrapper request; - request = TripleCustomerProtocolWapper.TripleRequestWrapper.parseFrom(data); - final String[] paramTypes = request.getArgTypes() - .toArray(new String[request.getArgs().size()]); - // wrapper mode the method can overload so maybe list - for (MethodDescriptor descriptor : methodDescriptors) { - // params type is array - if (Arrays.equals(descriptor.getCompatibleParamSignatures(), paramTypes)) { - methodDescriptor = descriptor; - break; - } - } - if (methodDescriptor == null) { - throw new UnimplementedException("method:" + originalMethodName); - } - } - setMethodDescriptor(methodDescriptor); - setMethodMetadata(MethodMetadata.fromMethodDescriptor(methodDescriptor)); - setRpcInvocation(buildRpcInvocation(getInvoker(), getServiceDescriptor(), methodDescriptor)); + StreamUtils.copy(dataHeader, merged); + byte[] data = StreamUtils.readBytes(rawMessage); + + RpcInvocationBuildContext context = getContext(); + if (null == context.getMethodDescriptor()) { + context.setMethodDescriptor(DescriptorUtils.findTripleMethodDescriptor( + context.getServiceDescriptor(), context.getMethodName(), data)); + + setHttpMessageListener(GrpcHttp2ServerTransportListener.super.buildHttpMessageListener()); + // replace decoder - HttpMessageListener httpMessageListener = - GrpcHttp2ServerTransportListener.super.newHttpMessageListener(); - GrpcCompositeCodec grpcCompositeCodec = (GrpcCompositeCodec) getHttpMessageDecoder(); - grpcCompositeCodec.setDecodeTypes(getMethodMetadata().getActualRequestTypes()); - grpcCompositeCodec.setEncodeTypes( - new Class[] {getMethodMetadata().getActualResponseType()}); - GrpcHttp2ServerTransportListener.super - .getServerChannelObserver() - .setResponseEncoder(grpcCompositeCodec); - setHttpMessageListener(httpMessageListener); + GrpcCompositeCodec grpcCompositeCodec = (GrpcCompositeCodec) context.getHttpMessageDecoder(); + MethodMetadata methodMetadata = context.getMethodMetadata(); + grpcCompositeCodec.setDecodeTypes(methodMetadata.getActualRequestTypes()); + grpcCompositeCodec.setEncodeTypes(new Class[] {methodMetadata.getActualResponseType()}); + getServerChannelObserver().setResponseEncoder(grpcCompositeCodec); } - transferToOutputStream(merge, new ByteArrayInputStream(data)); - getHttpMessageListener().onMessage(new ByteArrayInputStream(merge.toByteArray())); + + merged.write(data); + getHttpMessageListener().onMessage(new ByteArrayInputStream(merged.toByteArray())); } catch (IOException e) { throw new DecodeException(e); } } - - private void transferToOutputStream(OutputStream out, InputStream inputStream) throws IOException { - byte[] bytes = new byte[1024]; - int len; - while ((len = inputStream.read(bytes)) != -1) { - out.write(bytes, 0, len); - } - } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListenerFactory.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListenerFactory.java index cdcd42489e2..0f7338ef002 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListenerFactory.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListenerFactory.java @@ -33,6 +33,6 @@ public Http2TransportListener newInstance(H2StreamChannel streamChannel, URL url @Override public boolean supportContentType(String contentType) { - return contentType.startsWith(CONTENT_TYPE); + return contentType != null && contentType.startsWith(CONTENT_TYPE); } } 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 new file mode 100644 index 00000000000..52894e0df8b --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcRequestHandlerMapping.java @@ -0,0 +1,54 @@ +/* + * 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.grpc; + +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.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.TripleConstant; +import org.apache.dubbo.rpc.protocol.tri.h12.HttpRequestHandlerMapping; +import org.apache.dubbo.rpc.protocol.tri.route.RequestHandler; + +@Activate(order = -3000) +public final class GrpcRequestHandlerMapping extends HttpRequestHandlerMapping { + + public static final GrpcCompositeCodecFactory CODEC_FACTORY = new GrpcCompositeCodecFactory(); + + public GrpcRequestHandlerMapping(FrameworkModel frameworkModel) { + super(frameworkModel); + } + + @Override + protected boolean supportContentType(String contentType) { + return contentType != null && contentType.startsWith(MediaType.APPLICATION_GRPC.getName()); + } + + @Override + protected void determineHttpMessageCodec(RequestHandler handler, URL url, HttpRequest request) { + HttpMessageCodec codec = CODEC_FACTORY.createCodec(url, getFrameworkModel(), request.contentType()); + handler.setHttpMessageDecoder(codec); + handler.setHttpMessageEncoder(codec); + } + + @Override + public String getType() { + return TripleConstant.TRIPLE_HANDLER_TYPE_GRPC; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/ProtobufHttpMessageCodec.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/ProtobufHttpMessageCodec.java index 011fd6b8a93..81de958115a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/ProtobufHttpMessageCodec.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/ProtobufHttpMessageCodec.java @@ -25,13 +25,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.Charset; public class ProtobufHttpMessageCodec implements HttpMessageCodec { private static final MediaType MEDIA_TYPE = new MediaType("application", "x-protobuf"); @Override - public void encode(OutputStream outputStream, Object data) throws EncodeException { + public void encode(OutputStream outputStream, Object data, Charset charset) throws EncodeException { try { SingleProtobufUtils.serialize(data, outputStream); } catch (IOException e) { @@ -40,7 +41,7 @@ public void encode(OutputStream outputStream, Object data) throws EncodeExceptio } @Override - public Object decode(InputStream inputStream, Class targetType) throws DecodeException { + public Object decode(InputStream inputStream, Class targetType, Charset charset) throws DecodeException { try { return SingleProtobufUtils.deserialize(inputStream, targetType); } catch (IOException e) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/WrapperHttpMessageCodec.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/WrapperHttpMessageCodec.java index 907f01cd30f..9082b442b53 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/WrapperHttpMessageCodec.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/WrapperHttpMessageCodec.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.Charset; public class WrapperHttpMessageCodec implements HttpMessageCodec { @@ -69,7 +70,7 @@ public void setDecodeTypes(Class[] decodeTypes) { } @Override - public void encode(OutputStream outputStream, Object data) throws EncodeException { + public void encode(OutputStream outputStream, Object data, Charset charset) throws EncodeException { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); serialization.serialize(url, serializeType, encodeTypes[0], data, bos); @@ -87,13 +88,13 @@ public void encode(OutputStream outputStream, Object data) throws EncodeExceptio } @Override - public void encode(OutputStream outputStream, Object[] data) throws EncodeException { + public void encode(OutputStream outputStream, Object[] data, Charset charset) throws EncodeException { // TODO } @Override - public Object decode(InputStream inputStream, Class targetType) throws DecodeException { - Object[] decode = this.decode(inputStream, new Class[] {targetType}); + public Object decode(InputStream inputStream, Class targetType, Charset charset) throws DecodeException { + Object[] decode = this.decode(inputStream, new Class[] {targetType}, charset); if (decode == null || decode.length == 0) { return null; } @@ -101,7 +102,7 @@ public Object decode(InputStream inputStream, Class targetType) throws Decode } @Override - public Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { + public Object[] decode(InputStream inputStream, Class[] targetTypes, Charset charset) throws DecodeException { try { int len; byte[] data = new byte[4096]; 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 389e48bac3e..23da009f29d 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 @@ -18,6 +18,8 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.stream.StreamObserver; +import org.apache.dubbo.common.threadpool.manager.ExecutorRepository; +import org.apache.dubbo.common.threadpool.serial.SerializingExecutor; import org.apache.dubbo.remoting.http12.HttpChannel; import org.apache.dubbo.remoting.http12.HttpHeaderNames; import org.apache.dubbo.remoting.http12.HttpInputMessage; @@ -26,14 +28,14 @@ import org.apache.dubbo.remoting.http12.h1.Http1ServerStreamChannelObserver; import org.apache.dubbo.remoting.http12.h1.Http1ServerTransportListener; import org.apache.dubbo.remoting.http12.message.DefaultListeningDecoder; -import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; -import org.apache.dubbo.remoting.http12.message.ListeningDecoder; import org.apache.dubbo.remoting.http12.message.MediaType; -import org.apache.dubbo.remoting.http12.message.MethodMetadata; +import org.apache.dubbo.remoting.http12.message.codec.JsonCodec; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcInvocation; +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.RpcInvocationBuildContext; import org.apache.dubbo.rpc.protocol.tri.h12.AbstractServerTransportListener; import org.apache.dubbo.rpc.protocol.tri.h12.DefaultHttpMessageListener; import org.apache.dubbo.rpc.protocol.tri.h12.HttpMessageListener; @@ -41,66 +43,68 @@ import org.apache.dubbo.rpc.protocol.tri.h12.ServerStreamServerCallListener; import org.apache.dubbo.rpc.protocol.tri.h12.UnaryServerCallListener; +import java.util.concurrent.Executor; + public class DefaultHttp11ServerTransportListener extends AbstractServerTransportListener implements Http1ServerTransportListener { + private final ExecutorSupport executorSupport; private final HttpChannel httpChannel; - - private final URL url; + private Http1ServerChannelObserver serverChannelObserver; public DefaultHttp11ServerTransportListener(HttpChannel httpChannel, URL url, FrameworkModel frameworkModel) { super(frameworkModel, url, httpChannel); - this.url = url; + executorSupport = ExecutorRepository.getInstance(url.getOrDefaultApplicationModel()) + .getExecutorSupport(url); this.httpChannel = httpChannel; + serverChannelObserver = new Http1ServerChannelObserver(httpChannel); + serverChannelObserver.setResponseEncoder(JsonCodec.INSTANCE); + } + + @Override + protected Executor initializeExecutor(RequestMetadata metadata) { + return new SerializingExecutor(executorSupport.getExecutor(metadata)); + } + + @Override + protected HttpMessageListener buildHttpMessageListener() { + RpcInvocationBuildContext context = getContext(); + RpcInvocation rpcInvocation = buildRpcInvocation(context); + + ServerCallListener serverCallListener = + startListener(rpcInvocation, context.getMethodDescriptor(), context.getInvoker()); + + DefaultListeningDecoder listeningDecoder = new DefaultListeningDecoder( + context.getHttpMessageDecoder(), context.getMethodMetadata().getActualRequestTypes()); + listeningDecoder.setListener(serverCallListener::onMessage); + return new DefaultHttpMessageListener(listeningDecoder); } private ServerCallListener startListener( RpcInvocation invocation, MethodDescriptor methodDescriptor, Invoker invoker) { switch (methodDescriptor.getRpcType()) { case UNARY: - Http1ServerChannelObserver http1ChannelObserver = new Http1ServerChannelObserver(httpChannel); - http1ChannelObserver.setResponseEncoder(getCodecUtils() - .determineHttpMessageEncoder( - getFrameworkModel(), getHttpMetadata().headers(), getUrl())); - return new AutoCompleteUnaryServerCallListener(invocation, invoker, http1ChannelObserver); + return new AutoCompleteUnaryServerCallListener(invocation, invoker, serverChannelObserver); case SERVER_STREAM: - Http1ServerChannelObserver serverStreamChannelObserver = - new Http1ServerStreamChannelObserver(httpChannel); - serverStreamChannelObserver.setResponseEncoder(getCodecUtils() - .determineHttpMessageEncoder( - getFrameworkModel(), getHttpMetadata().headers(), getUrl())); - serverStreamChannelObserver.setHeadersCustomizer((headers) -> headers.set( - HttpHeaderNames.CONTENT_TYPE.getName(), MediaType.TEXT_EVENT_STREAM_VALUE.getName())); - return new AutoCompleteServerStreamServerCallListener(invocation, invoker, serverStreamChannelObserver); + serverChannelObserver = new Http1ServerStreamChannelObserver(httpChannel); + serverChannelObserver.setHeadersCustomizer((headers) -> + headers.set(HttpHeaderNames.CONTENT_TYPE.getName(), MediaType.TEXT_EVENT_STREAM.getName())); + return new AutoCompleteServerStreamServerCallListener(invocation, invoker, serverChannelObserver); default: throw new UnsupportedOperationException("HTTP1.x only support unary and server-stream"); } } @Override - protected HttpMessageListener newHttpMessageListener() { - RequestMetadata httpMetadata = getHttpMetadata(); - String path = httpMetadata.path(); - String[] parts = path.split("/"); - String originalMethodName = parts[2]; - boolean hasStub = getPathResolver().hasNativeStub(path); - MethodDescriptor methodDescriptor = findMethodDescriptor(getServiceDescriptor(), originalMethodName, hasStub); - MethodMetadata methodMetadata = MethodMetadata.fromMethodDescriptor(methodDescriptor); - RpcInvocation rpcInvocation = buildRpcInvocation(getInvoker(), getServiceDescriptor(), methodDescriptor); - setMethodDescriptor(methodDescriptor); - setMethodMetadata(methodMetadata); - setRpcInvocation(rpcInvocation); - ListeningDecoder listeningDecoder = - newListeningDecoder(getHttpMessageDecoder(), methodMetadata.getActualRequestTypes()); - return new DefaultHttpMessageListener(listeningDecoder); + protected void onMetadataCompletion(RequestMetadata metadata) { + serverChannelObserver.setResponseEncoder(getContext().getHttpMessageEncoder()); + super.onMetadataCompletion(metadata); } - private ListeningDecoder newListeningDecoder(HttpMessageDecoder decoder, Class[] actualRequestTypes) { - DefaultListeningDecoder defaultListeningDecoder = new DefaultListeningDecoder(decoder, actualRequestTypes); - ServerCallListener serverCallListener = startListener(getRpcInvocation(), getMethodDescriptor(), getInvoker()); - defaultListeningDecoder.setListener(serverCallListener::onMessage); - return defaultListeningDecoder; + @Override + protected void onError(Throwable throwable) { + serverChannelObserver.onError(throwable); } private static class AutoCompleteUnaryServerCallListener extends UnaryServerCallListener { @@ -113,7 +117,7 @@ public AutoCompleteUnaryServerCallListener( @Override public void onMessage(Object message) { super.onMessage(message); - super.onComplete(); + onComplete(); } } @@ -127,7 +131,7 @@ public AutoCompleteServerStreamServerCallListener( @Override public void onMessage(Object message) { super.onMessage(message); - super.onComplete(); + onComplete(); } } } 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 a859ae48ae2..326e09ede3e 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 @@ -19,11 +19,12 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.threadpool.manager.ExecutorRepository; import org.apache.dubbo.common.threadpool.serial.SerializingExecutor; -import org.apache.dubbo.remoting.http12.RequestMetadata; +import org.apache.dubbo.remoting.http12.HttpMethods; import org.apache.dubbo.remoting.http12.exception.HttpStatusException; import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; import org.apache.dubbo.remoting.http12.h2.Http2Header; import org.apache.dubbo.remoting.http12.h2.Http2InputMessage; +import org.apache.dubbo.remoting.http12.h2.Http2InputMessageFrame; import org.apache.dubbo.remoting.http12.h2.Http2ServerChannelObserver; import org.apache.dubbo.remoting.http12.h2.Http2TransportListener; import org.apache.dubbo.remoting.http12.message.DefaultListeningDecoder; @@ -31,6 +32,7 @@ import org.apache.dubbo.remoting.http12.message.MethodMetadata; import org.apache.dubbo.remoting.http12.message.NoOpStreamingDecoder; import org.apache.dubbo.remoting.http12.message.StreamingDecoder; +import org.apache.dubbo.remoting.http12.message.codec.JsonCodec; import org.apache.dubbo.rpc.CancellationContext; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcContext; @@ -39,6 +41,7 @@ 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.h12.AbstractServerTransportListener; import org.apache.dubbo.rpc.protocol.tri.h12.BiStreamServerCallListener; import org.apache.dubbo.rpc.protocol.tri.h12.HttpMessageListener; @@ -47,36 +50,66 @@ import org.apache.dubbo.rpc.protocol.tri.h12.UnaryServerCallListener; import org.apache.dubbo.rpc.protocol.tri.h12.grpc.StreamingHttpMessageListener; +import java.io.ByteArrayInputStream; import java.util.concurrent.Executor; public class GenericHttp2ServerTransportListener extends AbstractServerTransportListener implements Http2TransportListener { - private final Http2ServerChannelObserver serverChannelObserver; - - private final H2StreamChannel h2StreamChannel; + private static final Http2InputMessage EMPTY_MESSAGE = + new Http2InputMessageFrame(new ByteArrayInputStream(new byte[0]), true); private final ExecutorSupport executorSupport; - private final StreamingDecoder streamingDecoder; + private final Http2ServerChannelObserver serverChannelObserver; private ServerCallListener serverCallListener; public GenericHttp2ServerTransportListener( H2StreamChannel h2StreamChannel, URL url, FrameworkModel frameworkModel) { super(frameworkModel, url, h2StreamChannel); - this.h2StreamChannel = h2StreamChannel; - this.executorSupport = ExecutorRepository.getInstance(url.getOrDefaultApplicationModel()) + executorSupport = ExecutorRepository.getInstance(url.getOrDefaultApplicationModel()) .getExecutorSupport(url); - this.streamingDecoder = newStreamingDecoder(); - this.serverChannelObserver = new Http2ServerCallToObserverAdapter(frameworkModel, h2StreamChannel); - this.serverChannelObserver.setStreamingDecoder(streamingDecoder); + streamingDecoder = newStreamingDecoder(); + serverChannelObserver = new Http2ServerCallToObserverAdapter(frameworkModel, h2StreamChannel); + serverChannelObserver.setResponseEncoder(JsonCodec.INSTANCE); + serverChannelObserver.setStreamingDecoder(streamingDecoder); + } + + protected StreamingDecoder newStreamingDecoder() { + // default no op + return new NoOpStreamingDecoder(); } @Override protected Executor initializeExecutor(Http2Header metadata) { - Executor executor = executorSupport.getExecutor(metadata); - return new SerializingExecutor(executor); + return new SerializingExecutor(executorSupport.getExecutor(metadata)); + } + + protected void doOnMetadata(Http2Header metadata) { + if (metadata.isEndStream()) { + if (!HttpMethods.supportBody(metadata.method())) { + super.doOnMetadata(metadata); + doOnData(EMPTY_MESSAGE); + } + return; + } + super.doOnMetadata(metadata); + } + + @Override + protected HttpMessageListener buildHttpMessageListener() { + RpcInvocationBuildContext context = getContext(); + RpcInvocation rpcInvocation = buildRpcInvocation(context); + + serverCallListener = startListener(rpcInvocation, context.getMethodDescriptor(), context.getInvoker()); + + DefaultListeningDecoder listeningDecoder = new DefaultListeningDecoder( + context.getHttpMessageDecoder(), context.getMethodMetadata().getActualRequestTypes()); + listeningDecoder.setListener(new Http2StreamingDecodeListener(serverCallListener)); + streamingDecoder.setFragmentListener(new StreamingDecoder.DefaultFragmentListener(listeningDecoder)); + getServerChannelObserver().setStreamingDecoder(streamingDecoder); + return new StreamingHttpMessageListener(streamingDecoder); } private ServerCallListener startListener( @@ -86,14 +119,13 @@ private ServerCallListener startListener( responseObserver.setCancellationContext(cancellationContext); switch (methodDescriptor.getRpcType()) { case UNARY: - Http2Header httpMetadata = getHttpMetadata(); - boolean hasStub = getPathResolver().hasNativeStub(httpMetadata.path()); boolean applyCustomizeException = false; - if (!hasStub) { + if (!getContext().isHasStub()) { + MethodMetadata methodMetadata = getContext().getMethodMetadata(); applyCustomizeException = ReflectionPackableMethod.needWrap( methodDescriptor, - getMethodMetadata().getActualRequestTypes(), - getMethodMetadata().getActualResponseType()); + methodMetadata.getActualRequestTypes(), + methodMetadata.getActualResponseType()); } UnaryServerCallListener unaryServerCallListener = startUnary(invocation, invoker, responseObserver); unaryServerCallListener.setApplyCustomizeException(applyCustomizeException); @@ -108,63 +140,25 @@ private ServerCallListener startListener( } } - public Http2ServerChannelObserver getServerChannelObserver() { - return serverChannelObserver; - } - - @Override - public void cancelByRemote(long errorCode) { - this.serverChannelObserver.cancel(new HttpStatusException((int) errorCode)); - this.serverCallListener.onCancel(errorCode); - } - - protected StreamingDecoder newStreamingDecoder() { - // default no op - return new NoOpStreamingDecoder(); + private UnaryServerCallListener startUnary( + RpcInvocation invocation, Invoker invoker, Http2ServerChannelObserver responseObserver) { + return new UnaryServerCallListener(invocation, invoker, responseObserver); } - protected void doOnMetadata(Http2Header metadata) { - if (metadata.isEndStream()) { - return; - } - super.doOnMetadata(metadata); + private ServerStreamServerCallListener startServerStreaming( + RpcInvocation invocation, Invoker invoker, Http2ServerChannelObserver responseObserver) { + return new ServerStreamServerCallListener(invocation, invoker, responseObserver); } - @Override - protected HttpMessageListener newHttpMessageListener() { - RequestMetadata httpMetadata = getHttpMetadata(); - String path = httpMetadata.path(); - String[] parts = path.split("/"); - String originalMethodName = parts[2]; - MethodDescriptor methodDescriptor = getMethodDescriptor(); - if (methodDescriptor == null) { - methodDescriptor = findMethodDescriptor(getServiceDescriptor(), originalMethodName, isHasStub()); - setMethodDescriptor(methodDescriptor); - } - MethodMetadata methodMetadata = getMethodMetadata(); - if (methodMetadata == null) { - methodMetadata = MethodMetadata.fromMethodDescriptor(getMethodDescriptor()); - setMethodMetadata(methodMetadata); - } - RpcInvocation rpcInvocation = getRpcInvocation(); - if (rpcInvocation == null) { - setRpcInvocation(buildRpcInvocation(getInvoker(), getServiceDescriptor(), methodDescriptor)); - } - initializeServerCallListener(); - DefaultListeningDecoder defaultListeningDecoder = new DefaultListeningDecoder( - getHttpMessageDecoder(), getMethodMetadata().getActualRequestTypes()); - defaultListeningDecoder.setListener(new Http2StreamingDecodeListener(serverCallListener)); - streamingDecoder.setFragmentListener(new StreamingDecoder.DefaultFragmentListener(defaultListeningDecoder)); - getServerChannelObserver().setStreamingDecoder(streamingDecoder); - return new StreamingHttpMessageListener(streamingDecoder); + private BiStreamServerCallListener startBiStreaming( + RpcInvocation invocation, Invoker invoker, Http2ServerChannelObserver responseObserver) { + return new BiStreamServerCallListener(invocation, invoker, responseObserver); } @Override protected void onMetadataCompletion(Http2Header metadata) { - super.onMetadataCompletion(metadata); - this.serverChannelObserver.setResponseEncoder( - getCodecUtils().determineHttpMessageEncoder(getFrameworkModel(), metadata.headers(), getUrl())); - this.serverChannelObserver.request(1); + serverChannelObserver.setResponseEncoder(getContext().getHttpMessageEncoder()); + serverChannelObserver.request(1); } @Override @@ -179,8 +173,18 @@ protected void onError(Throwable throwable) { serverChannelObserver.onError(throwable); } + @Override + public void cancelByRemote(long errorCode) { + serverChannelObserver.cancel(new HttpStatusException((int) errorCode)); + serverCallListener.onCancel(errorCode); + } + protected StreamingDecoder getStreamingDecoder() { - return this.streamingDecoder; + return streamingDecoder; + } + + protected final Http2ServerChannelObserver getServerChannelObserver() { + return serverChannelObserver; } private static class Http2StreamingDecodeListener implements ListeningDecoder.Listener { @@ -193,37 +197,12 @@ private Http2StreamingDecodeListener(ServerCallListener serverCallListener) { @Override public void onMessage(Object message) { - this.serverCallListener.onMessage(message); + serverCallListener.onMessage(message); } @Override public void onClose() { - this.serverCallListener.onComplete(); - } - } - - private void initializeServerCallListener() { - if (serverCallListener == null) { - this.serverCallListener = startListener(getRpcInvocation(), getMethodDescriptor(), getInvoker()); + serverCallListener.onComplete(); } } - - private UnaryServerCallListener startUnary( - RpcInvocation invocation, Invoker invoker, Http2ServerChannelObserver responseObserver) { - return new UnaryServerCallListener(invocation, invoker, responseObserver); - } - - private ServerStreamServerCallListener startServerStreaming( - RpcInvocation invocation, Invoker invoker, Http2ServerChannelObserver responseObserver) { - return new ServerStreamServerCallListener(invocation, invoker, responseObserver); - } - - private BiStreamServerCallListener startBiStreaming( - RpcInvocation invocation, Invoker invoker, Http2ServerChannelObserver responseObserver) { - return new BiStreamServerCallListener(invocation, invoker, responseObserver); - } - - protected ServerCallListener getServerCallListener() { - return serverCallListener; - } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ServerStreamObserver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ServerStreamObserver.java index 7a0f96c282d..58fa91996e4 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ServerStreamObserver.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ServerStreamObserver.java @@ -63,7 +63,7 @@ public Map getResponseAttachments() { protected HttpMetadata encodeTrailers(Throwable throwable) { HttpMetadata httpMetadata = super.encodeTrailers(throwable); HttpHeaders headers = httpMetadata.headers(); - StreamUtils.convertAttachment(headers, attachments, TripleProtocol.CONVERT_NO_LOWER_HEADER); + StreamUtils.putHeaders(headers, attachments, TripleProtocol.CONVERT_NO_LOWER_HEADER); return httpMetadata; } } 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 new file mode 100644 index 00000000000..e1493f428d5 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/Messages.java @@ -0,0 +1,64 @@ +/* + * 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.common.utils.ArrayUtils; + +import java.text.MessageFormat; + +public enum Messages { + MISSING_CLOSE_CAPTURE("Expected close capture character after variable name '}' for path ''{0}'' at index {1}"), + MISSING_OPEN_CAPTURE("Missing preceding open capture character before variable name '{' for path ''{0}''"), + ILLEGAL_NESTED_CAPTURE("Not allowed to nest variable captures for path ''{0}'' at index {1}"), + ILLEGAL_DOUBLE_CAPTURE("Not allowed to capture ''{0}'' twice in the same pattern"), + DUPLICATE_CAPTURE_VARIABLE("Duplicate capture variable ''{0}''"), + MISSING_REGEX_CONSTRAINT("Missing regex constraint on capture for path ''{0}'' at index {1}"), + REGEX_PATTERN_INVALID("Invalid regex pattern ''{0}'' for path ''{0}''"), + 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}}"), + 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."), + ARGUMENT_VALUE_MISSING("Missing argument ''{0}'' for method parameter of type [{1}]", 412), + ARGUMENT_CONVERT_ERROR("Convert argument ''{0}'' value [{1}] from type [{2}] to type [{3}] error", 412), + ARGUMENT_COULD_NOT_RESOLVED("Could not resolve ''{0}'', no suitable resolver", 400), + ARGUMENT_BIND_ERROR("Bind argument ''{0}'' of type [{1}] error", 400), + INTERNAL_ERROR("Rest internal error"); + + private final String message; + private final int statusCode; + + Messages(String message) { + this.message = message; + statusCode = 500; + } + + Messages(String message, int statusCode) { + this.message = message; + this.statusCode = statusCode; + } + + public int statusCode() { + return statusCode; + } + + public String format(Object... args) { + return ArrayUtils.isEmpty(args) ? message : MessageFormat.format(message, args); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/PathParserException.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/PathParserException.java new file mode 100644 index 00000000000..02b8b484504 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/PathParserException.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; + +public final class PathParserException extends RestException { + + private static final long serialVersionUID = 1L; + + public PathParserException(Messages message, Object... arguments) { + super(message, arguments); + } + + public PathParserException(Messages message, Throwable cause, Object... arguments) { + super(cause, message, arguments); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestBadRequestException.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestBadRequestException.java new file mode 100644 index 00000000000..0b743372347 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestBadRequestException.java @@ -0,0 +1,49 @@ +/* + * 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.remoting.http12.HttpStatus; + +public class RestBadRequestException extends RestException { + + private static final long serialVersionUID = 1L; + + public RestBadRequestException(Messages message, Object... arguments) { + super(message, arguments); + } + + public RestBadRequestException(Throwable cause, Messages message, Object... arguments) { + super(cause, message, arguments); + } + + public RestBadRequestException(String message, Throwable cause) { + super(message, cause); + } + + public RestBadRequestException(String message) { + super(message); + } + + public RestBadRequestException(Throwable cause) { + super(cause); + } + + @Override + public int getStatusCode() { + return HttpStatus.BAD_REQUEST.getCode(); + } +} 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 new file mode 100644 index 00000000000..7a431bd02a3 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java @@ -0,0 +1,61 @@ +/* + * 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.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta; + +public final class RestConstants { + + public static final String REST = "rest"; + + public static final String REST_FILTER_KEY = "rest.filter"; + public static final String EXTENSION_KEY = "extension"; + public static final String EXTENSIONS_ATTRIBUTE_KEY = "restExtensionsAttributeKey"; + + public static final int DIALECT_SPRING_MVC = 1; + public static final int DIALECT_JAXRS = 2; + + public static final String HEADER_SERVICE_VERSION = "rest-service-version"; + public static final String HEADER_SERVICE_GROUP = "rest-service-group"; + + public static final String SLASH = "/"; + + /* 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 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"; + public static final String URI_TEMPLATE_VARIABLES_ATTRIBUTE = + "org.springframework.web.servlet.HandlerMapping.uriTemplateVariables"; + public static final String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = + "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 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"; + public static final String FORMAT_PARAMETER_NAME_KEY = CONFIG_PREFIX + "format-parameter-name"; + + private RestConstants() {} +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestException.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestException.java new file mode 100644 index 00000000000..f63a358ef7a --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestException.java @@ -0,0 +1,89 @@ +/* + * 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.remoting.http12.exception.HttpStatusException; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; + +public class RestException extends HttpStatusException { + + private static final long serialVersionUID = 1L; + + private final Messages message; + + public RestException(Messages message, Object... arguments) { + super(message.statusCode(), message.format(arguments)); + this.message = message; + } + + public RestException(Throwable cause, Messages message, Object... arguments) { + super(message.statusCode(), message.format(arguments), unwrap(cause)); + this.message = message; + } + + public RestException(String message, Throwable cause) { + super(500, message, unwrap(cause)); + this.message = Messages.INTERNAL_ERROR; + } + + public RestException(int statusCode, String message) { + super(statusCode, message); + this.message = Messages.INTERNAL_ERROR; + } + + public RestException(String message) { + super(500, message); + this.message = Messages.INTERNAL_ERROR; + } + + public RestException(Throwable cause) { + super(500, unwrap(cause)); + message = Messages.INTERNAL_ERROR; + } + + public String getErrorCode() { + return message.name(); + } + + public static RuntimeException wrap(Throwable t) { + t = unwrap(t); + return t instanceof RuntimeException ? (RuntimeException) t : new RestException(t); + } + + public static Throwable unwrap(Throwable t) { + while (true) { + if (t instanceof UndeclaredThrowableException) { + t = ((UndeclaredThrowableException) t).getUndeclaredThrowable(); + } else if (t instanceof InvocationTargetException) { + t = ((InvocationTargetException) t).getTargetException(); + } else if (t instanceof CompletionException || t instanceof ExecutionException) { + Throwable cause = t.getCause(); + if (cause == t) { + break; + } + t = cause; + } else { + break; + } + } + return t; + } +} 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 new file mode 100644 index 00000000000..23462b7b6b6 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestHttpMessageCodec.java @@ -0,0 +1,128 @@ +/* + * 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.common.io.StreamUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.exception.EncodeException; +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.rpc.protocol.tri.rest.argument.ArgumentResolver; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +public final class RestHttpMessageCodec implements HttpMessageDecoder, HttpMessageEncoder { + + private static final Object[] EMPTY_ARGS = new Object[0]; + + private final HttpRequest request; + private final HttpResponse response; + private final ParameterMeta[] parameters; + private final ArgumentResolver argumentResolver; + private final TypeConverter typeConverter; + private final HttpMessageEncoder messageEncoder; + private final Charset charset; + + public RestHttpMessageCodec( + HttpRequest request, + HttpResponse response, + ParameterMeta[] parameters, + ArgumentResolver argumentResolver, + TypeConverter typeConverter, + HttpMessageEncoder messageEncoder) { + this.request = request; + this.response = response; + this.parameters = parameters; + this.argumentResolver = argumentResolver; + this.typeConverter = typeConverter; + this.messageEncoder = messageEncoder; + charset = request.charsetOrDefault(); + } + + @Override + public Object decode(InputStream inputStream, Class targetType, Charset charset) throws DecodeException { + return decode(inputStream, new Class[] {targetType}, charset); + } + + @Override + public Object[] decode(InputStream inputStream, Class[] targetTypes, Charset charset) throws DecodeException { + request.setInputStream(inputStream); + ParameterMeta[] parameters = this.parameters; + int len = parameters.length; + if (len == 0) { + return EMPTY_ARGS; + } + Object[] args = new Object[len]; + for (int i = 0; i < len; i++) { + args[i] = argumentResolver.resolve(parameters[i], request, response); + } + return args; + } + + @Override + public void encode(OutputStream os, Object data, Charset charset) throws EncodeException { + encode(os, data); + } + + @Override + public void encode(OutputStream os, Object data) throws EncodeException { + if (data != null) { + Class type = data.getClass(); + try { + if (type == byte[].class) { + os.write((byte[]) data); + return; + } + if (type == ByteArrayOutputStream.class) { + ((ByteArrayOutputStream) data).writeTo(os); + return; + } + if (data instanceof InputStream) { + try (InputStream is = (InputStream) data) { + StreamUtils.copy(is, os); + } + return; + } + if (messageEncoder.mediaType().isPureText() && type != String.class) { + data = typeConverter.convert(data, String.class); + } + } catch (Exception e) { + throw new EncodeException(e); + } + } + messageEncoder.encode(os, data, charset); + } + + @Override + public MediaType mediaType() { + return messageEncoder.mediaType(); + } + + @Override + public String contentType() { + String contentType = response.contentType(); + return contentType == null ? messageEncoder.contentType() : contentType; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestInitializeException.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestInitializeException.java new file mode 100644 index 00000000000..e3cbabf8704 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestInitializeException.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; + +public final class RestInitializeException extends RestException { + + private static final long serialVersionUID = 1L; + + public RestInitializeException(Messages message, Object... arguments) { + super(message, arguments); + } + + public RestInitializeException(Throwable cause, Messages message, Object... arguments) { + super(cause, message, arguments); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestParameterException.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestParameterException.java new file mode 100644 index 00000000000..b340d795683 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestParameterException.java @@ -0,0 +1,49 @@ +/* + * 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.remoting.http12.HttpStatus; + +public class RestParameterException extends RestException { + + private static final long serialVersionUID = 1L; + + public RestParameterException(Messages message, Object... arguments) { + super(message, arguments); + } + + public RestParameterException(Throwable cause, Messages message, Object... arguments) { + super(cause, message, arguments); + } + + public RestParameterException(String message, Throwable cause) { + super(message, cause); + } + + public RestParameterException(String message) { + super(message); + } + + public RestParameterException(Throwable cause) { + super(cause); + } + + @Override + public int getStatusCode() { + return HttpStatus.PRECONDITION_FAILED.getCode(); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AbstractAnnotationBaseArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AbstractAnnotationBaseArgumentResolver.java new file mode 100644 index 00000000000..9b9143e0a70 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AbstractAnnotationBaseArgumentResolver.java @@ -0,0 +1,42 @@ +/* + * 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.argument; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +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 java.lang.annotation.Annotation; + +public abstract class AbstractAnnotationBaseArgumentResolver extends NamedValueArgumentResolverSupport + implements AnnotationBaseArgumentResolver { + + @Override + public Object resolve( + ParameterMeta parameter, + AnnotationMeta annotation, + HttpRequest request, + HttpResponse response) { + NamedValueMeta namedValue = + cache.computeIfAbsent(parameter, k -> updateNamedValueMeta(k, createNamedValueMeta(k, annotation))); + return resolve(namedValue, request, response); + } + + protected abstract NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta ann); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AbstractArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AbstractArgumentResolver.java new file mode 100644 index 00000000000..6675f1e5809 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AbstractArgumentResolver.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.argument; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.NamedValueMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +public abstract class AbstractArgumentResolver extends NamedValueArgumentResolverSupport implements ArgumentResolver { + + @Override + public Object resolve(ParameterMeta parameter, HttpRequest request, HttpResponse response) { + NamedValueMeta namedValue = + cache.computeIfAbsent(parameter, k -> updateNamedValueMeta(k, createNamedValueMeta(k))); + return resolve(namedValue, request, response); + } + + protected abstract NamedValueMeta createNamedValueMeta(ParameterMeta parameter); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AnnotationBaseArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AnnotationBaseArgumentResolver.java new file mode 100644 index 00000000000..30b7a12205d --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/AnnotationBaseArgumentResolver.java @@ -0,0 +1,40 @@ +/* + * 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.argument; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +import java.lang.annotation.Annotation; + +public interface AnnotationBaseArgumentResolver extends ArgumentResolver { + + Class accept(); + + Object resolve(ParameterMeta parameter, AnnotationMeta annotation, HttpRequest request, HttpResponse response); + + default boolean accept(ParameterMeta parameter) { + return true; + } + + @Override + default Object resolve(ParameterMeta parameter, HttpRequest request, HttpResponse response) { + throw new UnsupportedOperationException("Resolve without annotation is unsupported"); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/ArgumentConverter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/ArgumentConverter.java new file mode 100644 index 00000000000..2599d510735 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/ArgumentConverter.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.argument; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface ArgumentConverter { + + T convert(S value, ParameterMeta parameter); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/ArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/ArgumentResolver.java new file mode 100644 index 00000000000..a6cd3b909dd --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/ArgumentResolver.java @@ -0,0 +1,31 @@ +/* + * 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.argument; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface ArgumentResolver { + + boolean accept(ParameterMeta parameter); + + Object resolve(ParameterMeta parameter, HttpRequest request, HttpResponse response); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentConverter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentConverter.java new file mode 100644 index 00000000000..85c538aeffe --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentConverter.java @@ -0,0 +1,85 @@ +/* + * 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.argument; + +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.Pair; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public final class CompositeArgumentConverter implements ArgumentConverter { + + private final List converters; + private final Map, List> cache = CollectionUtils.newConcurrentHashMap(); + + public CompositeArgumentConverter(FrameworkModel frameworkModel) { + converters = frameworkModel.getActivateExtensions(ArgumentConverter.class); + } + + @Override + public Object convert(Object value, ParameterMeta parameter) { + Class type = parameter.getType(); + if (value == null) { + return TypeUtils.nullDefault(type); + } + + if (type.isInstance(value)) { + if (parameter.getGenericType() instanceof Class) { + return value; + } + return parameter.getToolKit().convert(value, parameter); + } + + List converters = getSuitableConverters(value.getClass(), type); + Object target; + for (int i = 0, size = converters.size(); i < size; i++) { + target = converters.get(i).convert(value, parameter); + if (target != null) { + return target; + } + } + + return parameter.getToolKit().convert(value, parameter); + } + + private List getSuitableConverters(Class sourceType, Class targetType) { + return cache.computeIfAbsent(Pair.of(sourceType, targetType), k -> { + List result = new ArrayList<>(); + for (ArgumentConverter converter : converters) { + Class supportSourceType = TypeUtils.getSuperGenericType(converter.getClass(), 0); + if (supportSourceType == null) { + continue; + } + Class supportTargetType = TypeUtils.getSuperGenericType(converter.getClass(), 1); + if (supportTargetType == null) { + continue; + } + if (supportSourceType.isAssignableFrom(sourceType) && targetType.isAssignableFrom(supportTargetType)) { + result.add(converter); + } + } + return result.isEmpty() ? Collections.emptyList() : result; + }); + } +} 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 new file mode 100644 index 00000000000..9a8690f6114 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentResolver.java @@ -0,0 +1,77 @@ +/* + * 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.argument; + +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.Messages; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public final class CompositeArgumentResolver implements ArgumentResolver { + + private final Map resolverMap = new HashMap<>(); + private final ArgumentResolver[] resolvers; + private final ArgumentConverter argumentConverter; + + public CompositeArgumentResolver(FrameworkModel frameworkModel) { + List extensions = frameworkModel.getActivateExtensions(ArgumentResolver.class); + List resolvers = new ArrayList<>(extensions.size()); + for (ArgumentResolver resolver : extensions) { + if (resolver instanceof AnnotationBaseArgumentResolver) { + AnnotationBaseArgumentResolver aar = (AnnotationBaseArgumentResolver) resolver; + resolverMap.put(aar.accept(), aar); + } else { + resolvers.add(resolver); + } + } + this.resolvers = resolvers.toArray(new ArgumentResolver[0]); + argumentConverter = new CompositeArgumentConverter(frameworkModel); + } + + @Override + public boolean accept(ParameterMeta parameter) { + return true; + } + + @Override + public Object resolve(ParameterMeta parameter, HttpRequest request, HttpResponse response) { + AnnotationMeta[] annotations = parameter.findAnnotations(); + for (AnnotationMeta annotation : annotations) { + AnnotationBaseArgumentResolver resolver = resolverMap.get(annotation.getAnnotationType()); + if (resolver != null) { + Object value = resolver.resolve(parameter, annotation, request, response); + return argumentConverter.convert(value, parameter); + } + } + for (ArgumentResolver resolver : resolvers) { + if (resolver.accept(parameter)) { + Object value = resolver.resolve(parameter, request, response); + return argumentConverter.convert(value, parameter); + } + } + + throw new IllegalStateException(Messages.ARGUMENT_COULD_NOT_RESOLVED.format(parameter.getDescription())); + } +} 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 new file mode 100644 index 00000000000..995334da7f4 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/GeneralTypeConverter.java @@ -0,0 +1,1269 @@ +/* + * 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.argument; + +import org.apache.dubbo.common.convert.ConverterUtil; +import org.apache.dubbo.common.io.StreamUtils; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.DateUtils; +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.rpc.model.FrameworkModel; +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.util.RequestUtils; +import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.StringReader; +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; +import java.lang.reflect.WildcardType; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAccessor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +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.Iterator; +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.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.Properties; +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.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.regex.Pattern; + +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; +import static org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils.nullDefault; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public class GeneralTypeConverter implements TypeConverter { + + private static final Logger LOGGER = LoggerFactory.getLogger(GeneralTypeConverter.class); + + private final ConverterUtil converterUtil; + + public GeneralTypeConverter() { + converterUtil = null; + } + + public GeneralTypeConverter(FrameworkModel frameworkModel) { + converterUtil = frameworkModel.getBeanFactory().getOrRegisterBean(ConverterUtil.class); + } + + @Override + public T convert(Object source, Class targetClass) { + try { + return targetClass == null ? (T) source : (T) doConvert(source, targetClass); + } catch (Exception e) { + throw RestException.wrap(e); + } + } + + @Override + public T convert(Object source, Type targetType) { + try { + return targetType == null ? (T) source : (T) doConvert(source, targetType); + } catch (Exception e) { + throw RestException.wrap(e); + } + } + + private Object doConvert(Object source, Class targetClass) throws Exception { + if (source == null) { + return nullDefault(targetClass); + } + + if (targetClass.isInstance(source)) { + return source; + } + + if (targetClass == Optional.class) { + return Optional.of(source); + } + + Class sourceClass = source.getClass(); + if (sourceClass == Optional.class) { + source = ((Optional) source).orElse(null); + if (source == null) { + return nullDefault(targetClass); + } + + if (targetClass.isInstance(source)) { + return source; + } + } + + if (source instanceof CharSequence) { + String str = source.toString(); + + if (targetClass == String.class) { + return str; + } + + if (str.isEmpty() || "null".equals(str) || "NULL".equals(str)) { + return emptyDefault(targetClass); + } + + switch (targetClass.getName()) { + case "java.lang.Double": + case "double": + return Double.valueOf(str); + case "java.lang.Float": + case "float": + return Float.valueOf(str); + case "java.lang.Long": + case "long": + return isHexNumber(str) ? Long.decode(str) : Long.valueOf(str); + case "java.lang.Integer": + case "int": + return isHexNumber(str) ? Integer.decode(str) : Integer.valueOf(str); + case "java.lang.Short": + case "short": + return isHexNumber(str) ? Short.decode(str) : Short.valueOf(str); + case "java.lang.Character": + case "char": + if (str.length() == 1) { + return str.charAt(0); + } + throw new RestParameterException("Can not convert String(" + str + ") to char, must only 1 char"); + case "java.lang.Byte": + case "byte": + return isHexNumber(str) ? Byte.decode(str) : Byte.valueOf(str); + case "java.lang.Boolean": + return toBoolean(str); + case "boolean": + return toBoolean(str) == Boolean.TRUE; + case "java.math.BigInteger": + return new BigInteger(str); + case "java.math.BigDecimal": + return new BigDecimal(str); + case "java.lang.Number": + return str.indexOf('.') == -1 ? doConvert(str, Long.class) : doConvert(str, Double.class); + case "java.util.Date": + return DateUtils.parse(str); + case "java.util.Calendar": + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(DateUtils.parse(str).getTime()); + return cal; + case "java.sql.Timestamp": + return new Timestamp(DateUtils.parse(str).getTime()); + case "java.time.Instant": + return DateUtils.parse(str).toInstant(); + case "java.time.ZonedDateTime": + return toZonedDateTime(str); + case "java.time.LocalDate": + return toZonedDateTime(str).toLocalDate(); + case "java.time.LocalTime": + return toZonedDateTime(str).toLocalTime(); + case "java.time.LocalDateTime": + return toZonedDateTime(str).toLocalDateTime(); + case "java.time.ZoneId": + return TimeZone.getTimeZone(str).toZoneId(); + case "java.util.TimeZone": + return TimeZone.getTimeZone(str); + case "java.io.File": + return new File(str); + case "java.nio.file.Path": + return Paths.get(str); + case "java.nio.charset.Charset": + return Charset.forName(str); + case "java.net.InetAddress": + return InetAddress.getByName(str); + case "java.net.URI": + return new URI(str); + case "java.net.URL": + return new URL(str); + case "java.util.UUID": + return UUID.fromString(str); + case "java.util.Locale": + String[] parts = StringUtils.tokenize(str, '-', '_'); + switch (parts.length) { + case 2: + return new Locale(parts[0], parts[1]); + case 3: + return new Locale(parts[0], parts[1], parts[2]); + default: + return new Locale(parts[0]); + } + case "java.util.Currency": + return Currency.getInstance(str); + case "java.util.regex.Pattern": + return Pattern.compile(str); + case "java.lang.Class": + return TypeUtils.loadClass(str); + case "[B": + return str.getBytes(StandardCharsets.UTF_8); + case "[C": + return str.toCharArray(); + case "java.util.OptionalInt": + return OptionalInt.of(isHexNumber(str) ? Integer.decode(str) : Integer.parseInt(str)); + case "java.util.OptionalLong": + return OptionalLong.of(isHexNumber(str) ? Long.decode(str) : Long.parseLong(str)); + case "java.util.OptionalDouble": + return OptionalDouble.of(Double.parseDouble(str)); + case "java.util.Properties": + Properties properties = new Properties(); + properties.load(new StringReader(str)); + return properties; + default: + } + + if (targetClass.isEnum()) { + try { + return Enum.valueOf((Class) targetClass, str); + } catch (Exception ignored) { + } + } + + Object target = jsonToObject(str, targetClass); + if (target != null) { + return target; + } + + if (targetClass.isArray()) { + List list = tokenizeToList(str); + int n = list.size(); + Class itemType = targetClass.getComponentType(); + if (itemType == String.class) { + return list.toArray(StringUtils.EMPTY_STRING_ARRAY); + } + Object arr = Array.newInstance(itemType, n); + for (int i = 0; i < n; i++) { + Array.set(arr, i, doConvert(list.get(i), itemType)); + } + return arr; + } else if (Collection.class.isAssignableFrom(targetClass)) { + target = convertCollection(tokenizeToList(str), targetClass); + if (target != null) { + return target; + } + } else if (Map.class.isAssignableFrom(targetClass)) { + target = convertMap(tokenizeToMap(str), targetClass); + if (target != null) { + return target; + } + } + } else if (source instanceof Number) { + Number num = (Number) source; + + switch (targetClass.getName()) { + case "java.lang.String": + return source.toString(); + case "java.lang.Double": + case "double": + return num.doubleValue(); + case "java.lang.Float": + case "float": + return num.floatValue(); + case "java.lang.Long": + case "long": + return num.longValue(); + case "java.lang.Integer": + case "int": + return num.intValue(); + case "java.lang.Short": + case "short": + return num.shortValue(); + case "java.lang.Character": + case "char": + return (char) num.intValue(); + case "java.lang.Byte": + case "byte": + return num.byteValue(); + case "java.lang.Boolean": + case "boolean": + return toBoolean(num); + case "java.math.BigInteger": + return BigInteger.valueOf(num.longValue()); + case "java.math.BigDecimal": + return BigDecimal.valueOf(num.doubleValue()); + case "java.util.Date": + return new Date(num.longValue()); + case "java.util.Calendar": + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(num.longValue()); + return cal; + case "java.sql.Timestamp": + return new Timestamp(num.longValue()); + case "java.time.Instant": + return Instant.ofEpochMilli(num.longValue()); + case "java.time.ZonedDateTime": + return toZonedDateTime(num); + case "java.time.LocalDate": + return toZonedDateTime(num).toLocalDate(); + case "java.time.LocalTime": + return toZonedDateTime(num).toLocalTime(); + case "java.time.LocalDateTime": + return toZonedDateTime(num).toLocalDateTime(); + case "java.util.TimeZone": + return toTimeZone(num.intValue()); + case "[B": + return toBytes(num); + case "[C": + return new char[] {(char) num.intValue()}; + case "java.util.OptionalInt": + return OptionalInt.of(num.intValue()); + case "java.util.OptionalLong": + return OptionalLong.of(num.longValue()); + case "java.util.OptionalDouble": + return OptionalDouble.of(num.doubleValue()); + default: + } + + if (targetClass.isEnum()) { + for (T e : targetClass.getEnumConstants()) { + if (((Enum) e).ordinal() == num.intValue()) { + return e; + } + } + } + } else if (source instanceof Date) { + Date date = (Date) source; + switch (targetClass.getName()) { + case "java.lang.String": + return DateUtils.format(date); + case "java.lang.Long": + case "long": + return date.getTime(); + case "java.lang.Integer": + case "int": + return (int) (date.getTime() / 1000); + case "java.util.Calendar": + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(date.getTime()); + return cal; + case "java.time.Instant": + return date.toInstant(); + case "java.time.ZonedDateTime": + return toZonedDateTime(date.getTime()); + case "java.time.LocalDate": + return toZonedDateTime(date.getTime()).toLocalDate(); + case "java.time.LocalTime": + return toZonedDateTime(date.getTime()).toLocalTime(); + case "java.time.LocalDateTime": + return toZonedDateTime(date.getTime()).toLocalDateTime(); + default: + } + } else if (source instanceof TemporalAccessor) { + return doConvert(DateUtils.toDate((TemporalAccessor) source), targetClass); + } else if (source instanceof Enum) { + Enum en = (Enum) source; + if (targetClass == String.class) { + return en.toString(); + } + if (targetClass == int.class || targetClass == Integer.class) { + return en.ordinal(); + } + if (Number.class.isAssignableFrom(targetClass)) { + return doConvert(en.ordinal(), targetClass); + } + if (targetClass.isEnum()) { + return Enum.valueOf((Class) targetClass, en.name()); + } + } else if (source instanceof byte[]) { + byte[] bytes = (byte[]) source; + + if (bytes.length == 0) { + return emptyDefault(targetClass); + } + + switch (targetClass.getName()) { + case "java.lang.String": + return new String(bytes, StandardCharsets.UTF_8); + case "java.lang.Double": + case "double": + return ByteBuffer.wrap(bytes).getDouble(); + case "java.lang.Float": + case "float": + return ByteBuffer.wrap(bytes).getFloat(); + case "java.lang.Long": + case "long": + return ByteBuffer.wrap(bytes).getLong(); + case "java.lang.Integer": + case "int": + return ByteBuffer.wrap(bytes).getInt(); + case "java.lang.Short": + case "short": + return ByteBuffer.wrap(bytes).getShort(); + case "java.lang.Character": + case "char": + return ByteBuffer.wrap(bytes).getChar(); + case "java.lang.Byte": + case "byte": + return bytes[0]; + case "java.lang.Boolean": + case "boolean": + return bytes[0] == (byte) 0 ? Boolean.FALSE : Boolean.TRUE; + case "java.math.BigInteger": + return new BigInteger(bytes); + case "java.util.Properties": + Properties properties = new Properties(); + properties.load(new ByteArrayInputStream(bytes)); + return properties; + default: + } + + Object target = jsonToObject(new String(bytes, StandardCharsets.ISO_8859_1), targetClass); + if (target != null) { + return target; + } + } + + Object target = customConvert(source, targetClass); + if (target != null) { + return target; + } + + if (targetClass.isArray()) { + Class itemType = targetClass.getComponentType(); + + if (source instanceof Collection) { + Collection c = (Collection) source; + int i = 0; + Object arr = Array.newInstance(itemType, c.size()); + for (Object item : c) { + Array.set(arr, i++, item == null ? null : doConvert(item, itemType)); + } + return arr; + } + + if (source instanceof Iterable) { + List list = new ArrayList(); + for (Object item : (Iterable) source) { + list.add(item == null ? null : doConvert(item, itemType)); + } + return list.toArray((Object[]) Array.newInstance(itemType, 0)); + } + + if (sourceClass.isArray()) { + int len = Array.getLength(source); + Object arr = Array.newInstance(itemType, len); + for (int i = 0; i < len; i++) { + Object item = Array.get(source, i); + Array.set(arr, i, item == null ? null : doConvert(item, itemType)); + } + return arr; + } + + Object arr = Array.newInstance(itemType, 1); + Array.set(arr, 0, doConvert(source, itemType)); + return arr; + } + + if (Collection.class.isAssignableFrom(targetClass)) { + target = convertCollection(toCollection(source), targetClass); + if (target != null) { + return target; + } + } + + if (Map.class.isAssignableFrom(targetClass) && source instanceof Map) { + target = convertMap((Map) source, targetClass); + if (target != null) { + return target; + } + } + + if (sourceClass.isArray()) { + if (Array.getLength(source) == 0) { + return nullDefault(targetClass); + } + return doConvert(Array.get(source, 0), targetClass); + } + + if (source instanceof List) { + List list = (List) source; + if (list.isEmpty()) { + return nullDefault(targetClass); + } + return doConvert(list.get(0), targetClass); + } + + if (source instanceof Iterable) { + Iterator it = ((Iterable) source).iterator(); + if (!it.hasNext()) { + return nullDefault(targetClass); + } + return doConvert(it.next(), targetClass); + } + + if (targetClass == String.class) { + if (sourceClass == HttpCookie.class) { + return ((HttpCookie) source).value(); + } + if (source instanceof InputStream) { + try (InputStream is = (InputStream) source) { + return StreamUtils.toString(is); + } + } + return source.toString(); + } + + if (targetClass == byte[].class) { + if (source instanceof InputStream) { + try (InputStream is = (InputStream) source) { + return StreamUtils.readBytes(is); + } + } + if (source instanceof Character) { + char c = (Character) source; + return new byte[] {(byte) (c >> 8), (byte) c}; + } + if (source instanceof Boolean) { + boolean b = (Boolean) source; + return new byte[] {b ? (byte) 1 : (byte) 0}; + } + } + + if (!Modifier.isAbstract(targetClass.getModifiers())) { + try { + for (Constructor ct : targetClass.getConstructors()) { + if (ct.getParameterCount() == 1) { + if (ct.getParameterTypes()[0].isAssignableFrom(sourceClass)) { + return ct.newInstance(source); + } + } + } + } catch (Throwable ignored) { + } + } + + if (sourceClass == String.class) { + try { + Method valueOf = targetClass.getMethod("valueOf", String.class); + //noinspection JavaReflectionInvocation + return valueOf.invoke(null, source); + } catch (Throwable ignored) { + } + return null; + } + + try { + return JsonUtils.convertObject(source, targetClass); + } catch (Exception e) { + LOGGER.debug("JSON convert from [{}] to [{}] failed", sourceClass.getName(), targetClass.getName(), e); + } + + return null; + } + + private Object doConvert(Object source, Type targetType) throws Exception { + if (targetType instanceof Class) { + return doConvert(source, (Class) targetType); + } + + if (source == null) { + return nullDefault(getActualType(targetType)); + } + + if (source.getClass() == Optional.class) { + source = ((Optional) source).orElse(null); + if (source == null) { + return nullDefault(getActualType(targetType)); + } + } + + if (source instanceof CharSequence) { + String str = source.toString(); + + if (str.isEmpty() || "null".equals(str) || "NULL".equals(str)) { + return emptyDefault(getActualType(targetType)); + } + + Object target = jsonToObject(str, targetType); + if (target != null) { + return target; + } + } + + if (targetType instanceof ParameterizedType) { + ParameterizedType type = (ParameterizedType) targetType; + Type rawType = type.getRawType(); + if (rawType instanceof Class) { + Class targetClass = (Class) rawType; + Type[] argTypes = type.getActualTypeArguments(); + + if (Collection.class.isAssignableFrom(targetClass)) { + Type itemType = getActualGenericType(argTypes[0]); + Collection items = toCollection(source); + Collection targetItems = createCollection(targetClass, items.size()); + for (Object item : items) { + targetItems.add(doConvert(item, itemType)); + } + return targetItems; + } + + if (Map.class.isAssignableFrom(targetClass)) { + Type keyType = argTypes[0]; + Type valueType = argTypes[1]; + Class mapValueClass = TypeUtils.getMapValueType(targetClass); + boolean multiValue = mapValueClass != null && Collection.class.isAssignableFrom(mapValueClass); + + if (source instanceof CharSequence) { + source = tokenizeToMap(source.toString()); + } + + if (source instanceof Map) { + Map map = (Map) source; + Map targetMap = createMap(targetClass, map.size()); + for (Map.Entry entry : map.entrySet()) { + Object key = doConvert(entry.getKey(), keyType); + if (multiValue) { + Collection items = toCollection(entry.getValue()); + Collection targetItems = createCollection(mapValueClass, items.size()); + for (Object item : items) { + targetItems.add(doConvert(item, valueType)); + } + targetMap.put(key, targetItems); + } else { + targetMap.put(key, doConvert(entry.getValue(), valueType)); + } + } + return targetMap; + } + } + + if (targetClass == Optional.class) { + return Optional.ofNullable(doConvert(source, argTypes[0])); + } + } + } else if (targetType instanceof TypeVariable) { + return doConvert(source, ((TypeVariable) targetType).getBounds()[0]); + } else if (targetType instanceof WildcardType) { + return doConvert(source, ((WildcardType) targetType).getUpperBounds()[0]); + } else if (targetType instanceof GenericArrayType) { + Type itemType = ((GenericArrayType) targetType).getGenericComponentType(); + Class itemClass = getActualType(itemType); + Collection items = toCollection(source); + Object target = Array.newInstance(itemClass, items.size()); + int i = 0; + for (Object item : items) { + Array.set(target, i++, doConvert(item, itemType)); + } + return target; + } + + Object target = customConvert(source, targetType); + if (target != null) { + return target; + } + + try { + return JsonUtils.convertObject(source, targetType); + } catch (Exception e) { + String name = source.getClass().getName(); + LOGGER.debug("JSON convert from [{}] to [{}] failed", name, targetType.getTypeName(), e); + } + + return null; + } + + protected Object customConvert(Object source, Class targetClass) { + return converterUtil == null ? null : converterUtil.convertIfPossible(source, targetClass); + } + + protected Object customConvert(Object source, Type targetType) { + return null; + } + + protected Collection customCreateCollection(Class targetClass, int size) { + return null; + } + + private Map customCreateMap(Class targetClass, int size) { + return null; + } + + private Collection createCollection(Class targetClass, int size) { + if (targetClass.isInterface()) { + if (targetClass == List.class || targetClass == Collection.class) { + return new ArrayList<>(size); + } + if (targetClass == Set.class) { + return CollectionUtils.newHashSet(size); + } + if (targetClass == SortedSet.class) { + return CollectionUtils.newLinkedHashSet(size); + } + if (targetClass == Queue.class || targetClass == Deque.class) { + return new LinkedList<>(); + } + } else if (Collection.class.isAssignableFrom(targetClass)) { + if (targetClass == ArrayList.class) { + return new ArrayList<>(size); + } + if (targetClass == LinkedList.class) { + return new LinkedList(); + } + if (targetClass == HashSet.class) { + return CollectionUtils.newHashSet(size); + } + if (targetClass == LinkedHashSet.class) { + return CollectionUtils.newLinkedHashSet(size); + } + if (!Modifier.isAbstract(targetClass.getModifiers())) { + try { + Constructor defCt = null; + for (Constructor ct : targetClass.getConstructors()) { + switch (ct.getParameterCount()) { + case 0: + defCt = ct; + break; + case 1: + Class paramType = ct.getParameterTypes()[0]; + if (paramType == int.class) { + return (Collection) ct.newInstance(size); + } + break; + default: + } + } + if (defCt != null) { + return (Collection) defCt.newInstance(); + } + } catch (Exception ignored) { + } + } + } + Collection collection = customCreateCollection(targetClass, size); + if (collection != null) { + return collection; + } + collection = (Collection) converterUtil.convertIfPossible(size, targetClass); + if (collection != null) { + return collection; + } + if (targetClass.isAssignableFrom(ArrayList.class)) { + return new ArrayList<>(size); + } + if (targetClass.isAssignableFrom(LinkedHashSet.class)) { + return CollectionUtils.newLinkedHashSet(size); + } + throw new IllegalArgumentException("Unsupported collection type: " + targetClass.getName()); + } + + private Collection convertCollection(Collection source, Class targetClass) { + if (targetClass.isInstance(source)) { + return source; + } + if (targetClass.isInterface()) { + if (targetClass == List.class || targetClass == Collection.class) { + return new ArrayList<>(source); + } + if (targetClass == Set.class) { + return new HashSet<>(source); + } + if (targetClass == SortedSet.class) { + return new LinkedHashSet(source); + } + if (targetClass == Queue.class || targetClass == Deque.class) { + return new LinkedList<>(source); + } + } else { + if (targetClass == ArrayList.class) { + return new ArrayList<>(source); + } + if (targetClass == LinkedList.class) { + return new LinkedList(source); + } + if (targetClass == HashSet.class) { + return new HashSet(source); + } + if (targetClass == LinkedHashSet.class) { + return new LinkedHashSet(source); + } + if (Modifier.isAbstract(targetClass.getModifiers())) { + Collection collection = (Collection) converterUtil.convertIfPossible(source.size(), targetClass); + if (collection != null) { + collection.addAll(source); + return collection; + } + if (targetClass.isAssignableFrom(ArrayList.class)) { + return new ArrayList<>(source); + } + if (targetClass.isAssignableFrom(LinkedHashSet.class)) { + return new LinkedHashSet(source); + } + return null; + } + try { + Constructor defCt = null; + for (Constructor ct : targetClass.getConstructors()) { + if (Modifier.isPublic(ct.getModifiers())) { + switch (ct.getParameterCount()) { + case 0: + defCt = ct; + break; + case 1: + Class paramType = ct.getParameterTypes()[0]; + if (paramType == Collection.class) { + return (Collection) ct.newInstance(source); + } else if (paramType == List.class) { + return (Collection) ct.newInstance(toList(source)); + } + break; + default: + } + } + } + if (defCt != null) { + Collection c = (Collection) defCt.newInstance(); + c.addAll(source); + return c; + } + } catch (Exception ignored) { + } + } + return null; + } + + private Map createMap(Class targetClass, int size) { + if (targetClass.isInterface()) { + if (targetClass == Map.class) { + return CollectionUtils.newHashMap(size); + } + if (targetClass == ConcurrentMap.class) { + return CollectionUtils.newConcurrentHashMap(size); + } + if (SortedMap.class.isAssignableFrom(targetClass)) { + return new TreeMap<>(); + } + } else if (Map.class.isAssignableFrom(targetClass)) { + if (targetClass == HashMap.class) { + return CollectionUtils.newHashMap(size); + } + if (targetClass == LinkedHashMap.class) { + return CollectionUtils.newLinkedHashMap(size); + } + if (targetClass == TreeMap.class) { + return new TreeMap<>(); + } + if (targetClass == ConcurrentHashMap.class) { + return CollectionUtils.newConcurrentHashMap(size); + } + if (!Modifier.isAbstract(targetClass.getModifiers())) { + try { + Constructor defCt = null; + for (Constructor ct : targetClass.getConstructors()) { + if (Modifier.isPublic(ct.getModifiers())) { + switch (ct.getParameterCount()) { + case 0: + defCt = ct; + break; + case 1: + Class paramType = ct.getParameterTypes()[0]; + if (paramType == int.class) { + return (Map) ct.newInstance(CollectionUtils.capacity(size)); + } + break; + default: + } + } + } + if (defCt != null) { + return (Map) defCt.newInstance(); + } + } catch (Throwable ignored) { + } + } + } + Map map = customCreateMap(targetClass, size); + if (map != null) { + return map; + } + map = (Map) converterUtil.convertIfPossible(size, targetClass); + if (map != null) { + return map; + } + if (targetClass.isAssignableFrom(LinkedHashMap.class)) { + return CollectionUtils.newLinkedHashMap(size); + } + throw new IllegalArgumentException("Unsupported map type: " + targetClass.getName()); + } + + private Map convertMap(Map source, Class targetClass) { + if (targetClass.isInstance(source)) { + return source; + } + if (targetClass.isInterface()) { + if (targetClass == Map.class) { + return new HashMap<>(source); + } + if (targetClass == ConcurrentMap.class) { + return new ConcurrentHashMap<>(source); + } + if (SortedMap.class.isAssignableFrom(targetClass)) { + return new TreeMap<>(source); + } + } else { + if (targetClass == HashMap.class) { + return new HashMap(source); + } + if (targetClass == LinkedHashMap.class) { + return new LinkedHashMap(source); + } + if (targetClass == TreeMap.class) { + return new TreeMap(source); + } + if (targetClass == ConcurrentHashMap.class) { + return new ConcurrentHashMap(source); + } + if (Modifier.isAbstract(targetClass.getModifiers())) { + Map map = (Map) converterUtil.convertIfPossible(source.size(), targetClass); + if (map != null) { + map.putAll(source); + return map; + } + if (targetClass.isAssignableFrom(LinkedHashMap.class)) { + return new LinkedHashMap(source); + } + return null; + } + try { + Constructor defCt = null; + for (Constructor ct : targetClass.getConstructors()) { + switch (ct.getParameterCount()) { + case 0: + defCt = ct; + break; + case 1: + Class paramType = ct.getParameterTypes()[0]; + if (paramType == Map.class) { + return (Map) ct.newInstance(source); + } + break; + default: + } + } + if (defCt != null) { + Map map = (Map) defCt.newInstance(); + map.putAll(source); + return map; + } + } catch (Exception ignored) { + } + } + return null; + } + + private Object emptyDefault(Class targetClass) { + if (targetClass == null) { + return null; + } + if (targetClass.isPrimitive()) { + return nullDefault(targetClass); + } + if (targetClass == Optional.class) { + return Optional.empty(); + } + if (List.class.isAssignableFrom(targetClass)) { + return targetClass == List.class ? Collections.EMPTY_LIST : createCollection(targetClass, 0); + } + if (Set.class.isAssignableFrom(targetClass)) { + return targetClass == Set.class ? Collections.EMPTY_SET : createCollection(targetClass, 0); + } + if (Map.class.isAssignableFrom(targetClass)) { + return targetClass == Map.class ? Collections.EMPTY_MAP : createMap(targetClass, 0); + } + if (targetClass.isArray()) { + return Array.newInstance(targetClass.getComponentType(), 0); + } + return null; + } + + private static Map tokenizeToMap(String str) { + if (StringUtils.isEmpty(str)) { + return Collections.emptyMap(); + } + Map result = new LinkedHashMap<>(); + for (String item : tokenizeToList(str, ';')) { + int index = item.indexOf('='); + if (index == -1) { + result.put(item, null); + } else { + result.put( + item.substring(0, index).trim(), + RequestUtils.decodeURL(item.substring(index + 1).trim())); + } + } + return result; + } + + private static boolean isMaybeJSON(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; + } + 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; + } + + private static Object jsonToObject(String value, Type targetType) { + if (isMaybeJSON(value)) { + try { + return JsonUtils.toJavaObject(value, targetType); + } catch (Throwable t) { + LOGGER.debug("Failed to parse [{}] from json string [{}]", targetType, value, t); + } + } + return null; + } + + private static boolean isHexNumber(String value) { + if (value.length() < 3) { + return false; + } + int index = value.indexOf('-') == 0 ? 1 : 0; + char c0 = value.charAt(index); + if (c0 == '0') { + char c1 = value.charAt(index + 1); + return c1 == 'x' || c1 == 'X'; + } + return c0 == '#'; + } + + private static Boolean toBoolean(Number n) { + Class type = n.getClass(); + if (type == Double.class) { + return n.doubleValue() != 0.0D; + } + if (type == Float.class) { + return n.floatValue() != 0.0F; + } + if (type == BigDecimal.class) { + return ((BigDecimal) n).compareTo(BigDecimal.ZERO) != 0; + } + return n.intValue() != 0; + } + + private static Boolean toBoolean(String str) { + if (str == null) { + return null; + } + switch (str.length()) { + case 1: + char c = str.charAt(0); + if (c == 'y' || c == 'Y' || c == 't' || c == 'T' || c == '1') { + return Boolean.TRUE; + } + if (c == 'n' || c == 'N' || c == 'f' || c == 'F' || c == '0') { + return Boolean.FALSE; + } + break; + case 2: + if ("on".equalsIgnoreCase(str)) { + return Boolean.TRUE; + } + if ("no".equalsIgnoreCase(str)) { + return Boolean.FALSE; + } + break; + case 3: + if ("yes".equalsIgnoreCase(str)) { + return Boolean.TRUE; + } + if ("off".equalsIgnoreCase(str)) { + return Boolean.FALSE; + } + break; + case 4: + if ("true".equalsIgnoreCase(str)) { + return Boolean.TRUE; + } + break; + case 5: + if ("false".equalsIgnoreCase(str)) { + return Boolean.FALSE; + } + break; + default: + } + return null; + } + + private static byte[] toBytes(Number n) { + ByteBuffer buffer; + if (n instanceof Long || n instanceof AtomicLong) { + buffer = ByteBuffer.allocate(8); + buffer.putLong(n.longValue()); + } else if (n instanceof Integer || n instanceof AtomicInteger) { + buffer = ByteBuffer.allocate(4); + buffer.putInt(n.intValue()); + } else if (n instanceof Double) { + buffer = ByteBuffer.allocate(8); + buffer.putDouble(n.doubleValue()); + } else if (n instanceof Float) { + buffer = ByteBuffer.allocate(4); + buffer.putFloat(n.floatValue()); + } else if (n instanceof Short) { + buffer = ByteBuffer.allocate(2); + buffer.putShort(n.shortValue()); + } else if (n instanceof Byte) { + return new byte[] {n.byteValue()}; + } else if (n instanceof BigInteger) { + return ((BigInteger) n).toByteArray(); + } else { + return null; + } + return buffer.array(); + } + + private static ZonedDateTime toZonedDateTime(String str) { + return DateUtils.parse(str).toInstant().atZone(ZoneId.systemDefault()); + } + + private static ZonedDateTime toZonedDateTime(Number num) { + return Instant.ofEpochMilli(num.longValue()).atZone(ZoneId.systemDefault()); + } + + private static TimeZone toTimeZone(int offset) { + if (offset < -12 || offset > 12) { + throw new RestParameterException("Invalid timeZone offset " + offset); + } + StringBuilder sb = new StringBuilder(); + sb.append("GMT"); + if (offset >= 0) { + sb.append('+'); + if (offset < 10) { + sb.append('0'); + } + } else { + sb.append('-'); + if (offset > -10) { + sb.append('0'); + } + } + sb.append(offset).append(":00"); + return TimeZone.getTimeZone(sb.toString()); + } + + private static List toList(Iterable source) { + List list = new ArrayList(32); + for (Object item : source) { + list.add(item); + } + return list; + } + + private static List toList(Collection source) { + if (source instanceof List) { + return (List) source; + } + List list = new ArrayList(source.size()); + list.addAll(source); + return list; + } + + private static List arrayToList(Object source) { + int len = Array.getLength(source); + Object[] array = new Object[len]; + for (int i = 0; i < len; i++) { + array[i] = Array.get(source, i); + } + return Arrays.asList(array); + } + + private static Collection toCollection(Object source) { + if (source instanceof Collection) { + return (Collection) source; + } + if (source.getClass().isArray()) { + return arrayToList(source); + } + if (source instanceof Iterable) { + return toList((Iterable) source); + } + if (source instanceof CharSequence) { + return tokenizeToList(source.toString()); + } + return Collections.singletonList(source); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/MiscArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/MiscArgumentResolver.java new file mode 100644 index 00000000000..d11f3ccc981 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/MiscArgumentResolver.java @@ -0,0 +1,73 @@ +/* + * 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.argument; + +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.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +@Activate +public class MiscArgumentResolver implements ArgumentResolver { + + private static final Set> SUPPORTED_TYPES = new HashSet<>(); + + static { + SUPPORTED_TYPES.add(HttpRequest.class); + SUPPORTED_TYPES.add(HttpResponse.class); + SUPPORTED_TYPES.add(HttpMethods.class); + SUPPORTED_TYPES.add(Locale.class); + SUPPORTED_TYPES.add(InputStream.class); + SUPPORTED_TYPES.add(OutputStream.class); + } + + @Override + public boolean accept(ParameterMeta parameter) { + return SUPPORTED_TYPES.contains(parameter.getActualType()); + } + + @Override + public Object resolve(ParameterMeta parameter, HttpRequest request, HttpResponse response) { + Class type = parameter.getActualType(); + if (type == HttpRequest.class) { + return request; + } + if (type == HttpResponse.class) { + return response; + } + if (type == HttpMethods.class) { + return HttpMethods.of(request.method()); + } + if (type == Locale.class) { + return request.locale(); + } + if (type == InputStream.class) { + return request.inputStream(); + } + if (type == OutputStream.class) { + return response.outputStream(); + } + return null; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java new file mode 100644 index 00000000000..90b02b68a9b --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java @@ -0,0 +1,95 @@ +/* + * 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.argument; + +import org.apache.dubbo.common.utils.CollectionUtils; +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; +import org.apache.dubbo.rpc.protocol.tri.rest.RestParameterException; +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.TypeUtils; + +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +public abstract class NamedValueArgumentResolverSupport { + + protected final Map cache = CollectionUtils.newConcurrentHashMap(); + + 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); + } + + if (arg != null) { + return StringUtils.EMPTY_STRING.equals(arg) ? meta.defaultValue() : arg; + } + arg = meta.defaultValue(); + if (arg != null) { + return arg; + } + if (meta.required()) { + throw new RestParameterException(Messages.ARGUMENT_VALUE_MISSING, meta.name(), type); + } + return null; + } + + protected final NamedValueMeta updateNamedValueMeta(ParameterMeta parameterMeta, NamedValueMeta meta) { + if (StringUtils.isEmpty(meta.name())) { + meta.setName(parameterMeta.getRequiredName()); + } + + Class type = parameterMeta.getType(); + Type genericType = parameterMeta.getGenericType(); + if (type == Optional.class) { + Class actualType = TypeUtils.getNestedType(genericType, 0); + meta.setType(actualType == null ? Object.class : actualType); + } else { + meta.setType(type); + } + if (type.isArray()) { + meta.setNestedTypes(new Class[] {type.getComponentType()}); + } else { + meta.setNestedTypes(TypeUtils.getNestedTypes(genericType)); + } + meta.setParameterMeta(parameterMeta); + return meta; + } + + protected abstract Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response); + + protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + return resolveValue(meta, request, response); + } + + protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) { + Object value = resolveValue(meta, request, response); + return value instanceof Map ? value : Collections.singletonMap(meta.name(), value); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/TypeConverter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/TypeConverter.java new file mode 100644 index 00000000000..355abb1d7a8 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/TypeConverter.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.argument; + +import javax.annotation.Nullable; + +import java.lang.reflect.Type; + +public interface TypeConverter { + + @Nullable + T convert(Object source, Class targetClass); + + @Nullable + T convert(Object source, Type targetType); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/AbstractRestFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/AbstractRestFilter.java new file mode 100644 index 00000000000..2517287fdf7 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/AbstractRestFilter.java @@ -0,0 +1,55 @@ +/* + * 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.filter; + +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestUtils; + +import java.util.Arrays; + +public abstract class AbstractRestFilter implements RestFilter { + + protected final E extension; + + public AbstractRestFilter(E extension) { + this.extension = extension; + } + + @Override + public int getPriority() { + return RestUtils.getPriority(extension); + } + + @Override + public String[] getPatterns() { + return RestUtils.getPattens(extension); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("RestFilter{extension="); + sb.append(extension); + int priority = getPriority(); + if (priority != 0) { + sb.append(", priority=").append(priority); + } + String[] patterns = getPatterns(); + if (patterns != null) { + sb.append(", patterns=").append(Arrays.toString(patterns)); + } + return sb.append('}').toString(); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/DefaultFilterChain.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/DefaultFilterChain.java new file mode 100644 index 00000000000..f4ddd2ad26c --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/DefaultFilterChain.java @@ -0,0 +1,136 @@ +/* + * 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.filter; + +import org.apache.dubbo.common.constants.LoggerCodeConstants; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.AppResponse; +import org.apache.dubbo.rpc.AsyncRpcResult; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter.FilterChain; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter.Listener; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +final class DefaultFilterChain implements FilterChain, Listener { + + private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(DefaultFilterChain.class); + + private final RestFilter[] filters; + private final Invocation invocation; + private final Supplier action; + + private int cursor; + private Result result; + private CompletableFuture resultFuture; + + DefaultFilterChain(RestFilter[] filters, Invocation invocation, Supplier action) { + this.filters = filters; + this.invocation = invocation; + this.action = action; + } + + public Result execute(HttpRequest request, HttpResponse response) throws Exception { + doFilter(request, response); + return result; + } + + @Override + public void doFilter(HttpRequest request, HttpResponse response) throws Exception { + if (cursor < filters.length) { + filters[cursor++].doFilter(request, response, this); + return; + } + if (resultFuture == null) { + result = action.get(); + } else { + action.get().whenCompleteWithContext((r, e) -> { + if (e == null) { + resultFuture.complete(new AppResponse(r)); + } else { + resultFuture.complete(new AppResponse(e)); + } + }); + } + } + + @Override + public CompletableFuture doFilterAsync(HttpRequest request, HttpResponse response) { + if (resultFuture == null) { + resultFuture = new CompletableFuture<>(); + result = new AsyncRpcResult(resultFuture, invocation); + } + CompletableFuture future = new CompletableFuture<>(); + future.whenComplete((v, t) -> { + if (t == null) { + if (v != null && v) { + try { + doFilter(request, response); + } catch (Exception e) { + resultFuture.complete(new AppResponse(e)); + } + } else { + resultFuture.complete(new AppResponse()); + } + } else { + resultFuture.complete(new AppResponse(t)); + } + }); + return future; + } + + @Override + public void onResponse(Result result, HttpRequest request, HttpResponse response) { + for (int i = cursor - 1; i > -1; i--) { + RestFilter filter = filters[i]; + if (filter instanceof Listener) { + try { + ((Listener) filter).onResponse(result, request, response); + } catch (Throwable t) { + LOGGER.error( + LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION, + "", + "", + "Call onResponse for filter " + "[" + filter + "] error"); + } + } + } + } + + @Override + public void onError(Throwable t, HttpRequest request, HttpResponse response) { + for (int i = cursor - 1; i > -1; i--) { + RestFilter filter = filters[i]; + if (filter instanceof Listener) { + try { + ((Listener) filter).onError(t, request, response); + } catch (Throwable th) { + LOGGER.error( + LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION, + "", + "", + "Call onError for filter " + "[" + filter + "] error"); + } + } + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtension.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtension.java new file mode 100644 index 00000000000..7f8683b45b0 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtension.java @@ -0,0 +1,29 @@ +/* + * 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.filter; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.common.lang.Prioritized; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface RestExtension extends Prioritized { + + default String[] getPatterns() { + return null; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtensionAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtensionAdapter.java new file mode 100644 index 00000000000..c623954d7a2 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtensionAdapter.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.rpc.protocol.tri.rest.filter; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface RestExtensionAdapter { + + boolean accept(Object extension); + + RestFilter adapt(T extension); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtensionExecutionFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtensionExecutionFilter.java new file mode 100644 index 00000000000..9c76f47e82a --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtensionExecutionFilter.java @@ -0,0 +1,198 @@ +/* + * 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.filter; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.beans.support.InstantiationStrategy; +import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.extension.ExtensionAccessorAware; +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.AppResponse; +import org.apache.dubbo.rpc.AsyncRpcResult; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.model.ApplicationModel; +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.RestInitializeException; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestUtils; +import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +@Activate(group = CommonConstants.PROVIDER, order = 1000) +public class RestExtensionExecutionFilter extends RestFilterAdapter { + + private static final String KEY = RestExtensionExecutionFilter.class.getSimpleName(); + + private final ApplicationModel applicationModel; + private final List> extensionAdapters; + + @SuppressWarnings({"unchecked", "rawtypes"}) + public RestExtensionExecutionFilter(ApplicationModel applicationModel) { + this.applicationModel = applicationModel; + extensionAdapters = (List) applicationModel.getActivateExtensions(RestExtensionAdapter.class); + } + + @Override + protected Result invoke(Invoker invoker, Invocation invocation, HttpRequest request, HttpResponse response) + throws RpcException { + RestFilter[] filters = getFilters(invoker); + DefaultFilterChain chain = new DefaultFilterChain(filters, invocation, () -> invoker.invoke(invocation)); + invocation.put(KEY, chain); + try { + Result result = chain.execute(request, response); + if (result != null) { + return result; + } + Object body = response.body(); + if (body instanceof Throwable) { + response.setBody(null); + return AsyncRpcResult.newDefaultAsyncResult((Throwable) body, invocation); + } + if (body instanceof CompletableFuture) { + CompletableFuture future = (CompletableFuture) body; + response.setBody(null); + return new AsyncRpcResult( + future.handleAsync((v, t) -> { + AppResponse r = new AppResponse(invocation); + if (t != null) { + r.setException(t); + } else { + r.setValue(v); + } + return r; + }), + invocation); + } + return AsyncRpcResult.newDefaultAsyncResult(invocation); + } catch (Throwable t) { + throw RestException.wrap(t); + } + } + + @Override + protected void onResponse( + Result result, Invoker invoker, Invocation invocation, HttpRequest request, HttpResponse response) { + DefaultFilterChain chain = (DefaultFilterChain) invocation.get(KEY); + if (chain == null) { + return; + } + chain.onResponse(result, request, response); + if (result.hasException()) { + Object body = response.body(); + if (body != null) { + if (body instanceof Throwable) { + result.setException((Throwable) body); + } else { + result.setValue(body); + result.setException(null); + } + response.setBody(null); + } + } + } + + @Override + protected void onError( + Throwable t, Invoker invoker, Invocation invocation, HttpRequest request, HttpResponse response) { + DefaultFilterChain chain = (DefaultFilterChain) invocation.get(KEY); + if (chain == null) { + return; + } + chain.onError(t, request, response); + } + + private RestFilter[] getFilters(Invoker invoker) { + URL url = invoker.getUrl(); + RestFilter[] filters = getFilters(url); + if (filters != null) { + return filters; + } + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (invoker) { + filters = getFilters(url); + if (filters != null) { + return filters; + } + filters = loadFilters(url); + url.putAttribute(RestConstants.EXTENSIONS_ATTRIBUTE_KEY, filters); + return filters; + } + } + + private RestFilter[] getFilters(URL url) { + return (RestFilter[]) url.getAttribute(RestConstants.EXTENSIONS_ATTRIBUTE_KEY); + } + + private RestFilter[] loadFilters(URL url) { + List extensions = new ArrayList<>(); + + // 1. load from extension config + String extensionConfig = url.getParameter(RestConstants.EXTENSION_KEY); + InstantiationStrategy strategy = new InstantiationStrategy(() -> applicationModel); + for (String className : StringUtils.tokenize(extensionConfig)) { + try { + Object extension = strategy.instantiate(TypeUtils.loadClass(className)); + if (extension instanceof ExtensionAccessorAware) { + ((ExtensionAccessorAware) extension).setExtensionAccessor(applicationModel); + } + adaptExtension(extension, extensions); + } catch (Throwable t) { + throw new RestInitializeException(t, Messages.EXTENSION_INIT_FAILED, className, url); + } + } + + // 2. load from extension loader + List restExtensions = applicationModel + .getExtensionLoader(RestExtension.class) + .getActivateExtension(url, RestConstants.REST_FILTER_KEY); + for (RestExtension extension : restExtensions) { + adaptExtension(extension, extensions); + } + + // 3. sorts by order + extensions.sort(Comparator.comparingInt(RestUtils::getPriority)); + + return extensions.toArray(new RestFilter[0]); + } + + private void adaptExtension(Object extension, List extensions) { + if (extension instanceof Supplier) { + extension = ((Supplier) extension).get(); + } + if (extension instanceof RestFilter) { + extensions.add((RestFilter) extension); + return; + } + for (RestExtensionAdapter adapter : extensionAdapters) { + if (adapter.accept(extension)) { + extensions.add(adapter.adapt(extension)); + } + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestFilter.java new file mode 100644 index 00000000000..c7a083bcacd --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestFilter.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.filter; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.Result; + +import java.util.concurrent.CompletableFuture; + +public interface RestFilter extends RestExtension { + + default void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) throws Exception { + chain.doFilter(request, response); + } + + interface Listener { + + default void onResponse(Result result, HttpRequest request, HttpResponse response) throws Exception {} + + default void onError(Throwable t, HttpRequest request, HttpResponse response) throws Exception {} + } + + interface FilterChain { + + void doFilter(HttpRequest request, HttpResponse response) throws Exception; + + CompletableFuture doFilterAsync(HttpRequest request, HttpResponse response); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestFilterAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestFilterAdapter.java new file mode 100644 index 00000000000..b0e016e01f1 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestFilterAdapter.java @@ -0,0 +1,67 @@ +/* + * 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.filter; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.BaseFilter; +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.RpcException; +import org.apache.dubbo.rpc.protocol.tri.TripleConstant; + +public abstract class RestFilterAdapter implements Filter, BaseFilter.Listener { + + @Override + public final Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + if (TripleConstant.TRIPLE_HANDLER_TYPE_REST.equals(invocation.get(TripleConstant.HANDLER_TYPE_KEY))) { + HttpRequest request = (HttpRequest) invocation.get(TripleConstant.HTTP_REQUEST_KEY); + HttpResponse response = (HttpResponse) invocation.get(TripleConstant.HTTP_RESPONSE_KEY); + return invoke(invoker, invocation, request, response); + } + return invoker.invoke(invocation); + } + + @Override + public final void onResponse(Result appResponse, Invoker invoker, Invocation invocation) { + if (TripleConstant.TRIPLE_HANDLER_TYPE_REST.equals(invocation.get(TripleConstant.HANDLER_TYPE_KEY))) { + HttpRequest request = (HttpRequest) invocation.get(TripleConstant.HTTP_REQUEST_KEY); + HttpResponse response = (HttpResponse) invocation.get(TripleConstant.HTTP_RESPONSE_KEY); + onResponse(appResponse, invoker, invocation, request, response); + } + } + + @Override + public final void onError(Throwable t, Invoker invoker, Invocation invocation) { + if (TripleConstant.TRIPLE_HANDLER_TYPE_REST.equals(invocation.get(TripleConstant.HANDLER_TYPE_KEY))) { + HttpRequest request = (HttpRequest) invocation.get(TripleConstant.HTTP_REQUEST_KEY); + HttpResponse response = (HttpResponse) invocation.get(TripleConstant.HTTP_RESPONSE_KEY); + onError(t, invoker, invocation, request, response); + } + } + + protected abstract Result invoke( + Invoker invoker, Invocation invocation, HttpRequest request, HttpResponse response) throws RpcException; + + protected void onResponse( + Result result, Invoker invoker, Invocation invocation, HttpRequest request, HttpResponse response) {} + + protected void onError( + Throwable t, Invoker invoker, Invocation invocation, HttpRequest request, HttpResponse response) {} +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java new file mode 100644 index 00000000000..abbf4a7a5ab --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestHeaderFilterAdapter.java @@ -0,0 +1,42 @@ +/* + * 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.filter; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.HeaderFilter; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.protocol.tri.TripleConstant; + +public abstract class RestHeaderFilterAdapter implements HeaderFilter { + + @Override + public RpcInvocation invoke(Invoker invoker, RpcInvocation invocation) throws RpcException { + if (TripleConstant.TRIPLE_HANDLER_TYPE_REST.equals(invocation.get(TripleConstant.HANDLER_TYPE_KEY))) { + HttpRequest request = (HttpRequest) invocation.get(TripleConstant.HTTP_REQUEST_KEY); + HttpResponse response = (HttpResponse) invocation.get(TripleConstant.HTTP_RESPONSE_KEY); + return invoke(invoker, invocation, request, response); + } + return invocation; + } + + protected abstract RpcInvocation invoke( + Invoker invoker, Invocation invocation, HttpRequest request, HttpResponse response) throws RpcException; +} 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 new file mode 100644 index 00000000000..d31858ce725 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/ContentNegotiator.java @@ -0,0 +1,154 @@ +/* + * 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.common.config.Configuration; +import org.apache.dubbo.common.config.ConfigurationUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpUtils; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; +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.RestConstants; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ContentNegotiator { + + private final FrameworkModel frameworkModel; + private final CodecUtils codecUtils; + private Map extensionMapping; + private String parameterName; + + public ContentNegotiator(FrameworkModel frameworkModel) { + this.frameworkModel = frameworkModel; + codecUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CodecUtils.class); + } + + public String negotiate(HttpRequest request) { + String mediaType; + + // 1. find mediaType by producible + List produces = request.attribute(RestConstants.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); + if (produces != null) { + for (int i = 0, size = produces.size(); i < size; i++) { + mediaType = getSuitableMediaType(produces.get(i).getName()); + if (mediaType != null) { + return mediaType; + } + } + } + + // 2. find mediaType by accept header + List accepts = HttpUtils.parseAccept(request.accept()); + if (accepts != null) { + for (int i = 0, size = accepts.size(); i < size; i++) { + mediaType = getSuitableMediaType(accepts.get(i)); + if (mediaType != null) { + return mediaType; + } + } + } + + // 3. find mediaType by format parameter + String format = request.queryParameter(getParameterName()); + if (format != null) { + mediaType = getMediaTypeByExtension(format); + if (mediaType != null) { + return mediaType; + } + } + + // 5. find mediaType by extension + String path = request.rawPath(); + int index = path.lastIndexOf('.'); + if (index != -1) { + String extension = path.substring(index + 1); + return getMediaTypeByExtension(extension); + } + + return null; + } + + private String getSuitableMediaType(String name) { + int index = name.indexOf('/'); + if (index == -1 || index == name.length() - 1) { + return null; + } + + String type = name.substring(0, index); + if (MediaType.WILDCARD.equals(type)) { + return null; + } + + String subType = name.substring(index + 1); + if (MediaType.WILDCARD.equals(subType)) { + return MediaType.TEXT_PLAIN.getType().equals(type) ? MediaType.TEXT_PLAIN.getName() : null; + } + + int suffixIndex = subType.lastIndexOf('+'); + if (suffixIndex != -1) { + return getMediaTypeByExtension(subType.substring(suffixIndex + 1)); + } + + return name; + } + + public String getParameterName() { + String parameterName = this.parameterName; + if (parameterName == null) { + Configuration conf = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); + parameterName = conf.getString(RestConstants.FORMAT_PARAMETER_NAME_KEY, "format"); + this.parameterName = parameterName; + } + return parameterName; + } + + private String getMediaTypeByExtension(String extension) { + Map extensionMapping = this.extensionMapping; + if (extensionMapping == null) { + extensionMapping = new HashMap<>(); + + for (HttpMessageEncoderFactory factory : codecUtils.getEncoderFactories()) { + MediaType mediaType = factory.mediaType(); + String subType = mediaType.getSubType(); + int index = subType.lastIndexOf('+'); + if (index != -1) { + subType = subType.substring(index + 1); + } + extensionMapping.putIfAbsent(subType, mediaType); + } + + extensionMapping.put("css", MediaType.TEXT_CSS); + extensionMapping.put("js", MediaType.TEXT_JAVASCRIPT); + extensionMapping.put("yml", MediaType.APPLICATION_YAML); + extensionMapping.put("xhtml", MediaType.TEXT_HTML); + extensionMapping.put("html", MediaType.TEXT_HTML); + extensionMapping.put("htm", MediaType.TEXT_HTML); + for (String ext : new String[] {"txt", "md", "csv", "log", "properties"}) { + extensionMapping.put(ext, MediaType.TEXT_PLAIN); + } + + this.extensionMapping = extensionMapping; + } + MediaType mediaType = extensionMapping.get(extension); + return mediaType == null ? null : mediaType.getName(); + } +} 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 new file mode 100644 index 00000000000..81680ac4f24 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java @@ -0,0 +1,236 @@ +/* + * 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.common.utils.Assert; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.message.MethodMetadata; +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.ServiceDescriptor; +import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.RestInitializeException; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RadixTree.Match; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ProducesCondition; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.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.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; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReadWriteLock; +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 List resolvers; + + private final RadixTree tree = new RadixTree<>(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + public DefaultRequestMappingRegistry(FrameworkModel frameworkModel) { + resolvers = frameworkModel.getActivateExtensions(RequestMappingResolver.class); + } + + @Override + public void register(Invoker invoker) { + Object service = invoker.getUrl().getServiceModel().getProxyObject(); + 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)) { + continue; + } + RequestMapping classMapping = resolver.resolve(serviceMeta); + consumer.accept((methods) -> { + MethodMeta methodMeta = new MethodMeta(methods, serviceMeta); + RequestMapping methodMapping = resolver.resolve(methodMeta); + if (methodMapping == null) { + return; + } + if (classMapping != null) { + methodMapping = classMapping.combine(methodMapping); + } + register0(methodMapping, buildHandlerMeta(invoker, methodMeta)); + }); + } + }); + } + + private void register0(RequestMapping mapping, HandlerMeta handler) { + lock.writeLock().lock(); + try { + Registration registration = new Registration(); + 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); + } + } + } 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(); + try { + tree.remove(mapping -> mapping.meta.getInvoker() == invoker); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void destroy() { + lock.writeLock().lock(); + try { + tree.clear(); + } finally { + lock.writeLock().unlock(); + } + } + + 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(); + } + + 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); + } + } + + size = candidates.size(); + if (size == 0) { + return null; + } + if (size > 1) { + candidates.sort((c1, c2) -> { + int comparison = c1.expression.compareTo(c2.expression, path); + if (comparison != 0) { + return comparison; + } + comparison = c1.mapping.compareTo(c2.mapping, request); + if (comparison != 0) { + return comparison; + } + return c1.variableMap.size() - c2.variableMap.size(); + }); + 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); + } + } + + Candidate winner = candidates.get(0); + RequestMapping mapping = winner.mapping; + HandlerMeta handler = winner.meta; + request.setAttribute(RestConstants.MAPPING_ATTRIBUTE, mapping); + request.setAttribute(RestConstants.HANDLER_ATTRIBUTE, handler); + + if (!winner.variableMap.isEmpty()) { + request.setAttribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE, winner.variableMap); + } + + ProducesCondition producesCondition = mapping.getProducesCondition(); + if (producesCondition != null) { + request.setAttribute(RestConstants.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, producesCondition.getMediaTypes()); + } + + return handler; + } + + private static final class Registration { + RequestMapping mapping; + HandlerMeta meta; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != Registration.class) { + return false; + } + return mapping.equals(((Registration) obj).mapping); + } + + @Override + public int hashCode() { + return mapping.hashCode(); + } + } + + private static final class Candidate { + RequestMapping mapping; + HandlerMeta meta; + PathExpression expression; + Map variableMap; + } +} 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 new file mode 100644 index 00000000000..cc13a38439d --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java @@ -0,0 +1,316 @@ +/* + * 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.common.utils.Pair; +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.PathUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Predicate; + +/** + * A high-performance Radix Tree for efficient path matching. + * + * @param Type of values associated with the paths. + */ +public final class RadixTree { + + private final Map>> directPathMap = new HashMap<>(); + private final Node root = new Node<>(); + + public T addPath(PathExpression path, T value) { + if (path.isDirect()) { + List> matches = directPathMap.computeIfAbsent(path.getPath(), k -> new ArrayList<>()); + for (int i = 0, len = matches.size(); i < len; i++) { + Match match = matches.get(i); + if (match.getValue().equals(value)) { + return match.getValue(); + } + } + matches.add(new Match<>(path, value)); + return null; + } + + Node current = root; + PathSegment[] segments = path.getSegments(); + for (int i = 0, len = segments.length; i < len; i++) { + Node child = getChild(current, segments[i]); + if (i == len - 1) { + List> values = child.values; + for (int j = 0, size = values.size(); j < size; j++) { + if (values.get(j).getLeft().equals(path)) { + return values.get(j).getRight(); + } + } + values.add(Pair.of(path, value)); + } + current = child; + } + return null; + } + + private static Node getChild(Node current, PathSegment segment) { + Node child; + if (segment.getType() == Type.LITERAL) { + Map> children = current.children; + Key key = new Key(segment.getValue()); + child = children.get(key); + if (child == null) { + child = new Node<>(); + children.put(key, child); + } + } else { + Map> children = current.fuzzyChildren; + child = children.get(segment); + if (child == null) { + child = new Node<>(); + children.put(segment, child); + } + } + return child; + } + + public void remove(Predicate tester) { + directPathMap.entrySet().removeIf(entry -> { + List> values = entry.getValue(); + values.removeIf(match -> tester.test(match.getValue())); + return values.isEmpty(); + }); + removeRecursive(root, tester); + } + + private void removeRecursive(Node current, Predicate tester) { + current.values.removeIf(pair -> tester.test(pair.getValue())); + + List>> list = new ArrayList<>(); + list.add(current.children); + list.add(current.fuzzyChildren); + for (Map> children : list) { + Iterator>> cit = children.entrySet().iterator(); + while (cit.hasNext()) { + Node node = cit.next().getValue(); + removeRecursive(node, tester); + if (node.isEmpty()) { + cit.remove(); + } + } + } + } + + /** + * Ensure that the path is normalized using {@link PathUtils#normalize(String)} before matching. + */ + public void match(String path, List> matches) { + List> directMatches = directPathMap.get(path); + if (directMatches != null) { + for (int i = 0, size = directMatches.size(); i < size; i++) { + matches.add(directMatches.get(i)); + } + return; + } + + matchRecursive(root, path, 1, new HashMap<>(), matches); + } + + public List> match(String path) { + List> matches = directPathMap.get(path); + if (matches != null) { + return new ArrayList<>(matches); + } + + matches = new ArrayList<>(); + matchRecursive(root, path, 1, new HashMap<>(), matches); + return matches; + } + + private void matchRecursive( + Node current, String path, int start, Map variableMap, List> matches) { + int end = path.indexOf('/', start); + Node node = current.children.get(new Key(path, start, end)); + if (node != null) { + if (node.isLeaf()) { + addMatch(node, variableMap, matches); + return; + } + matchRecursive(node, path, end + 1, variableMap, matches); + } + + if (current.fuzzyChildren.isEmpty()) { + return; + } + Map workVariableMap = new LinkedHashMap<>(); + for (Map.Entry> entry : current.fuzzyChildren.entrySet()) { + PathSegment segment = entry.getKey(); + if (segment.match(path, start, end, workVariableMap)) { + workVariableMap.putAll(variableMap); + Node child = entry.getValue(); + if (segment.isTailMatching() || child.isLeaf()) { + addMatch(child, workVariableMap, matches); + } else { + matchRecursive(child, path, end + 1, workVariableMap, matches); + } + if (!workVariableMap.isEmpty()) { + workVariableMap = new HashMap<>(); + } + } + } + } + + private static void addMatch(Node node, Map variableMap, List> matches) { + List> values = node.values; + variableMap = variableMap.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(variableMap); + for (int i = 0, size = values.size(); i < size; i++) { + Pair pair = values.get(i); + matches.add(new Match<>(pair.getLeft(), pair.getRight(), variableMap)); + } + } + + public void clear() { + directPathMap.clear(); + root.clear(); + } + + public boolean isEmpty() { + return directPathMap.isEmpty() && root.isEmpty(); + } + + public static final class Match implements Comparable> { + + private final PathExpression expression; + private final T value; + private final Map variableMap; + + Match(PathExpression expression, T value, Map variableMap) { + this.expression = expression; + this.value = value; + this.variableMap = variableMap; + } + + private Match(PathExpression expression, T value) { + this.expression = expression; + this.value = value; + variableMap = Collections.emptyMap(); + } + + public PathExpression getExpression() { + return expression; + } + + public T getValue() { + return value; + } + + public Map getVariableMap() { + return variableMap; + } + + @Override + public int compareTo(Match other) { + int comparison = expression.compareTo(other.getExpression()); + return comparison == 0 ? variableMap.size() - other.variableMap.size() : comparison; + } + } + + /** + * 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> fuzzyChildren = new HashMap<>(); + private final List> values = new ArrayList<>(); + + private boolean isLeaf() { + return children.isEmpty() && fuzzyChildren.isEmpty(); + } + + private boolean isEmpty() { + return isLeaf() && values.isEmpty(); + } + + private void clear() { + children.clear(); + fuzzyChildren.clear(); + values.clear(); + } + } +} 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 new file mode 100644 index 00000000000..2c38367c71a --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java @@ -0,0 +1,385 @@ +/* + * 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.remoting.http12.HttpMethods; +import org.apache.dubbo.remoting.http12.HttpRequest; +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; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.HeadersCondition; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.MethodsCondition; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ParamsCondition; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathCondition; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ProducesCondition; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ResponseMeta; + +import java.util.Objects; + +import static org.apache.dubbo.common.utils.ArrayUtils.isEmpty; + +public final class RequestMapping implements Condition { + + private final String name; + private final PathCondition pathCondition; + private final MethodsCondition methodsCondition; + private final ParamsCondition paramsCondition; + private final HeadersCondition headersCondition; + private final ConsumesCondition consumesCondition; + private final ProducesCondition producesCondition; + private final ConditionWrapper customCondition; + private final ResponseMeta response; + + private int hashCode; + + private RequestMapping( + String name, + PathCondition pathCondition, + MethodsCondition methodsCondition, + ParamsCondition paramsCondition, + HeadersCondition headersCondition, + ConsumesCondition consumesCondition, + ProducesCondition producesCondition, + Condition customCondition, + ResponseMeta response) { + this.name = name; + this.pathCondition = pathCondition; + this.methodsCondition = methodsCondition; + this.paramsCondition = paramsCondition; + this.headersCondition = headersCondition; + this.consumesCondition = consumesCondition; + this.producesCondition = producesCondition; + this.customCondition = customCondition == null ? null : new ConditionWrapper(customCondition); + this.response = response; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public RequestMapping combine(RequestMapping other) { + String name = this.name == null ? other.name : other.name == null ? this.name : this.name + "#" + other.name; + PathCondition paths = combine(pathCondition, other.pathCondition); + MethodsCondition methods = combine(methodsCondition, other.methodsCondition); + ParamsCondition params = combine(paramsCondition, other.paramsCondition); + HeadersCondition headers = combine(headersCondition, other.headersCondition); + ConsumesCondition consumes = combine(consumesCondition, other.consumesCondition); + ProducesCondition produces = combine(producesCondition, other.producesCondition); + ConditionWrapper custom = combine(customCondition, other.customCondition); + ResponseMeta response = ResponseMeta.combine(this.response, other.response); + return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, response); + } + + private > T combine(T value, T other) { + return value == null ? other : other == null ? value : value.combine(other); + } + + public RequestMapping match(HttpRequest request, PathExpression path) { + return doMatch(request, new PathCondition(path)); + } + + @Override + public RequestMapping match(HttpRequest request) { + return doMatch(request, null); + } + + private RequestMapping doMatch(HttpRequest request, PathCondition pathCondition) { + MethodsCondition methods = null; + if (methodsCondition != null) { + methods = methodsCondition.match(request); + if (methods == null) { + return null; + } + } + + PathCondition paths = pathCondition; + if (paths == null && this.pathCondition != null) { + paths = this.pathCondition.match(request); + if (paths == null) { + return null; + } + } + + ParamsCondition params = null; + if (paramsCondition != null) { + params = paramsCondition.match(request); + if (params == null) { + return null; + } + } + + HeadersCondition headers = null; + if (headersCondition != null) { + headers = headersCondition.match(request); + if (headers == null) { + return null; + } + } + + ConsumesCondition consumes = null; + if (consumesCondition != null) { + consumes = consumesCondition.match(request); + if (consumes == null) { + return null; + } + } + + ProducesCondition produces = null; + if (producesCondition != null) { + produces = producesCondition.match(request); + if (produces == null) { + return null; + } + } + + ConditionWrapper custom = null; + if (customCondition != null) { + custom = customCondition.match(request); + if (custom == null) { + return null; + } + } + + return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, response); + } + + public String getName() { + return name; + } + + public PathCondition getPathCondition() { + return pathCondition; + } + + public ProducesCondition getProducesCondition() { + return producesCondition; + } + + public ResponseMeta getResponse() { + return response; + } + + @Override + public int compareTo(RequestMapping other, HttpRequest request) { + int result; + if (methodsCondition != null && HttpMethods.HEAD.name().equals(request.method())) { + result = methodsCondition.compareTo(other.methodsCondition, request); + if (result != 0) { + return result; + } + } + if (pathCondition != null) { + result = pathCondition.compareTo(other.pathCondition, request); + if (result != 0) { + return result; + } + } + if (paramsCondition != null) { + result = paramsCondition.compareTo(other.paramsCondition, request); + if (result != 0) { + return result; + } + } + if (headersCondition != null) { + result = headersCondition.compareTo(other.headersCondition, request); + if (result != 0) { + return result; + } + } + if (consumesCondition != null) { + result = consumesCondition.compareTo(other.consumesCondition, request); + if (result != 0) { + return result; + } + } + if (producesCondition != null) { + result = producesCondition.compareTo(other.producesCondition, request); + if (result != 0) { + return result; + } + } + if (methodsCondition != null) { + result = methodsCondition.compareTo(other.methodsCondition, request); + if (result != 0) { + return result; + } + } + if (customCondition != null) { + result = customCondition.compareTo(other.customCondition, request); + return result; + } + return 0; + } + + @Override + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash( + pathCondition, + methodsCondition, + paramsCondition, + headersCondition, + consumesCondition, + producesCondition, + customCondition); + this.hashCode = hashCode; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != RequestMapping.class) { + return false; + } + RequestMapping other = (RequestMapping) obj; + return Objects.equals(pathCondition, other.pathCondition) + && Objects.equals(methodsCondition, other.methodsCondition) + && Objects.equals(paramsCondition, other.paramsCondition) + && Objects.equals(headersCondition, other.headersCondition) + && Objects.equals(consumesCondition, other.consumesCondition) + && Objects.equals(producesCondition, other.producesCondition) + && Objects.equals(customCondition, other.customCondition); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("RequestMapping{name='"); + sb.append(name).append('\''); + if (pathCondition != null) { + sb.append(", pathCondition=").append(pathCondition); + } + if (methodsCondition != null) { + sb.append(", methodsCondition=").append(methodsCondition); + } + if (paramsCondition != null) { + sb.append(", paramsCondition=").append(paramsCondition); + } + if (headersCondition != null) { + sb.append(", headersCondition=").append(headersCondition); + } + if (consumesCondition != null) { + sb.append(", consumesCondition=").append(consumesCondition); + } + if (producesCondition != null) { + sb.append(", producesCondition=").append(producesCondition); + } + if (customCondition != null) { + sb.append(", customCondition=").append(customCondition); + } + if (response != null) { + sb.append(", response=").append(response); + } + sb.append('}'); + return sb.toString(); + } + + public static final class Builder { + private String name; + private String contextPath; + private String[] paths; + private String[] methods; + private String[] params; + private String[] headers; + private String[] consumes; + private String[] produces; + private Condition customCondition; + private Integer responseStatus; + private String responseReason; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder contextPath(String contextPath) { + this.contextPath = contextPath; + return this; + } + + public Builder path(String... paths) { + this.paths = paths; + return this; + } + + public Builder method(String... methods) { + this.methods = methods; + return this; + } + + public Builder param(String... params) { + this.params = params; + return this; + } + + public Builder header(String... headers) { + this.headers = headers; + return this; + } + + public Builder consume(String... consumes) { + this.consumes = consumes; + return this; + } + + public Builder produce(String... produces) { + this.produces = produces; + return this; + } + + public Builder custom(Condition customCondition) { + this.customCondition = customCondition; + return this; + } + + public Builder responseStatus(int status) { + responseStatus = status; + return this; + } + + public Builder responseReason(String reason) { + responseReason = reason; + return this; + } + + public RequestMapping build() { + PathCondition pathCondition = isEmpty(paths) ? null : new PathCondition(contextPath, paths); + MethodsCondition methodsCondition = isEmpty(methods) ? null : new MethodsCondition(methods); + ParamsCondition paramsCondition = isEmpty(params) ? null : new ParamsCondition(params); + HeadersCondition headersCondition = isEmpty(headers) ? null : new HeadersCondition(headers); + ConsumesCondition consumesCondition = isEmpty(consumes) ? null : new ConsumesCondition(consumes); + ProducesCondition producesCondition = isEmpty(produces) ? null : new ProducesCondition(produces); + ResponseMeta response = responseStatus == null ? null : new ResponseMeta(responseStatus, responseReason); + return new RequestMapping( + name, + pathCondition, + methodsCondition, + paramsCondition, + headersCondition, + consumesCondition, + producesCondition, + customCondition, + response); + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.java new file mode 100644 index 00000000000..b7dfb559946 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingRegistry.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.mapping; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta; + +/** + * RequestMappingRegistry used for registering and unregistering rest request mappings. + */ +public interface RequestMappingRegistry { + + void register(Invoker invoker); + + void unregister(Invoker invoker); + + HandlerMeta lookup(HttpRequest request); + + void destroy(); +} 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 new file mode 100644 index 00000000000..7c7b5f372e5 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.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.rest.mapping; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +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; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface RequestMappingResolver { + + RestToolKit getRestToolKit(); + + default boolean accept(ServiceMeta serviceMeta) { + return true; + } + + RequestMapping resolve(ServiceMeta serviceMeta); + + RequestMapping resolve(MethodMeta methodMeta); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java new file mode 100644 index 00000000000..e04b9794de1 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java @@ -0,0 +1,110 @@ +/* + * 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.common.URL; +import org.apache.dubbo.common.beans.factory.ScopeBeanFactory; +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.HttpResponse; +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.TripleConstant; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.RestHttpMessageCodec; +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.argument.GeneralTypeConverter; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils; +import org.apache.dubbo.rpc.protocol.tri.route.RequestHandler; +import org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping; + +@Activate(order = -2000) +public final class RestRequestHandlerMapping implements RequestHandlerMapping { + + private final FrameworkModel frameworkModel; + private final RequestMappingRegistry requestMappingRegistry; + private final ArgumentResolver argumentResolver; + private final TypeConverter typeConverter; + private final ContentNegotiator contentNegotiator; + private final CodecUtils codecUtils; + + public RestRequestHandlerMapping(FrameworkModel frameworkModel) { + this.frameworkModel = frameworkModel; + ScopeBeanFactory beanFactory = frameworkModel.getBeanFactory(); + requestMappingRegistry = beanFactory.getOrRegisterBean(DefaultRequestMappingRegistry.class); + argumentResolver = beanFactory.getOrRegisterBean(CompositeArgumentResolver.class); + typeConverter = beanFactory.getOrRegisterBean(GeneralTypeConverter.class); + contentNegotiator = beanFactory.getOrRegisterBean(ContentNegotiator.class); + codecUtils = beanFactory.getOrRegisterBean(CodecUtils.class); + } + + @Override + public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) { + HandlerMeta meta = requestMappingRegistry.lookup(request); + if (meta == null) { + return null; + } + + String requestMediaType = request.mediaType(); + String responseMediaType = contentNegotiator.negotiate(request); + if (responseMediaType != null) { + response.setContentType(responseMediaType); + } else { + if (requestMediaType != null && !RequestUtils.isFormOrMultiPart(request)) { + responseMediaType = requestMediaType; + } else { + responseMediaType = MediaType.APPLICATION_JSON.getName(); + } + } + RestHttpMessageCodec codec = new RestHttpMessageCodec( + request, + response, + meta.getParameters(), + argumentResolver, + typeConverter, + codecUtils.determineHttpMessageEncoder(url, frameworkModel, responseMediaType)); + + if (HttpMethods.supportBody(request.method()) && !RequestUtils.isFormOrMultiPart(request)) { + if (StringUtils.isEmpty(requestMediaType)) { + requestMediaType = responseMediaType; + } + request.setAttribute( + RestConstants.BODY_DECODER_ATTRIBUTE, + codecUtils.determineHttpMessageDecoder(url, frameworkModel, requestMediaType)); + } + + RequestHandler handler = new RequestHandler(meta.getInvoker()); + handler.setHasStub(false); + handler.setMethodDescriptor(meta.getMethodDescriptor()); + handler.setMethodMetadata(meta.getMethodMetadata()); + handler.setServiceDescriptor(meta.getServiceDescriptor()); + handler.setHttpMessageDecoder(codec); + handler.setHttpMessageEncoder(codec); + return handler; + } + + @Override + public String getType() { + return TripleConstant.TRIPLE_HANDLER_TYPE_REST; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/Condition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/Condition.java new file mode 100644 index 00000000000..aa34a44be67 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/Condition.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.rpc.protocol.tri.rest.mapping.condition; + +import org.apache.dubbo.remoting.http12.HttpRequest; + +public interface Condition { + + T combine(T other); + + T match(R request); + + int compareTo(T other, R request); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConditionWrapper.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConditionWrapper.java new file mode 100644 index 00000000000..c7b9ac17f93 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConditionWrapper.java @@ -0,0 +1,70 @@ +/* + * 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.remoting.http12.HttpRequest; + +@SuppressWarnings("unchecked") +public final class ConditionWrapper implements Condition { + + private final Condition condition; + + public ConditionWrapper(Condition condition) { + this.condition = (Condition) condition; + } + + @Override + public ConditionWrapper combine(ConditionWrapper other) { + return new ConditionWrapper((Condition) condition.combine(other.condition)); + } + + @Override + public ConditionWrapper match(HttpRequest request) { + Condition match = (Condition) condition.match(request); + return match == null ? null : new ConditionWrapper(match); + } + + @Override + public int compareTo(ConditionWrapper other, HttpRequest request) { + return condition.compareTo(other.condition, request); + } + + public Condition getCondition() { + return condition; + } + + @Override + public int hashCode() { + return condition.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != ConditionWrapper.class) { + return false; + } + return condition.equals(((ConditionWrapper) obj).condition); + } + + @Override + public String toString() { + return condition.toString(); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConsumesCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConsumesCondition.java new file mode 100644 index 00000000000..181d4c4a1c6 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConsumesCondition.java @@ -0,0 +1,136 @@ +/* + * 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.remoting.http12.HttpHeaderNames; +import org.apache.dubbo.remoting.http12.HttpRequest; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public final class ConsumesCondition implements Condition { + + public static final MediaTypeExpression DEFAULT = MediaTypeExpression.parse("application/octet-stream"); + + private final List expressions; + + public ConsumesCondition(String... consumes) { + this(consumes, null); + } + + public ConsumesCondition(String[] consumes, String[] headers) { + Set expressions = null; + if (headers != null) { + for (String header : headers) { + NameValueExpression expr = NameValueExpression.parse(header); + if (HttpHeaderNames.CONTENT_TYPE.getName().equalsIgnoreCase(expr.getName())) { + MediaTypeExpression expression = MediaTypeExpression.parse(expr.getValue()); + if (expression == null) { + continue; + } + if (expressions == null) { + expressions = new LinkedHashSet<>(); + } + expressions.add(expression); + } + } + } + if (consumes != null) { + for (String consume : consumes) { + MediaTypeExpression expression = MediaTypeExpression.parse(consume); + if (expression == null) { + continue; + } + if (expressions == null) { + expressions = new LinkedHashSet<>(); + } + expressions.add(expression); + } + } + if (expressions == null) { + this.expressions = Collections.emptyList(); + } else { + this.expressions = new ArrayList<>(expressions); + Collections.sort(this.expressions); + } + } + + private ConsumesCondition(List expressions) { + this.expressions = expressions; + } + + @Override + public ConsumesCondition combine(ConsumesCondition other) { + return other.expressions.isEmpty() ? this : other; + } + + @Override + public ConsumesCondition match(HttpRequest request) { + if (expressions.isEmpty()) { + return null; + } + + String contentType = request.contentType(); + MediaTypeExpression mediaType = contentType == null ? DEFAULT : MediaTypeExpression.parse(contentType); + List result = null; + for (int i = 0, size = expressions.size(); i < size; i++) { + MediaTypeExpression expression = expressions.get(i); + if (expression.match(mediaType)) { + if (result == null) { + result = new ArrayList<>(); + } + result.add(expression); + } + } + return result == null ? null : new ConsumesCondition(result); + } + + @Override + public int compareTo(ConsumesCondition other, HttpRequest request) { + if (expressions.isEmpty()) { + return other.expressions.isEmpty() ? 0 : 1; + } + if (other.expressions.isEmpty()) { + return -1; + } + return expressions.get(0).compareTo(other.expressions.get(0)); + } + + @Override + public int hashCode() { + return expressions.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != ConsumesCondition.class) { + return false; + } + return expressions.equals(((ConsumesCondition) obj).expressions); + } + + @Override + public String toString() { + return "ConsumesCondition{mediaTypes=" + expressions + '}'; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/HeadersCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/HeadersCondition.java new file mode 100644 index 00000000000..2a69d3b1b4b --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/HeadersCondition.java @@ -0,0 +1,95 @@ +/* + * 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.remoting.http12.HttpHeaderNames; +import org.apache.dubbo.remoting.http12.HttpRequest; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +public final class HeadersCondition implements Condition { + + private final Set expressions; + + public HeadersCondition(String... headers) { + Set expressions = null; + if (headers != null) { + for (String header : headers) { + NameValueExpression expr = NameValueExpression.parse(header); + String name = expr.getName(); + if (HttpHeaderNames.ACCEPT.getName().equalsIgnoreCase(name) + || HttpHeaderNames.CONTENT_TYPE.getName().equalsIgnoreCase(name)) { + continue; + } + if (expressions == null) { + expressions = new LinkedHashSet<>(); + } + expressions.add(expr); + } + } + this.expressions = expressions == null ? Collections.emptySet() : expressions; + } + + private HeadersCondition(Set expressions) { + this.expressions = expressions; + } + + @Override + public HeadersCondition combine(HeadersCondition other) { + Set set = new LinkedHashSet<>(expressions); + set.addAll(other.expressions); + return new HeadersCondition(set); + } + + @Override + public HeadersCondition match(HttpRequest request) { + for (NameValueExpression expression : expressions) { + if (!expression.match(request::hasHeader, request::header)) { + return null; + } + } + return this; + } + + @Override + public int compareTo(HeadersCondition other, HttpRequest request) { + return other.expressions.size() - expressions.size(); + } + + @Override + public int hashCode() { + return expressions.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != HeadersCondition.class) { + return false; + } + return expressions.equals(((HeadersCondition) obj).expressions); + } + + @Override + public String toString() { + return "HeadersCondition{headers=" + expressions + '}'; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MediaTypeExpression.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MediaTypeExpression.java new file mode 100644 index 00000000000..9edb4c71cb2 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MediaTypeExpression.java @@ -0,0 +1,251 @@ +/* + * 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.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpUtils; +import org.apache.dubbo.remoting.http12.message.MediaType; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +public final class MediaTypeExpression implements Comparable { + + public static final MediaTypeExpression ALL = new MediaTypeExpression(MediaType.WILDCARD, MediaType.WILDCARD); + public static final List ALL_LIST = Collections.singletonList(ALL); + + public static final Comparator COMPARATOR = (m1, m2) -> { + int comparison = compareQuality(m1, m2); + if (comparison != 0) { + return comparison; + } + + comparison = compareType(m1.type, m2.type); + if (comparison != Integer.MIN_VALUE) { + return comparison; + } + + comparison = compareType(m1.subType, m2.subType); + return comparison == Integer.MIN_VALUE ? 0 : comparison; + }; + + public static final Comparator QUALITY_COMPARATOR = MediaTypeExpression::compareQuality; + + private final String type; + private final String subType; + private final boolean negated; + private final float quality; + + private MediaTypeExpression(String type, String subType, float quality, boolean negated) { + this.type = type; + this.subType = subType; + this.quality = quality; + this.negated = negated; + } + + public MediaTypeExpression(String type, String subType) { + this.type = type; + this.subType = subType; + quality = 1.0F; + negated = false; + } + + public static MediaTypeExpression parse(String expr) { + boolean negated; + if (expr.indexOf('!') == 0) { + negated = true; + expr = expr.substring(1); + } else { + negated = false; + } + if (StringUtils.isEmpty(expr)) { + return null; + } + + int index = expr.indexOf(';'); + String mimeType = (index == -1 ? expr : expr.substring(0, index)).trim(); + if (MediaType.WILDCARD.equals(mimeType)) { + mimeType = "*/*"; + } + int subIndex = mimeType.indexOf('/'); + if (subIndex == -1 || subIndex == mimeType.length() - 1) { + return null; + } + String type = mimeType.substring(0, subIndex); + String subType = mimeType.substring(subIndex + 1); + if (MediaType.WILDCARD.equals(type) && !MediaType.WILDCARD.equals(subType)) { + return null; + } + + return new MediaTypeExpression(type, subType, HttpUtils.parseQuality(expr, index), negated); + } + + private static int compareType(String type1, String type2) { + boolean type1IsWildcard = MediaType.WILDCARD.equals(type1); + boolean type2IsWildcard = MediaType.WILDCARD.equals(type2); + if (type1IsWildcard && !type2IsWildcard) { + return 1; + } + if (type2IsWildcard && !type1IsWildcard) { + return -1; + } + if (!type1.equals(type2)) { + return 0; + } + return Integer.MIN_VALUE; + } + + public String getType() { + return type; + } + + public String getSubType() { + return subType; + } + + public float getQuality() { + return quality; + } + + private static int compareQuality(MediaTypeExpression m1, MediaTypeExpression m2) { + return Float.compare(m2.quality, m1.quality); + } + + public boolean typesEquals(MediaTypeExpression other) { + return type.equalsIgnoreCase(other.type) && subType.equalsIgnoreCase(other.subType); + } + + public boolean match(MediaTypeExpression other) { + return matchMediaType(other) != negated; + } + + private boolean matchMediaType(MediaTypeExpression other) { + if (other == null) { + return false; + } + if (isWildcardType()) { + return true; + } + if (type.equals(other.type)) { + if (subType.equals(other.subType)) { + return true; + } + if (isWildcardSubtype()) { + int plusIdx = subType.lastIndexOf('+'); + if (plusIdx == -1) { + return true; + } + int otherPlusIdx = other.subType.indexOf('+'); + if (otherPlusIdx != -1) { + String subTypeNoSuffix = subType.substring(0, plusIdx); + String subTypeSuffix = subType.substring(plusIdx + 1); + String otherSubtypeSuffix = other.subType.substring(otherPlusIdx + 1); + return subTypeSuffix.equals(otherSubtypeSuffix) && MediaType.WILDCARD.equals(subTypeNoSuffix); + } + } + } + return false; + } + + public boolean compatibleWith(MediaTypeExpression other) { + return compatibleWithMediaType(other) != negated; + } + + private boolean compatibleWithMediaType(MediaTypeExpression other) { + if (other == null) { + return false; + } + if (isWildcardType() || other.isWildcardType()) { + return true; + } + if (type.equals(other.type)) { + if (subType.equalsIgnoreCase(other.subType)) { + return true; + } + if (isWildcardSubtype() || other.isWildcardSubtype()) { + if (subType.equals(MediaType.WILDCARD) || other.subType.equals(MediaType.WILDCARD)) { + return true; + } + String thisSuffix = getSubtypeSuffix(); + String otherSuffix = other.getSubtypeSuffix(); + if (isWildcardSubtype() && thisSuffix != null) { + return (thisSuffix.equals(other.subType) || thisSuffix.equals(otherSuffix)); + } + if (other.isWildcardSubtype() && otherSuffix != null) { + return (subType.equals(otherSuffix) || otherSuffix.equals(thisSuffix)); + } + } + } + return false; + } + + private boolean isWildcardType() { + return MediaType.WILDCARD.equals(type); + } + + private boolean isWildcardSubtype() { + return MediaType.WILDCARD.equals(subType) || subType.startsWith("*+"); + } + + private String getSubtypeSuffix() { + int suffixIndex = subType.lastIndexOf('+'); + if (suffixIndex != -1) { + return subType.substring(suffixIndex + 1); + } + return null; + } + + @Override + public int compareTo(MediaTypeExpression other) { + return COMPARATOR.compare(this, other); + } + + @Override + public int hashCode() { + return Objects.hash(type, subType, negated, quality); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != MediaTypeExpression.class) { + return false; + } + MediaTypeExpression other = (MediaTypeExpression) obj; + return negated == other.negated + && Float.compare(quality, other.quality) == 0 + && Objects.equals(type, other.type) + && Objects.equals(subType, other.subType); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (negated) { + sb.append('!'); + } + sb.append(type).append('/').append(subType); + if (quality != 1.0F) { + sb.append(";q=").append(quality); + } + return sb.toString(); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java new file mode 100644 index 00000000000..fdde62dc61d --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java @@ -0,0 +1,95 @@ +/* + * 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.remoting.http12.HttpRequest; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.apache.dubbo.remoting.http12.HttpMethods.GET; +import static org.apache.dubbo.remoting.http12.HttpMethods.HEAD; + +public final class MethodsCondition implements Condition { + + private final Set methods; + + public MethodsCondition(String... methods) { + this.methods = new HashSet<>(Arrays.asList(methods)); + } + + private MethodsCondition(Set methods) { + this.methods = methods; + } + + @Override + public MethodsCondition combine(MethodsCondition other) { + Set set = new HashSet<>(methods); + set.addAll(other.methods); + return new MethodsCondition(set); + } + + @Override + public MethodsCondition match(HttpRequest request) { + String method = request.method(); + if (methods.contains(method)) { + return new MethodsCondition(method); + } + if (HEAD.name().equals(method) && methods.contains(GET.name())) { + return new MethodsCondition(GET.name()); + } + return null; + } + + @Override + public int compareTo(MethodsCondition other, HttpRequest request) { + if (other.methods.size() != methods.size()) { + return other.methods.size() - methods.size(); + } + if (methods.size() == 1) { + if (methods.contains(HEAD.name()) && other.methods.contains(GET.name())) { + return -1; + } + if (methods.contains(GET.name()) && other.methods.contains(HEAD.name())) { + return 1; + } + } + return 0; + } + + @Override + public int hashCode() { + return methods.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != MethodsCondition.class) { + return false; + } + return methods.equals(((MethodsCondition) obj).methods); + } + + @Override + public String toString() { + return "MethodsCondition{methods=" + methods + '}'; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/NameValueExpression.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/NameValueExpression.java new file mode 100644 index 00000000000..ca1a8a9f456 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/NameValueExpression.java @@ -0,0 +1,115 @@ +/* + * 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.common.utils.ArrayUtils; +import org.apache.dubbo.common.utils.CollectionUtils; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +public final class NameValueExpression { + + private final String name; + private final String value; + private final boolean negated; + + private NameValueExpression(String name, String value, boolean negated) { + this.name = name; + this.value = value; + this.negated = negated; + } + + public NameValueExpression(String name, String value) { + this.name = name; + this.value = value; + negated = false; + } + + public static Set parse(String... params) { + if (ArrayUtils.isEmpty(params)) { + return Collections.emptySet(); + } + int len = params.length; + Set expressions = CollectionUtils.newHashSet(len); + for (String param : params) { + expressions.add(parse(param)); + } + return expressions; + } + + public static NameValueExpression parse(String expr) { + int index = expr.indexOf('='); + if (index == -1) { + boolean negated = expr.indexOf('!') == 0; + return new NameValueExpression(negated ? expr.substring(1) : expr, null, negated); + } else { + boolean negated = index > 0 && expr.charAt(index - 1) == '!'; + return new NameValueExpression( + negated ? expr.substring(0, index - 1) : expr.substring(0, index), + expr.substring(index + 1), + negated); + } + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + public boolean match(Predicate nameFn, Function valueFn) { + boolean matched; + if (value == null) { + matched = nameFn.test(name); + } else { + matched = Objects.equals(valueFn.apply(name), value); + } + return matched != negated; + } + + public boolean match(Function valueFn) { + return match(n -> valueFn.apply(n) != null, valueFn); + } + + @Override + public int hashCode() { + return Objects.hash(name, value, negated); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != NameValueExpression.class) { + return false; + } + NameValueExpression other = (NameValueExpression) obj; + return negated == other.negated && Objects.equals(name, other.name) && Objects.equals(value, other.value); + } + + @Override + public String toString() { + return name + (negated ? "!=" : "=") + value; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ParamsCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ParamsCondition.java new file mode 100644 index 00000000000..63e792d63da --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ParamsCondition.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.rest.mapping.condition; + +import org.apache.dubbo.remoting.http12.HttpRequest; + +import java.util.LinkedHashSet; +import java.util.Set; + +public final class ParamsCondition implements Condition { + + private final Set expressions; + + public ParamsCondition(String... params) { + expressions = NameValueExpression.parse(params); + } + + private ParamsCondition(Set expressions) { + this.expressions = expressions; + } + + @Override + public ParamsCondition combine(ParamsCondition other) { + Set set = new LinkedHashSet<>(expressions); + set.addAll(other.expressions); + return new ParamsCondition(set); + } + + @Override + public ParamsCondition match(HttpRequest request) { + for (NameValueExpression expression : expressions) { + if (!expression.match(request::hasParameter, request::parameter)) { + return null; + } + } + return this; + } + + @Override + public int compareTo(ParamsCondition other, HttpRequest request) { + return other.expressions.size() - expressions.size(); + } + + @Override + public int hashCode() { + return expressions.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != ParamsCondition.class) { + return false; + } + return expressions.equals(((ParamsCondition) obj).expressions); + } + + @Override + public String toString() { + return "ParamsCondition{params=" + expressions + '}'; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathCondition.java new file mode 100644 index 00000000000..5291c504a72 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathCondition.java @@ -0,0 +1,159 @@ +/* + * 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.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; +import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public final class PathCondition implements Condition { + + private final String contextPath; + private final Set paths; + private List expressions; + + public PathCondition(String contextPath, String... paths) { + this.contextPath = contextPath; + this.paths = new LinkedHashSet<>(Arrays.asList(paths)); + } + + private PathCondition(String contextPath, Set paths) { + this.contextPath = contextPath; + this.paths = paths; + } + + public PathCondition(PathExpression path) { + contextPath = null; + paths = Collections.singleton(path.getPath()); + expressions = Collections.singletonList(path); + } + + public List getExpressions() { + List expressions = this.expressions; + if (expressions == null) { + expressions = new ArrayList<>(); + for (String path : paths) { + expressions.add(PathExpression.parse(PathUtils.normalize(contextPath, path))); + } + this.expressions = expressions; + } + return expressions; + } + + @Override + public PathCondition combine(PathCondition other) { + Set result = new LinkedHashSet<>(); + if (paths.isEmpty()) { + if (other.paths.isEmpty()) { + result.add(StringUtils.EMPTY_STRING); + } else { + result.addAll(other.paths); + } + } else { + if (other.paths.isEmpty()) { + result.addAll(paths); + } else { + for (String left : paths) { + for (String right : other.paths) { + result.add(PathUtils.combine(left, right)); + } + } + } + } + return new PathCondition(contextPath, result); + } + + @Override + public PathCondition match(HttpRequest request) { + List matches = null; + String path = request.rawPath(); + List expressions = getExpressions(); + for (int i = 0, size = expressions.size(); i < size; i++) { + PathExpression expression = expressions.get(i); + Map variables = expression.match(path); + if (variables != null) { + if (matches == null) { + matches = new ArrayList<>(); + } + matches.add(expression); + } + } + if (matches != null) { + Collections.sort(matches); + Set result = CollectionUtils.newLinkedHashSet(matches.size()); + for (int i = 0, size = matches.size(); i < size; i++) { + result.add(matches.get(i).getPath()); + } + return new PathCondition(contextPath, result); + } + return null; + } + + @Override + public int compareTo(PathCondition other, HttpRequest request) { + String lookupPath = request.attribute(RestConstants.PATH_ATTRIBUTE); + Iterator it = getExpressions().iterator(); + Iterator oit = other.getExpressions().iterator(); + while (it.hasNext() && oit.hasNext()) { + int result = it.next().compareTo(oit.next(), lookupPath); + if (result != 0) { + return result; + } + } + if (it.hasNext()) { + return -1; + } + if (oit.hasNext()) { + return 1; + } + return 0; + } + + @Override + public int hashCode() { + return 31 * paths.hashCode() + contextPath.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != PathCondition.class) { + return false; + } + PathCondition other = (PathCondition) obj; + return paths.equals(other.paths) && Objects.equals(contextPath, other.contextPath); + } + + @Override + public String toString() { + return "PathCondition{paths=" + paths + '}'; + } +} 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 new file mode 100644 index 00000000000..28594018160 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java @@ -0,0 +1,126 @@ +/* + * 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.mapping.condition.PathSegment.Type; + +import javax.annotation.Nonnull; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public final class PathExpression implements Comparable { + + private final String path; + private final PathSegment[] segments; + + private PathExpression(String path, PathSegment[] segments) { + this.path = path; + this.segments = segments; + } + + public static PathExpression parse(@Nonnull String path) { + return new PathExpression(path, PathParser.parse(path)); + } + + public String getPath() { + return path; + } + + public PathSegment[] getSegments() { + return segments; + } + + public boolean isDirect() { + return segments.length == 1 && segments[0].getType() == Type.LITERAL; + } + + public Map match(@Nonnull String path) { + if (isDirect()) { + return this.path.equals(path) ? Collections.emptyMap() : null; + } + Map variableMap = new LinkedHashMap<>(); + int start, end = 0; + for (PathSegment segment : segments) { + if (end != -1) { + start = end + 1; + end = path.indexOf('/', start); + if (segment.match(path, start, end, variableMap)) { + continue; + } + } + return null; + } + return variableMap; + } + + public int compareTo(PathExpression other, String lookupPath) { + boolean equalsPath = path.equals(lookupPath); + boolean otherEqualsPath = other.path.equals(lookupPath); + if (equalsPath) { + return otherEqualsPath ? 0 : -1; + } + if (otherEqualsPath) { + return 1; + } + return compareTo(other); + } + + @Override + public int compareTo(PathExpression other) { + int size = segments.length; + int otherSize = other.segments.length; + for (int i = 0; i < size && i < otherSize; i++) { + int result = segments[i].compareTo(other.segments[i]); + if (result != 0) { + return result; + } + } + return otherSize - size; + } + + @Override + public int hashCode() { + return path.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != PathExpression.class) { + return false; + } + return path.equals(((PathExpression) obj).path); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(32); + for (PathSegment segment : segments) { + sb.append('/'); + if (segment.getType() == Type.VARIABLE) { + sb.append('{').append(segment.getValue()).append('}'); + } else { + sb.append(segment.getValue()); + } + } + 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 new file mode 100644 index 00000000000..30e978c21ec --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParser.java @@ -0,0 +1,462 @@ +/* + * 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.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.RestConstants; +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; + +/** + * See + *

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

+ */ +final class PathParser { + + private static final PathSegment SLASH = new PathSegment(Type.SLASH, RestConstants.SLASH); + + private final List segments = new LinkedList<>(); + private final StringBuilder buf = new StringBuilder(); + + /** + * Ensure that the path is normalized using {@link PathUtils#normalize(String)} before parsing. + */ + static PathSegment[] parse(String path) { + if (path == null || path.isEmpty() || RestConstants.SLASH.equals(path)) { + return new PathSegment[] {PathSegment.literal(RestConstants.SLASH)}; + } + if (PathUtils.isDirectPath(path)) { + return new PathSegment[] {PathSegment.literal(path)}; + } + List segments = new PathParser().doParse(path); + return segments.toArray(new PathSegment[0]); + } + + private List doParse(String path) { + parseSegments(path); + if (segments.isEmpty()) { + return Collections.emptyList(); + } + transformSegments(segments, path); + for (PathSegment segment : segments) { + try { + segment.initPattern(); + } catch (Exception e) { + throw new PathParserException(Messages.REGEX_PATTERN_INVALID, segment.getValue(), path, e); + } + } + return segments; + } + + private void parseSegments(String path) { + int state = State.INITIAL; + boolean regexBraceStart = false; + boolean regexMulti = false; + String variableName = null; + int len = path.length(); + for (int i = 0; i < len; i++) { + char c = path.charAt(i); + switch (c) { + case '/': + switch (state) { + case State.INITIAL: + case State.SEGMENT_END: + continue; + case State.LITERAL_START: + if (buf.length() > 0) { + appendSegment(Type.LITERAL); + } + break; + case State.WILDCARD_START: + appendSegment(Type.WILDCARD); + break; + case State.REGEX_VARIABLE_START: + if (path.charAt(i - 1) == '^' && path.charAt(i - 2) == '[') { + break; + } + regexMulti = true; + break; + case State.VARIABLE_START: + case State.WILDCARD_VARIABLE_START: + throw new PathParserException(Messages.MISSING_CLOSE_CAPTURE, path, i); + default: + } + segments.add(SLASH); + state = State.SEGMENT_END; + continue; + case '?': + switch (state) { + case State.INITIAL: + case State.LITERAL_START: + case State.SEGMENT_END: + state = State.WILDCARD_START; + break; + default: + } + break; + case '*': + switch (state) { + case State.INITIAL: + case State.LITERAL_START: + case State.SEGMENT_END: + state = State.WILDCARD_START; + break; + case State.VARIABLE_START: + if (path.charAt(i - 1) == '{') { + state = State.WILDCARD_VARIABLE_START; + continue; + } + break; + default: + } + break; + case '.': + if (state == State.REGEX_VARIABLE_START) { + if (path.charAt(i - 1) != '\\') { + regexMulti = true; + } + } + break; + case 'S': + case 'W': + if (state == State.REGEX_VARIABLE_START) { + if (path.charAt(i - 1) == '\\') { + regexMulti = true; + } + } + break; + case ':': + if (state == State.VARIABLE_START) { + state = State.REGEX_VARIABLE_START; + variableName = buf.toString(); + buf.setLength(0); + continue; + } + break; + case '{': + switch (state) { + case State.INITIAL: + case State.SEGMENT_END: + state = State.VARIABLE_START; + continue; + case State.LITERAL_START: + if (buf.length() > 0) { + appendSegment(Type.LITERAL); + } + state = State.VARIABLE_START; + continue; + case State.VARIABLE_START: + case State.WILDCARD_VARIABLE_START: + throw new PathParserException(Messages.ILLEGAL_NESTED_CAPTURE, path, i); + case State.REGEX_VARIABLE_START: + if (path.charAt(i - 1) != '\\') { + regexBraceStart = true; + } + break; + default: + } + break; + case '}': + switch (state) { + case State.INITIAL: + case State.LITERAL_START: + case State.SEGMENT_END: + throw new PathParserException(Messages.MISSING_OPEN_CAPTURE, path); + case State.VARIABLE_START: + appendSegment(Type.VARIABLE, buf.toString()); + state = State.LITERAL_START; + continue; + case State.REGEX_VARIABLE_START: + if (regexBraceStart) { + regexBraceStart = false; + } else { + if (buf.length() == 0) { + throw new PathParserException(Messages.MISSING_REGEX_CONSTRAINT, path, i); + } + appendSegment(regexMulti ? Type.PATTERN_MULTI : Type.PATTERN, variableName); + regexMulti = false; + state = State.LITERAL_START; + continue; + } + break; + case State.WILDCARD_VARIABLE_START: + appendSegment(Type.WILDCARD_TAIL, buf.toString()); + state = State.END; + continue; + default: + } + break; + default: + if (state == State.INITIAL || state == State.SEGMENT_END) { + state = State.LITERAL_START; + } + break; + } + if (state == State.END) { + throw new PathParserException(Messages.NO_MORE_DATA_ALLOWED, path, i); + } + buf.append(c); + } + + if (buf.length() > 0) { + switch (state) { + case State.LITERAL_START: + appendSegment(Type.LITERAL); + break; + case State.WILDCARD_START: + appendSegment(Type.WILDCARD); + break; + case State.VARIABLE_START: + 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: + } + } + } + + private void appendSegment(Type type) { + segments.add(new PathSegment(type, buf.toString())); + buf.setLength(0); + } + + private void appendSegment(Type type, String name) { + segments.add(new PathSegment(type, buf.toString().trim(), name.trim())); + buf.setLength(0); + } + + private static void transformSegments(List segments, String path) { + ListIterator iterator = segments.listIterator(); + PathSegment curr, prev = null; + while (iterator.hasNext()) { + curr = iterator.next(); + String value = curr.getValue(); + Type type = curr.getType(); + switch (type) { + case SLASH: + if (prev != null) { + switch (prev.getType()) { + case LITERAL: + case VARIABLE: + case PATTERN: + prev = curr; + break; + case PATTERN_MULTI: + prev.setValue(prev.getValue() + '/'); + break; + default: + } + } + iterator.remove(); + continue; + case WILDCARD: + if ("*".equals(value)) { + type = Type.VARIABLE; + value = StringUtils.EMPTY_STRING; + } else if ("**".equals(value)) { + if (!iterator.hasNext()) { + type = Type.WILDCARD_TAIL; + value = StringUtils.EMPTY_STRING; + } else { + type = Type.PATTERN_MULTI; + value = ".*"; + } + } else { + type = Type.PATTERN; + value = toRegex(value); + } + curr.setType(type); + curr.setValue(value); + break; + case WILDCARD_TAIL: + break; + case PATTERN: + case PATTERN_MULTI: + curr.setValue("(?<" + curr.getVariable() + '>' + value + ')'); + break; + default: + } + if (prev == null) { + prev = curr; + continue; + } + String pValue = prev.getValue(); + switch (prev.getType()) { + case LITERAL: + switch (type) { + case VARIABLE: + prev.setType(Type.PATTERN); + prev.setValue(quoteRegex(pValue) + "(?<" + curr.getVariable() + ">[^/]+)"); + prev.setVariables(curr.getVariables()); + iterator.remove(); + continue; + case PATTERN: + case PATTERN_MULTI: + prev.setType(type); + prev.setValue(quoteRegex(pValue) + "(?<" + curr.getVariable() + '>' + value + ')'); + prev.setVariables(curr.getVariables()); + iterator.remove(); + continue; + default: + } + break; + case VARIABLE: + switch (type) { + case LITERAL: + prev.setType(Type.PATTERN); + prev.setValue("(?<" + prev.getVariable() + ">[^/]+)" + quoteRegex(value)); + iterator.remove(); + continue; + case VARIABLE: + throw new PathParserException(Messages.ILLEGAL_DOUBLE_CAPTURE, path); + case PATTERN: + case PATTERN_MULTI: + prev.addVariable(curr.getVariable()); + prev.setType(type); + prev.setValue("(?<" + prev.getVariable() + ">[^/]+)" + value); + iterator.remove(); + continue; + default: + } + break; + case PATTERN: + case PATTERN_MULTI: + switch (type) { + case LITERAL: + prev.setValue(pValue + quoteRegex(value)); + iterator.remove(); + continue; + case WILDCARD_TAIL: + prev.addVariable(curr.getVariable()); + prev.setValue(pValue + "(?<" + curr.getVariable() + ">.*)"); + iterator.remove(); + continue; + case VARIABLE: + if (value.isEmpty()) { + prev.setValue(pValue + "[^/]+"); + iterator.remove(); + continue; + } + prev.addVariable(curr.getVariable()); + prev.setValue(pValue + "(?<" + curr.getVariable() + ">[^/]+)"); + iterator.remove(); + continue; + case PATTERN: + case PATTERN_MULTI: + prev.addVariable(curr.getVariable()); + prev.setValue(pValue + "(?<" + curr.getVariable() + '>' + value + ')'); + iterator.remove(); + continue; + default: + } + break; + default: + } + prev = curr; + } + } + + private static String quoteRegex(String regex) { + for (int i = 0, len = regex.length(); i < len; i++) { + switch (regex.charAt(i)) { + case '(': + case ')': + case '[': + case ']': + case '$': + case '^': + case '.': + case '{': + case '}': + case '|': + case '\\': + return "\\Q" + regex + "\\E"; + default: + } + } + return regex; + } + + private static String toRegex(String wildcard) { + int len = wildcard.length(); + StringBuilder sb = new StringBuilder(len + 8); + for (int i = 0; i < len; i++) { + char c = wildcard.charAt(i); + switch (c) { + case '*': + if (i > 0) { + char prev = wildcard.charAt(i - 1); + if (prev == '*') { + continue; + } + if (prev == '?') { + sb.append("*"); + continue; + } + } + sb.append("[^/]*"); + break; + case '?': + if (i > 0 && sb.charAt(i - 1) == '*') { + continue; + } + sb.append("[^/]"); + break; + case '(': + case ')': + case '$': + case '.': + case '{': + case '}': + case '|': + case '\\': + sb.append('\\'); + sb.append(c); + break; + default: + sb.append(c); + break; + } + } + return sb.toString(); + } + + private interface State { + + int INITIAL = 0; + int LITERAL_START = 1; + int WILDCARD_START = 2; + int VARIABLE_START = 3; + int REGEX_VARIABLE_START = 4; + int WILDCARD_VARIABLE_START = 5; + int SEGMENT_END = 6; + int END = 7; + } +} 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 new file mode 100644 index 00000000000..4a05a20bf16 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java @@ -0,0 +1,242 @@ +/* + * 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.common.utils.StringUtils; +import org.apache.dubbo.rpc.protocol.tri.rest.Messages; +import org.apache.dubbo.rpc.protocol.tri.rest.PathParserException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class PathSegment implements Comparable { + + private Type type; + private String value; + private List variables; + private Pattern pattern; + + public PathSegment(Type type, String value) { + this.type = type; + this.value = value; + } + + public PathSegment(Type type, String value, String variable) { + this.type = type; + this.value = value; + addVariable(variable); + } + + public static PathSegment literal(String value) { + return new PathSegment(Type.LITERAL, value); + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public List getVariables() { + return variables; + } + + public void setVariables(List variables) { + this.variables = variables; + } + + public String getVariable() { + return variables.get(0); + } + + public void addVariable(String variable) { + if (variables == null) { + variables = new ArrayList<>(); + } else if (variables.contains(variable)) { + throw new PathParserException(Messages.DUPLICATE_CAPTURE_VARIABLE, variable); + } + variables.add(variable); + } + + public Pattern getPattern() { + if (pattern == null) { + initPattern(); + } + return pattern; + } + + public void initPattern() { + if (isPattern()) { + pattern = Pattern.compile(value); + } + } + + public boolean isPattern() { + return type == Type.PATTERN || type == Type.PATTERN_MULTI; + } + + public boolean isTailMatching() { + return type == Type.WILDCARD_TAIL || type == Type.PATTERN_MULTI; + } + + public boolean match(String path, int start, int end, Map variableMap) { + switch (type) { + case SLASH: + case LITERAL: + return path.regionMatches(start, value, 0, value.length()); + case WILDCARD_TAIL: + if (variables != null) { + variableMap.put(getVariable(), path.substring(start)); + } + return true; + case VARIABLE: + if (variables != null) { + variableMap.put(getVariable(), StringUtils.substring(path, start, end)); + } + return true; + case PATTERN: + return matchPattern(StringUtils.substring(path, start, end), variableMap); + case PATTERN_MULTI: + return matchPattern(path.substring(start), variableMap); + default: + return false; + } + } + + private boolean matchPattern(String path, Map variableMap) { + Matcher matcher = getPattern().matcher(path); + if (matcher.matches()) { + for (int i = 0, size = variables.size(); i < size; i++) { + String variable = variables.get(i); + variableMap.put(variable, matcher.group(variable)); + } + return true; + } + return false; + } + + @Override + public int hashCode() { + return type.ordinal() | (value == null ? 0 : value.hashCode() << 3); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != PathSegment.class) { + return false; + } + PathSegment that = (PathSegment) obj; + return type == that.type && value.equals(that.value); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{type="); + sb.append(type); + if (value != null) { + sb.append(", value=").append(value); + } + if (variables != null) { + sb.append(", variables=").append(variables); + } + sb.append('}'); + return sb.toString(); + } + + @Override + public int compareTo(PathSegment other) { + if (type != other.type) { + int comparison = type.score() - other.type.score; + if (comparison != 0) { + return comparison; + } + } + int size = variables == null ? 0 : variables.size(); + int otherSize = other.variables == null ? 0 : other.variables.size(); + return size - otherSize; + } + + public enum Type { + /** + * A slash segment, transient type used for parsing. + * will not be present in the PathExpression + * E.g.: '/' + */ + SLASH, + /** + * A literal segment. + * E.g.: 'foo' + */ + LITERAL(1), + /** + * A wildcard segment. + * E.g.: 't?st*uv' and '/foo/*/bar' + */ + WILDCARD, + /** + * A wildcard matching suffix. + * Transient type used for parsing, will not be present in the PathExpression + * E.g.: '/foo/**' and '/**' and '/{*bar}' + */ + WILDCARD_TAIL, + /** + * A template variable segment. + * E.g.: '{foo}' + */ + VARIABLE(10), + /** + * A regex variable matching single segment. + * E.g.: '{foo:\d+}' + */ + PATTERN(100), + /** + * A regex variable matching multiple segments. + * E.g.: '{foo:.*}' + */ + PATTERN_MULTI(200); + + private final int score; + + Type(int score) { + this.score = score; + } + + Type() { + score = 10000; + } + + public int score() { + return score; + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ProducesCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ProducesCondition.java new file mode 100644 index 00000000000..03f01a38ed1 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ProducesCondition.java @@ -0,0 +1,218 @@ +/* + * 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.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.Pair; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpHeaderNames; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.message.MediaType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiPredicate; + +public final class ProducesCondition implements Condition { + + private final List expressions; + + public ProducesCondition(String... produces) { + this(produces, null); + } + + public ProducesCondition(String[] produces, String[] headers) { + Set expressions = null; + if (headers != null) { + for (String header : headers) { + NameValueExpression expr = NameValueExpression.parse(header); + if (HttpHeaderNames.ACCEPT.getName().equalsIgnoreCase(expr.getName())) { + MediaTypeExpression expression = MediaTypeExpression.parse(expr.getValue()); + if (expression == null) { + continue; + } + if (expressions == null) { + expressions = new LinkedHashSet<>(); + } + expressions.add(expression); + } + } + } + if (produces != null) { + for (String produce : produces) { + MediaTypeExpression expression = MediaTypeExpression.parse(produce); + if (expression == null) { + continue; + } + if (expressions == null) { + expressions = new LinkedHashSet<>(); + } + expressions.add(expression); + } + } + if (expressions == null) { + this.expressions = Collections.emptyList(); + } else { + this.expressions = new ArrayList<>(expressions); + Collections.sort(this.expressions); + } + } + + private ProducesCondition(List expressions) { + this.expressions = expressions; + } + + @Override + public ProducesCondition combine(ProducesCondition other) { + return other.expressions.isEmpty() ? this : other; + } + + @Override + public ProducesCondition match(HttpRequest request) { + if (expressions.isEmpty()) { + return null; + } + + List acceptedMediaTypes = getAcceptedMediaTypes(request); + List result = null; + for (int i = 0, size = expressions.size(); i < size; i++) { + MediaTypeExpression expression = expressions.get(i); + for (int j = 0, aSize = acceptedMediaTypes.size(); j < aSize; j++) { + if (expression.compatibleWith(acceptedMediaTypes.get(j))) { + if (result == null) { + result = new ArrayList<>(); + } + result.add(expression); + break; + } + } + } + return result == null ? null : new ProducesCondition(result); + } + + private List getAcceptedMediaTypes(HttpRequest request) { + List values = request.headerValues(HttpHeaderNames.ACCEPT.getName()); + if (CollectionUtils.isEmpty(values)) { + return MediaTypeExpression.ALL_LIST; + } + List mediaTypes = null; + for (int i = 0, size = values.size(); i < size; i++) { + String value = values.get(i); + if (StringUtils.isEmpty(value)) { + continue; + } + for (String item : StringUtils.tokenize(value, ',')) { + MediaTypeExpression expression = MediaTypeExpression.parse(item); + if (expression == null) { + continue; + } + if (mediaTypes == null) { + mediaTypes = new ArrayList<>(); + } + mediaTypes.add(expression); + } + } + if (mediaTypes == null) { + return Collections.emptyList(); + } + mediaTypes.sort(MediaTypeExpression.QUALITY_COMPARATOR.thenComparing(MediaTypeExpression.COMPARATOR)); + return mediaTypes; + } + + @Override + public int compareTo(ProducesCondition other, HttpRequest request) { + if (expressions.isEmpty() && other.expressions.isEmpty()) { + return 0; + } + List mediaTypes = getAcceptedMediaTypes(request); + for (int i = 0, size = mediaTypes.size(); i < size; i++) { + MediaTypeExpression mediaType = mediaTypes.get(i); + Pair thisPair, otherPair; + + thisPair = findMediaType(mediaType, MediaTypeExpression::typesEquals); + otherPair = findMediaType(mediaType, MediaTypeExpression::typesEquals); + int result = compareMediaType(thisPair, otherPair); + if (result != 0) { + return result; + } + + thisPair = findMediaType(mediaType, MediaTypeExpression::compatibleWith); + otherPair = findMediaType(mediaType, MediaTypeExpression::compatibleWith); + result = compareMediaType(thisPair, otherPair); + if (result != 0) { + return result; + } + } + return 0; + } + + public List getMediaTypes() { + List expressions = this.expressions; + int size = expressions.size(); + List mediaTypes = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + MediaTypeExpression expr = expressions.get(i); + mediaTypes.add(new MediaType(expr.getType(), expr.getSubType())); + } + return mediaTypes; + } + + @Override + public int hashCode() { + return expressions.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != ProducesCondition.class) { + return false; + } + return expressions.equals(((ProducesCondition) obj).expressions); + } + + private Pair findMediaType( + MediaTypeExpression mediaType, BiPredicate tester) { + List toCompare = expressions.isEmpty() ? MediaTypeExpression.ALL_LIST : expressions; + for (int i = 0; i < toCompare.size(); i++) { + MediaTypeExpression currentMediaType = toCompare.get(i); + if (tester.test(mediaType, currentMediaType)) { + return Pair.of(i, currentMediaType); + } + } + return Pair.of(-1, null); + } + + private int compareMediaType(Pair p1, Pair p2) { + int index1 = p1.getLeft(); + int index2 = p2.getLeft(); + if (index1 != index2) { + return index2 - index1; + } + return index1 != -1 ? p1.getRight().compareTo(p2.getRight()) : 0; + } + + @Override + public String toString() { + return "ProducesCondition{mediaTypes=" + expressions + '}'; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java new file mode 100644 index 00000000000..f00a26d57df --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java @@ -0,0 +1,87 @@ +/* + * 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.remoting.http12.HttpRequest; +import org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; + +import java.util.Objects; + +public final class ServiceVersionCondition implements Condition { + + private final String group; + private final String version; + + public ServiceVersionCondition(String group, String version) { + this.group = group; + this.version = version; + } + + @Override + public ServiceVersionCondition combine(ServiceVersionCondition other) { + return this; + } + + @Override + public ServiceVersionCondition match(HttpRequest request) { + String group = request.header(TripleHeaderEnum.SERVICE_GROUP.name()); + if (group == null) { + group = request.header(RestConstants.HEADER_SERVICE_GROUP); + } + if (group != null && !group.equals(this.group)) { + return null; + } + + String version = request.header(TripleHeaderEnum.SERVICE_VERSION.name()); + if (version == null) { + version = request.header(RestConstants.HEADER_SERVICE_VERSION); + } + + if (version != null && !version.equals(this.version)) { + return null; + } + return this; + } + + @Override + public int compareTo(ServiceVersionCondition other, HttpRequest request) { + return 0; + } + + @Override + public int hashCode() { + return Objects.hash(group, version); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != ServiceVersionCondition.class) { + return false; + } + ServiceVersionCondition other = (ServiceVersionCondition) obj; + return Objects.equals(group, other.group) && Objects.equals(version, other.version); + } + + @Override + public String toString() { + return "ServiceVersionCondition{" + "group='" + group + '\'' + ", version='" + version + '\'' + '}'; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationEnum.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationEnum.java new file mode 100644 index 00000000000..f6d52406cc9 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationEnum.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.mapping.meta; + +import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; + +import java.lang.annotation.Annotation; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public interface AnnotationEnum { + + String className(); + + Class type(); + + default Class loadType() { + try { + return (Class) TypeUtils.loadClass(className()); + } catch (Throwable t) { + return (Class) NotFound.class; + } + } + + default boolean isPresent() { + return type() != (Class) NotFound.class; + } + + @interface NotFound {} +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationMeta.java new file mode 100644 index 00000000000..8919222c911 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationMeta.java @@ -0,0 +1,233 @@ +/* + * 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.common.utils.CollectionUtils; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Array; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +@SuppressWarnings("unchecked") +public final class AnnotationMeta { + + private final Map> cache = CollectionUtils.newConcurrentHashMap(); + private final AnnotatedElement element; + private final A annotation; + private final RestToolKit toolKit; + + private Map attributes; + + public AnnotationMeta(AnnotatedElement element, A annotation, RestToolKit toolKit) { + this.element = element; + this.annotation = annotation; + this.toolKit = toolKit; + } + + public A getAnnotation() { + return annotation; + } + + public Class getAnnotationType() { + return annotation.annotationType(); + } + + public Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + Map map = toolKit.getAttributes(element, annotation); + if (CollectionUtils.isEmptyMap(map)) { + attributes = Collections.emptyMap(); + } else { + attributes = CollectionUtils.newHashMap(map.size()); + for (Map.Entry entry : map.entrySet()) { + Object value = entry.getValue(); + if (value instanceof String) { + value = toolKit.resolvePlaceholders((String) value); + } else if (value instanceof String[]) { + String[] array = (String[]) value; + for (int i = 0, len = array.length; i < len; i++) { + array[i] = toolKit.resolvePlaceholders(array[i]); + } + } + attributes.put(entry.getKey(), value); + } + } + this.attributes = attributes; + } + return attributes; + } + + public boolean hasAttribute(String attributeName) { + return getAttributes().containsKey(attributeName); + } + + public String getValue() { + return getString("value"); + } + + public String[] getValueArray() { + return getStringArray("value"); + } + + public String getString(String attributeName) { + return getRequiredAttribute(attributeName, String.class); + } + + public String[] getStringArray(String attributeName) { + return getRequiredAttribute(attributeName, String[].class); + } + + public boolean getBoolean(String attributeName) { + return getRequiredAttribute(attributeName, Boolean.class); + } + + public N getNumber(String attributeName) { + return (N) getRequiredAttribute(attributeName, Number.class); + } + + public > E getEnum(String attributeName) { + return (E) getRequiredAttribute(attributeName, Enum.class); + } + + public > E[] getEnumArray(String attributeName) { + return (E[]) getRequiredAttribute(attributeName, Enum[].class); + } + + public Class getClass(String attributeName) { + return getRequiredAttribute(attributeName, Class.class); + } + + public Class[] getClassArray(String attributeName) { + return getRequiredAttribute(attributeName, Class[].class); + } + + public AnnotationMeta getAnnotation(String attributeName) { + return (AnnotationMeta) cache.computeIfAbsent(attributeName, k -> { + if (getAttributes().get(attributeName) == null) { + return Optional.empty(); + } + Annotation annotation = getRequiredAttribute(attributeName, Annotation.class); + return Optional.of(new AnnotationMeta<>(getAnnotationType(), annotation, toolKit)); + }) + .orElseThrow(() -> attributeNotFound(attributeName)); + } + + public AnnotationMeta[] getAnnotationArray(String attributeName) { + return (AnnotationMeta[]) cache.computeIfAbsent(attributeName, k -> { + if (getAttributes().get(attributeName) == null) { + return Optional.empty(); + } + Annotation[] annotation = getRequiredAttribute(attributeName, Annotation[].class); + int len = annotation.length; + AnnotationMeta[] metas = new AnnotationMeta[len]; + for (int i = 0; i < len; i++) { + metas[i] = new AnnotationMeta<>(getAnnotationType(), (A1) annotation[i], toolKit); + } + return Optional.of(metas); + }) + .orElseThrow(() -> attributeNotFound(attributeName)); + } + + public A1 getAnnotation(String attributeName, Class annotationType) { + return getRequiredAttribute(attributeName, annotationType); + } + + public A1[] getAnnotationArray(String attributeName, Class annotationType) { + Class arrayType = Array.newInstance(annotationType, 0).getClass(); + return (A1[]) getRequiredAttribute(attributeName, arrayType); + } + + public T getRequiredAttribute(String attributeName, Class expectedType) { + Object value = getAttributes().get(attributeName); + if (value == null) { + throw attributeNotFound(attributeName); + } + if (value instanceof Throwable) { + throw new IllegalArgumentException( + String.format( + "Attribute '%s' for annotation [%s] was not resolvable due to exception [%s]", + attributeName, getAnnotationType().getName(), value), + (Throwable) value); + } + if (expectedType.isInstance(value)) { + return (T) value; + } + if (expectedType == String.class) { + return (T) value.toString(); + } + if (expectedType.isArray()) { + Class expectedComponentType = expectedType.getComponentType(); + if (expectedComponentType.isInstance(value)) { + Object array = Array.newInstance(expectedComponentType, 1); + Array.set(array, 0, value); + return (T) array; + } + if (expectedComponentType == String.class) { + String[] array; + if (value.getClass().isArray()) { + int len = Array.getLength(value); + array = new String[len]; + for (int i = 0; i < len; i++) { + array[i] = Array.get(value, i).toString(); + } + } else { + array = new String[] {value.toString()}; + } + return (T) array; + } + } + throw new IllegalArgumentException(String.format( + "Attribute '%s' is of type %s, but %s was expected in attributes for annotation [%s]", + attributeName, + value.getClass().getSimpleName(), + expectedType.getSimpleName(), + getAnnotationType().getName())); + } + + private IllegalArgumentException attributeNotFound(String attributeName) { + return new IllegalArgumentException(String.format( + "Attribute '%s' not found in attributes for annotation [%s]", + attributeName, getAnnotationType().getName())); + } + + @Override + public int hashCode() { + return 31 * element.hashCode() + annotation.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != AnnotationMeta.class) { + return false; + } + AnnotationMeta other = (AnnotationMeta) obj; + return element.equals(other.element) && annotation.equals(other.annotation); + } + + @Override + public String toString() { + return "AnnotationMeta{" + "element=" + element + ", annotation=" + annotation + '}'; + } +} 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 new file mode 100644 index 00000000000..393106e2d3d --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationSupport.java @@ -0,0 +1,216 @@ +/* + * 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.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.Pair; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public abstract class AnnotationSupport { + + private static final AnnotationMeta[] EMPTY = new AnnotationMeta[0]; + private static final Integer GET_KEY = 1; + private static final Integer GET_MERGED_KEY = 2; + private static final Integer FIND_KEY = 3; + private static final Integer FIND_MERGED_KEY = 4; + + private final Map, Optional> cache = CollectionUtils.newConcurrentHashMap(); + private final Map arrayCache = CollectionUtils.newConcurrentHashMap(); + private final RestToolKit toolKit; + + protected AnnotationSupport(RestToolKit toolKit) { + this.toolKit = toolKit; + } + + public final AnnotationMeta[] getAnnotations() { + return arrayCache.computeIfAbsent(GET_KEY, k -> { + AnnotatedElement element = getAnnotatedElement(); + Annotation[] annotations = element.getAnnotations(); + int len = annotations.length; + if (len == 0) { + return EMPTY; + } + AnnotationMeta[] metas = new AnnotationMeta[len]; + for (int i = 0; i < len; i++) { + metas[i] = new AnnotationMeta(element, annotations[i], toolKit); + } + return metas; + }); + } + + public final Annotation[] getRealAnnotations() { + AnnotationMeta[] annotations = getAnnotations(); + int len = annotations.length; + Annotation[] result = new Annotation[len]; + for (int i = 0; i < len; i++) { + result[i] = annotations[i].getAnnotation(); + } + return result; + } + + public final AnnotationMeta getAnnotation(Class annotationType) { + return cache.computeIfAbsent(Pair.of(annotationType, GET_KEY), k -> { + AnnotatedElement element = getAnnotatedElement(); + Annotation annotation = element.getAnnotation(annotationType); + if (annotation != null) { + return Optional.of(new AnnotationMeta(element, annotation, toolKit)); + } + return Optional.empty(); + }) + .orElse(null); + } + + public final AnnotationMeta getAnnotation(AnnotationEnum annotationEnum) { + return annotationEnum.isPresent() ? getAnnotation(annotationEnum.type()) : null; + } + + public final boolean isAnnotated(Class annotationType) { + return getAnnotation(annotationType) != null; + } + + public final boolean isAnnotated(AnnotationEnum annotationEnum) { + return getAnnotation(annotationEnum) != null; + } + + public final AnnotationMeta getMergedAnnotation(Class annotationType) { + return cache.computeIfAbsent(Pair.of(annotationType, GET_MERGED_KEY), k -> { + AnnotatedElement element = getAnnotatedElement(); + Annotation[] annotations = element.getAnnotations(); + for (Annotation annotation : annotations) { + if (annotation.annotationType() == annotationType) { + return Optional.of(new AnnotationMeta(element, annotation, toolKit)); + } + Annotation metaAnnotation = annotation.annotationType().getAnnotation(annotationType); + if (metaAnnotation != null) { + return Optional.of(new AnnotationMeta(element, metaAnnotation, toolKit)); + } + } + return Optional.empty(); + }) + .orElse(null); + } + + public final AnnotationMeta getMergedAnnotation(AnnotationEnum annotationEnum) { + return annotationEnum.isPresent() ? getMergedAnnotation(annotationEnum.type()) : null; + } + + public final boolean isMergedAnnotated(Class annotationType) { + return getMergedAnnotation(annotationType) != null; + } + + public final boolean isMergedAnnotated(AnnotationEnum annotationEnum) { + return getMergedAnnotation(annotationEnum) != null; + } + + public final AnnotationMeta[] findAnnotations() { + return arrayCache.computeIfAbsent(FIND_KEY, k -> { + List elements = getAnnotatedElements(); + List metas = new ArrayList<>(); + for (int i = 0, len = elements.size(); i < len; i++) { + AnnotatedElement element = elements.get(i); + Annotation[] annotations = element.getAnnotations(); + for (Annotation annotation : annotations) { + metas.add(new AnnotationMeta(element, annotation, toolKit)); + } + } + if (metas.isEmpty()) { + return EMPTY; + } + return metas.toArray(new AnnotationMeta[0]); + }); + } + + 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++) { + AnnotatedElement element = elements.get(i); + Annotation annotation = element.getDeclaredAnnotation(annotationType); + if (annotation != null) { + return Optional.of(new AnnotationMeta(element, annotation, toolKit)); + } + } + return Optional.empty(); + }) + .orElse(null); + } + + 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(AnnotationEnum annotationEnum) { + return getMergedAnnotation(annotationEnum) != null; + } + + 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++) { + AnnotatedElement element = elements.get(i); + Annotation[] annotations = element.getDeclaredAnnotations(); + for (Annotation annotation : annotations) { + if (annotation.annotationType() == annotationType) { + return Optional.of(new AnnotationMeta(element, annotation, toolKit)); + } + Annotation metaAnnotation = + annotation.annotationType().getAnnotation(annotationType); + if (metaAnnotation != null) { + return Optional.of(new AnnotationMeta(element, metaAnnotation, toolKit)); + } + } + } + return Optional.empty(); + }) + .orElse(null); + } + + public final AnnotationMeta findMergedAnnotation(AnnotationEnum annotationEnum) { + return annotationEnum.isPresent() ? findMergedAnnotation(annotationEnum.type()) : null; + } + + public final boolean isMergedHierarchyAnnotated(Class annotationType) { + return findMergedAnnotation(annotationType) != null; + } + + public final boolean isMergedHierarchyAnnotated(AnnotationEnum annotationEnum) { + return findMergedAnnotation(annotationEnum) != null; + } + + public final RestToolKit getToolKit() { + return toolKit; + } + + protected List getAnnotatedElements() { + return Collections.singletonList(getAnnotatedElement()); + } + + protected abstract AnnotatedElement getAnnotatedElement(); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/HandlerMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/HandlerMeta.java new file mode 100644 index 00000000000..a38d619781c --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/HandlerMeta.java @@ -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.mapping.meta; + +import org.apache.dubbo.remoting.http12.message.MethodMetadata; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.model.MethodDescriptor; +import org.apache.dubbo.rpc.model.ServiceDescriptor; + +public final class HandlerMeta { + + private final Invoker invoker; + private final MethodMeta method; + private final MethodMetadata methodMetadata; + private final MethodDescriptor methodDescriptor; + private final ServiceDescriptor serviceDescriptor; + + public HandlerMeta( + Invoker invoker, + MethodMeta method, + MethodMetadata methodMetadata, + MethodDescriptor methodDescriptor, + ServiceDescriptor serviceDescriptor) { + this.invoker = invoker; + this.method = method; + this.methodMetadata = methodMetadata; + this.methodDescriptor = methodDescriptor; + this.serviceDescriptor = serviceDescriptor; + } + + public Invoker getInvoker() { + return invoker; + } + + public MethodMeta getMethod() { + return method; + } + + public ServiceMeta getService() { + return method.getServiceMeta(); + } + + public ParameterMeta[] getParameters() { + return method.getParameters(); + } + + public MethodMetadata getMethodMetadata() { + return methodMetadata; + } + + public MethodDescriptor getMethodDescriptor() { + return methodDescriptor; + } + + public ServiceDescriptor getServiceDescriptor() { + return serviceDescriptor; + } +} 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 new file mode 100644 index 00000000000..93ea6b2faf5 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodMeta.java @@ -0,0 +1,122 @@ +/* + * 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 java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public final class MethodMeta extends AnnotationSupport { + + private final List hierarchy; + private final Method method; + private final 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) { + int count = method.getParameterCount(); + List> parameterHierarchies = new ArrayList<>(count); + for (int i = 0, len = hierarchy.size(); i < len; i++) { + Method m = hierarchy.get(i); + Parameter[] mps = m.getParameters(); + for (int j = 0; j < count; j++) { + List parameterHierarchy; + if (parameterHierarchies.size() <= j) { + parameterHierarchy = new ArrayList<>(len); + parameterHierarchies.add(parameterHierarchy); + } else { + parameterHierarchy = parameterHierarchies.get(j); + } + parameterHierarchy.add(mps[j]); + } + } + + String[] parameterNames = getToolKit().getParameterNames(method); + ParameterMeta[] parameters = new ParameterMeta[count]; + for (int i = 0; i < count; i++) { + String parameterName = parameterNames == null ? null : parameterNames[i]; + parameters[i] = new MethodParameterMeta(parameterHierarchies.get(i), parameterName, i, this); + } + return parameters; + } + + public List getHierarchy() { + return hierarchy; + } + + public Method getMethod() { + return method; + } + + public ParameterMeta[] getParameters() { + return parameters; + } + + public ServiceMeta getServiceMeta() { + return serviceMeta; + } + + public Class getReturnType() { + return method.getReturnType(); + } + + public Type getGenericReturnType() { + return method.getGenericReturnType(); + } + + @Override + protected List getAnnotatedElements() { + return hierarchy; + } + + @Override + protected AnnotatedElement getAnnotatedElement() { + return method; + } + + @Override + public int hashCode() { + return method.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != MethodMeta.class) { + return false; + } + return method.equals(((MethodMeta) obj).method); + } + + @Override + public String toString() { + return "MethodMeta{method=" + method + '}'; + } +} 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 new file mode 100644 index 00000000000..a6094410f8f --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodParameterMeta.java @@ -0,0 +1,106 @@ +/* + * 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 java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; +import java.util.List; + +public final class MethodParameterMeta extends ParameterMeta { + + private final List hierarchy; + private final Parameter parameter; + private final int index; + private final MethodMeta methodMeta; + + public MethodParameterMeta(List hierarchy, String name, int index, MethodMeta methodMeta) { + super(methodMeta.getToolKit(), name); + this.hierarchy = hierarchy; + parameter = hierarchy.get(0); + this.index = index; + this.methodMeta = methodMeta; + } + + public List getHierarchy() { + return hierarchy; + } + + public Parameter getParameter() { + return parameter; + } + + public int getIndex() { + return index; + } + + public MethodMeta getMethodMeta() { + return methodMeta; + } + + @Override + public Class getType() { + return parameter.getType(); + } + + @Override + public Type getGenericType() { + return parameter.getParameterizedType(); + } + + @Override + public String getDescription() { + return "parameter [" + getMethod() + "] in {" + index + "}"; + } + + public Method getMethod() { + return methodMeta.getMethod(); + } + + @Override + protected AnnotatedElement getAnnotatedElement() { + return parameter; + } + + @Override + protected List getAnnotatedElements() { + return hierarchy; + } + + @Override + public int hashCode() { + return 31 * getMethod().hashCode() + index; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != MethodParameterMeta.class) { + return false; + } + MethodParameterMeta other = (MethodParameterMeta) obj; + return getMethod().equals(other.getMethod()) && index == other.index; + } + + @Override + public String toString() { + return "MethodParameterMeta{parameter=" + parameter + '}'; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java new file mode 100644 index 00000000000..04c77875b8e --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta; + +import java.util.Arrays; + +public class NamedValueMeta { + + private String name; + private final boolean required; + private final String defaultValue; + private Class type; + private Class[] nestedTypes; + private ParameterMeta parameterMeta; + + public NamedValueMeta(String name, boolean required, String defaultValue) { + this.name = name; + this.required = required; + this.defaultValue = defaultValue; + } + + public NamedValueMeta(boolean required, String defaultValue) { + name = null; + this.required = required; + this.defaultValue = defaultValue; + } + + public String name() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean required() { + return required; + } + + public String defaultValue() { + return defaultValue; + } + + public Class type() { + return type; + } + + public void setType(Class type) { + this.type = type; + } + + public Class nestedType() { + return nestedTypes == null ? null : nestedTypes[0]; + } + + public Class nestedType(int index) { + return nestedTypes == null || nestedTypes.length <= index ? null : nestedTypes[index]; + } + + public Class[] nestedTypes() { + return nestedTypes; + } + + public void setNestedTypes(Class[] nestedTypes) { + this.nestedTypes = nestedTypes; + } + + public ParameterMeta parameterMeta() { + return parameterMeta; + } + + public void setParameterMeta(ParameterMeta parameterMeta) { + this.parameterMeta = parameterMeta; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("NamedValueMeta{name='"); + sb.append(name).append('\''); + if (required) { + sb.append(", required=true"); + } + if (defaultValue != null) { + sb.append(", defaultValue='").append(defaultValue).append('\''); + } + if (type != null) { + sb.append(", type=").append(type); + } + if (nestedTypes != null) { + sb.append(", nestedTypes=").append(Arrays.toString(nestedTypes)); + } + sb.append('}'); + return sb.toString(); + } +} 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 new file mode 100644 index 00000000000..32779b34e06 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java @@ -0,0 +1,116 @@ +/* + * 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.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; +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.Type; +import java.util.Optional; + +public abstract class ParameterMeta extends AnnotationSupport { + + private final String prefix; + private final String name; + private Boolean simple; + private Class actualType; + private Object typeDescriptor; + + protected ParameterMeta(RestToolKit toolKit, String prefix, String name) { + super(toolKit); + this.prefix = StringUtils.isEmpty(prefix) ? null : prefix; + this.name = name; + } + + protected ParameterMeta(RestToolKit toolKit, String name) { + super(toolKit); + prefix = null; + this.name = name; + } + + public String getPrefix() { + return prefix; + } + + @Nullable + public final String getName() { + return name; + } + + public final String getRequiredName() { + if (name == null) { + throw new RestException(Messages.ARGUMENT_NAME_MISSING, getType()); + } + return name; + } + + public final boolean isSimple() { + Boolean simple = this.simple; + if (simple == null) { + simple = TypeUtils.isSimpleProperty(getActualType()); + this.simple = simple; + } + return simple; + } + + public final Class getActualType() { + Class type = actualType; + if (type == null) { + type = getType(); + if (type == Optional.class) { + type = TypeUtils.getNestedType(getGenericType(), 0); + if (type == null) { + type = Object.class; + } + } + actualType = type; + } + return type; + } + + public final Object getTypeDescriptor() { + return typeDescriptor; + } + + public final void setTypeDescriptor(Object typeDescriptor) { + this.typeDescriptor = typeDescriptor; + } + + public final Object bind(HttpRequest request, HttpResponse response) { + return getToolKit().bind(this, request, response); + } + + public String getDescription() { + return name; + } + + public abstract Class getType(); + + public abstract Type getGenericType(); + + @Override + public String toString() { + return "ParameterMeta{name='" + name + "', type=" + getType() + '}'; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ResponseMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ResponseMeta.java new file mode 100644 index 00000000000..18a1bf346f1 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ResponseMeta.java @@ -0,0 +1,63 @@ +/* + * 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.common.utils.StringUtils; + +public class ResponseMeta { + + private final Integer status; + private final String reason; + + public ResponseMeta(Integer status, String reason) { + this.status = status; + this.reason = reason; + } + + public Integer getStatus() { + return status; + } + + public String getReason() { + return reason; + } + + public static ResponseMeta combine(ResponseMeta self, ResponseMeta other) { + if (self == null) { + return other; + } + if (other == null) { + return self; + } + Integer status = other.getStatus() == null ? self.status : other.getStatus(); + String reason = other.getReason() == null ? self.reason : other.getReason(); + return new ResponseMeta(status, reason); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("ResponseMeta{"); + if (status != null) { + sb.append("status=").append(status); + } + if (StringUtils.isNotEmpty(reason)) { + sb.append(", reason='").append(reason).append('\''); + } + sb.append('}'); + return sb.toString(); + } +} 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 new file mode 100644 index 00000000000..f9f68ca6f50 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ServiceMeta.java @@ -0,0 +1,104 @@ +/* + * 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.common.URL; +import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils; +import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit; + +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public final class ServiceMeta extends AnnotationSupport { + + private final List> hierarchy; + private final Class type; + private final Object service; + private final URL url; + private final String contextPath; + + private List exceptionHandlers; + + public ServiceMeta(Collection> hierarchy, Object service, URL url, RestToolKit toolKit) { + super(toolKit); + this.hierarchy = new ArrayList<>(hierarchy); + type = this.hierarchy.get(0); + this.service = service; + this.url = url; + contextPath = PathUtils.getContextPath(url); + } + + public List> getHierarchy() { + return hierarchy; + } + + public Class getType() { + return type; + } + + public Object getService() { + return service; + } + + public URL getUrl() { + return url; + } + + public String getServiceInterface() { + return url.getServiceInterface(); + } + + public String getServiceGroup() { + return url.getGroup(); + } + + public String getServiceVersion() { + return url.getVersion(); + } + + public String getContextPath() { + return contextPath; + } + + public List getExceptionHandlers() { + return exceptionHandlers; + } + + @Override + protected List getAnnotatedElements() { + return hierarchy; + } + + @Override + protected AnnotatedElement getAnnotatedElement() { + return hierarchy.get(0); + } + + @Override + public String toString() { + return "ServiceMeta{class='" + getType().getName() + "', service=" + service + '}'; + } + + public void addExceptionHandler(MethodMeta methodMeta) { + if (exceptionHandlers == null) { + exceptionHandlers = new ArrayList<>(); + } + exceptionHandlers.add(methodMeta); + } +} 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 new file mode 100644 index 00000000000..0d5a2a03f79 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/DefaultRestToolKit.java @@ -0,0 +1,87 @@ +/* + * 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.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.argument.GeneralTypeConverter; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Map; + +public class DefaultRestToolKit implements RestToolKit { + + protected final FrameworkModel frameworkModel; + protected final TypeConverter typeConverter; + + public DefaultRestToolKit(FrameworkModel frameworkModel) { + this.frameworkModel = frameworkModel; + typeConverter = frameworkModel.getBeanFactory().getOrRegisterBean(GeneralTypeConverter.class); + } + + @Override + public int getDialect() { + return 0; + } + + @Override + public String resolvePlaceholders(String text) { + return RestUtils.hasPlaceholder(text) ? getEnvironment().resolvePlaceholders(text) : text; + } + + private Environment getEnvironment() { + return frameworkModel.defaultApplication().modelEnvironment(); + } + + @Override + 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(); + int len = parameters.length; + String[] names = new String[len]; + for (int i = 0; i < len; i++) { + Parameter param = parameters[i]; + if (!param.isNamePresent()) { + return null; + } + names[i] = param.getName(); + } + return names; + } + + @Override + public Map getAttributes(AnnotatedElement element, Annotation annotation) { + return AnnotationUtils.getAttributes(annotation, false); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java new file mode 100644 index 00000000000..61413c0633c --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java @@ -0,0 +1,108 @@ +/* + * 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 java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public final class MethodWalker { + + private final Set> classes = new LinkedHashSet<>(); + private final Map> methodsMap = new HashMap<>(); + + public void walk(Class clazz, BiConsumer>, Consumer>>> visitor) { + if (clazz.getName().contains("$$")) { + clazz = clazz.getSuperclass(); + } + + walkHierarchy(clazz); + + visitor.accept(classes, consumer -> { + for (Map.Entry> entry : methodsMap.entrySet()) { + consumer.accept(entry.getValue()); + } + }); + } + + private void walkHierarchy(Class clazz) { + if (classes.isEmpty() || clazz.getDeclaredAnnotations().length > 0) { + classes.add(clazz); + } + for (Method method : clazz.getDeclaredMethods()) { + int modifiers = method.getModifiers(); + if ((modifiers & (Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC) { + if (Modifier.isAbstract(modifiers) && method.getDeclaredAnnotations().length == 0) { + continue; + } + methodsMap + .computeIfAbsent(Key.of(method), k -> new ArrayList<>()) + .add(method); + } + } + Class superClass = clazz.getSuperclass(); + if (superClass != null && superClass != Object.class) { + walkHierarchy(superClass); + } + for (Class itf : clazz.getInterfaces()) { + walkHierarchy(itf); + } + } + + private static final class Key { + private final String name; + private final Class[] parameterTypes; + + private Key(String name, Class[] parameterTypes) { + this.name = name; + this.parameterTypes = parameterTypes; + } + + private static Key of(Method method) { + return new Key(method.getName(), method.getParameterTypes()); + } + + @Override + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + public boolean equals(Object obj) { + Key key = (Key) obj; + return name.equals(key.name) && Arrays.equals(parameterTypes, key.parameterTypes); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + for (Class type : parameterTypes) { + result = 31 * result + type.hashCode(); + } + return result; + } + + @Override + public String toString() { + return name + Arrays.toString(parameterTypes); + } + } +} 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 new file mode 100644 index 00000000000..e6a6bf51c09 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtils.java @@ -0,0 +1,260 @@ +/* + * 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.common.constants.CommonConstants; +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.RestConstants; + +import javax.annotation.Nonnull; + +public final class PathUtils { + private PathUtils() {} + + public static String getContextPath(URL url) { + String path = url.getPath(); + if (path == null) { + return StringUtils.EMPTY_STRING; + } + int len = path.length(); + if (len == 0) { + return StringUtils.EMPTY_STRING; + } + String ifName = url.getParameter(CommonConstants.INTERFACE_KEY); + if (ifName == null || path.equalsIgnoreCase(ifName)) { + return StringUtils.EMPTY_STRING; + } + int index = path.lastIndexOf(ifName); + if (index + ifName.length() == len) { + return path.substring(0, index - 1); + } + return path.charAt(len - 1) == '/' ? path.substring(0, len - 1) : path; + } + + public static boolean isDirectPath(@Nonnull String path) { + boolean braceStart = false; + for (int i = 0, len = path.length(); i < len; i++) { + switch (path.charAt(i)) { + case '*': + case '?': + return false; + case '{': + braceStart = true; + continue; + case '}': + if (braceStart) { + return false; + } + break; + default: + } + } + return true; + } + + /** + * See + * AntPathMatcher#combine + */ + public static String combine(@Nonnull String path1, @Nonnull String path2) { + if (path1.isEmpty()) { + return path2.isEmpty() ? StringUtils.EMPTY_STRING : path2; + } + if (path2.isEmpty()) { + return path1; + } + + int len1 = path1.length(); + char last1 = path1.charAt(len1 - 1); + if (len1 == 1) { + if (last1 == '/' || last1 == '*') { + return path2; + } + } else if (path1.indexOf('{') == -1) { + int starDotPos1 = path1.lastIndexOf("*."); + if (starDotPos1 > -1) { + String ext1 = path1.substring(starDotPos1 + 1); + int dotPos2 = path2.lastIndexOf('.'); + String file2, ext2; + if (dotPos2 == -1) { + file2 = path2; + ext2 = StringUtils.EMPTY_STRING; + } else { + file2 = path2.substring(0, dotPos2); + ext2 = path2.substring(dotPos2); + } + boolean ext1All = ext1.equals(".*"); + boolean ext2All = ext2.equals(".*") || ext2.isEmpty(); + if (!ext1All && !ext2All) { + throw new PathParserException(Messages.CANNOT_COMBINE_PATHS, path1, path2); + } + return file2 + (ext1All ? ext2 : ext1); + } + } + if (last1 == '*' && path1.charAt(len1 - 2) == '/') { + path1 = path1.substring(0, len1 - 2); + } + + boolean slash1 = last1 == '/'; + boolean slash2 = path2.charAt(0) == '/'; + if (slash1) { + return slash2 ? path1 + path2.substring(1) : path1 + path2; + } + if (slash2) { + return path1 + path2; + } + return path1 + '/' + path2; + } + + public static String normalize(String contextPath, String path) { + if (StringUtils.isEmpty(contextPath)) { + return StringUtils.isEmpty(path) ? RestConstants.SLASH : normalize(path); + } + if (StringUtils.isEmpty(path)) { + return normalize(contextPath); + } + if (path.charAt(0) != '/') { + contextPath += '/'; + } + return normalize(contextPath + path); + } + + public static String normalize(@Nonnull String path) { + int len = path.length(); + if (len == 0) { + return RestConstants.SLASH; + } + int state = State.INITIAL; + int start = -1, end = 0; + StringBuilder buf = null; + out: + for (int i = 0; i < len; i++) { + char c = path.charAt(i); + switch (c) { + case ' ': + case '\t': + case '\n': + case '\r': + continue; + case '/': + switch (state) { + case State.SLASH: + if (start != -1) { + if (buf == null) { + buf = new StringBuilder(len); + } + buf.append(path, start, i); + } + start = -1; + continue; + case State.DOT: + state = State.SLASH; + if (buf == null) { + buf = new StringBuilder(len); + } + buf.append(path, start, i - 1); + start = -1; + continue; + case State.DOT_DOT: + state = State.SLASH; + if (buf == null) { + buf = new StringBuilder(len); + } + if (end > 2) { + buf.append(path, start, i - 2); + int bLen = buf.length(); + if (bLen > 1) { + int index = buf.lastIndexOf(RestConstants.SLASH, bLen - 2); + if (!"/../".equals(buf.substring(index))) { + buf.setLength(index + 1); + start = -1; + continue; + } + } + } + buf.append(path, start, i + 1); + start = -1; + continue; + default: + state = State.SLASH; + break; + } + break; + case '.': + switch (state) { + case State.INITIAL: + if (buf == null) { + buf = new StringBuilder(len); + } + buf.append('/'); + case State.SLASH: + state = State.DOT; + break; + case State.DOT: + state = State.DOT_DOT; + break; + case State.DOT_DOT: + state = State.NORMAL; + break; + default: + } + break; + case '?': + case '#': + break out; + default: + switch (state) { + case State.INITIAL: + if (buf == null) { + buf = new StringBuilder(len); + } + buf.append('/'); + case State.SLASH: + case State.DOT: + case State.DOT_DOT: + state = State.NORMAL; + break; + default: + } + break; + } + if (start == -1) { + start = i; + } + end = i; + } + if (buf == null) { + return path; + } + if (start != -1) { + buf.append(path, start, end + 1); + } + return buf.toString(); + } + + private interface State { + + int INITIAL = 0; + int NORMAL = 1; + int SLASH = 2; + int DOT = 3; + int DOT_DOT = 4; + } +} 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 new file mode 100644 index 00000000000..a91fbb3289f --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java @@ -0,0 +1,179 @@ +/* + * 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.utils.CollectionUtils; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.exception.EncodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +public final class RequestUtils { + + private RequestUtils() {} + + public static boolean isFormOrMultiPart(HttpRequest request) { + String contentType = request.contentType(); + if (contentType == null) { + return false; + } + return contentType.startsWith(MediaType.APPLICATION_FROM_URLENCODED.getName()) + || contentType.startsWith(MediaType.MULTIPART_FORM_DATA.getName()); + } + + public static String decodeURL(String value) { + try { + return URLDecoder.decode(value, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new DecodeException(e); + } + } + + public static String encodeURL(String value) { + try { + return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new EncodeException(e); + } + } + + public static Map> getParametersMap(HttpRequest request) { + Collection paramNames = request.parameterNames(); + if (paramNames.isEmpty()) { + return Collections.emptyMap(); + } + Map> params = CollectionUtils.newLinkedHashMap(paramNames.size()); + for (String paramName : paramNames) { + params.put(paramName, request.parameterValues(paramName)); + } + return params; + } + + public static Map> getFormParametersMap(HttpRequest request) { + Collection paramNames = request.formParameterNames(); + if (paramNames.isEmpty()) { + return Collections.emptyMap(); + } + Map> params = CollectionUtils.newLinkedHashMap(paramNames.size()); + for (String paramName : paramNames) { + params.put(paramName, request.formParameterValues(paramName)); + } + return params; + } + + public static Map getParametersMapStartingWith(HttpRequest request, String prefix) { + Collection paramNames = request.parameterNames(); + if (paramNames.isEmpty()) { + return Collections.emptyMap(); + } + if (prefix == null) { + prefix = StringUtils.EMPTY_STRING; + } + Map params = CollectionUtils.newLinkedHashMap(paramNames.size()); + for (String paramName : paramNames) { + if (prefix.isEmpty() || paramName.startsWith(prefix)) { + String name = paramName.substring(prefix.length()); + List values = request.parameterValues(paramName); + if (CollectionUtils.isEmpty(values)) { + continue; + } + params.put(name, values.size() == 1 ? values.get(0) : values); + } + } + return params; + } + + public static Map> parseMatrixVariables(String matrixVariables) { + Map> result = null; + StringTokenizer pairs = new StringTokenizer(matrixVariables, ";"); + while (pairs.hasMoreTokens()) { + String pair = pairs.nextToken(); + int index = pair.indexOf('='); + if (index == -1) { + if (result == null) { + result = new LinkedHashMap<>(); + } + result.computeIfAbsent(pair, k -> new ArrayList<>()).add(StringUtils.EMPTY_STRING); + continue; + } + String name = pair.substring(0, index); + if ("jsessionid".equalsIgnoreCase(name)) { + continue; + } + if (result == null) { + result = new LinkedHashMap<>(); + } + for (String value : StringUtils.tokenize(pair.substring(index + 1), ',')) { + result.computeIfAbsent(name, k -> new ArrayList<>()).add(decodeURL(value)); + } + } + return result; + } + + public static List parseMatrixVariableValues(Map variableMap, String name) { + if (variableMap == null) { + return Collections.emptyList(); + } + List result = null; + for (Map.Entry entry : variableMap.entrySet()) { + Map> matrixVariables = parseMatrixVariables(entry.getValue()); + if (matrixVariables == null) { + continue; + } + List values = matrixVariables.get(name); + if (values == null) { + continue; + } + if (result == null) { + result = new ArrayList<>(); + } + result.addAll(values); + } + return result == null ? Collections.emptyList() : result; + } + + public static Object decodeBody(HttpRequest request, Class type) { + HttpMessageDecoder decoder = request.attribute(RestConstants.BODY_DECODER_ATTRIBUTE); + if (decoder == null) { + return null; + } + Object value = request.attribute(RestConstants.BODY_ATTRIBUTE); + if (value == null) { + if (decoder.mediaType() == MediaType.TEXT_PLAIN) { + type = String.class; + } + 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/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestToolKit.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestToolKit.java new file mode 100644 index 00000000000..2ed7c511fbd --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestToolKit.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.util; + +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta; + +import javax.annotation.Nullable; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Map; + +public interface RestToolKit { + + int getDialect(); + + String resolvePlaceholders(String text); + + Object convert(Object value, ParameterMeta parameter); + + Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response); + + @Nullable + String[] getParameterNames(Method method); + + Map getAttributes(AnnotatedElement element, Annotation annotation); +} 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 new file mode 100644 index 00000000000..a5e5331dc58 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestUtils.java @@ -0,0 +1,67 @@ +/* + * 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.extension.Activate; +import org.apache.dubbo.common.lang.Prioritized; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension; + +public final class RestUtils { + private RestUtils() {} + + public static boolean hasPlaceholder(String text) { + if (text == null) { + return false; + } + int len = text.length(); + if (len < 4) { + return false; + } + int state = 0; + for (int i = 0; i < len; i++) { + char c = text.charAt(i); + if (c == '$') { + state = 1; + } else if (c == '{') { + if (state == 1) { + if (text.charAt(i + 1) != '$') { + return false; + } + state = 2; + } + } else if (c == '}' && state == 2) { + return true; + } + } + return false; + } + + public static int getPriority(Object obj) { + if (obj instanceof Prioritized) { + int priority = ((Prioritized) obj).getPriority(); + if (priority != 0) { + return priority; + } + } + Activate activate = obj.getClass().getAnnotation(Activate.class); + return activate == null ? 0 : activate.order(); + } + + public static String[] getPattens(Object obj) { + return obj instanceof RestExtension ? ((RestExtension) obj).getPatterns() : null; + } +} 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 new file mode 100644 index 00000000000..1e6181795da --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java @@ -0,0 +1,218 @@ +/* + * 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.utils.ArrayUtils; +import org.apache.dubbo.common.utils.ClassUtils; +import org.apache.dubbo.common.utils.ConcurrentHashSet; + +import java.io.File; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.time.ZoneId; +import java.time.temporal.Temporal; +import java.util.Collections; +import java.util.Currency; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TimeZone; +import java.util.UUID; +import java.util.regex.Pattern; + +public final class TypeUtils { + private static final Set> SIMPLE_TYPES = new ConcurrentHashSet<>(); + + static { + Collections.addAll( + SIMPLE_TYPES, + Void.class, + void.class, + String.class, + URI.class, + URL.class, + UUID.class, + Locale.class, + Currency.class, + Pattern.class, + Class.class); + } + + private TypeUtils() {} + + public static ClassLoader getDefaultClassLoader() { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + return cl == null ? RestUtils.class.getClassLoader() : cl; + } + + public static Class loadClass(String className) throws ClassNotFoundException { + return getDefaultClassLoader().loadClass(className); + } + + public static boolean isPresent(String className) { + try { + loadClass(className); + return true; + } catch (Throwable ignored) { + return false; + } + } + + public static boolean isSimpleProperty(Class type) { + return type == null || isSimpleValueType(type) || type.isArray() && isSimpleValueType(type.getComponentType()); + } + + private static boolean isSimpleValueType(Class type) { + if (type.isPrimitive() || ClassUtils.isPrimitiveWrapper(type)) { + return true; + } + if (SIMPLE_TYPES.contains(type)) { + return true; + } + if (Enum.class.isAssignableFrom(type) + || CharSequence.class.isAssignableFrom(type) + || Number.class.isAssignableFrom(type) + || Date.class.isAssignableFrom(type) + || Temporal.class.isAssignableFrom(type) + || ZoneId.class.isAssignableFrom(type) + || TimeZone.class.isAssignableFrom(type) + || File.class.isAssignableFrom(type) + || Path.class.isAssignableFrom(type) + || Charset.class.isAssignableFrom(type) + || InetAddress.class.isAssignableFrom(type)) { + SIMPLE_TYPES.add(type); + return true; + } + return false; + } + + public static Class getMapValueType(Class targetClass) { + for (Type gi : targetClass.getGenericInterfaces()) { + if (gi instanceof ParameterizedType) { + ParameterizedType type = (ParameterizedType) gi; + if (type.getRawType() == Map.class) { + return getActualType(type.getActualTypeArguments()[1]); + } + } + } + return null; + } + + public static Class getSuperGenericType(Class clazz, int index) { + Class result = getNestedType(clazz.getGenericSuperclass(), index); + return result == null ? getNestedType(ArrayUtils.first(clazz.getGenericInterfaces()), index) : result; + } + + public static Class getSuperGenericType(Class clazz) { + return getSuperGenericType(clazz, 0); + } + + public static Class[] getNestedTypes(Type type) { + if (type instanceof ParameterizedType) { + Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments(); + int len = typeArgs.length; + Class[] nestedTypes = new Class[len]; + for (int i = 0; i < len; i++) { + nestedTypes[i] = getActualType(typeArgs[i]); + } + return nestedTypes; + } + return null; + } + + public static Class getNestedType(Type type, int index) { + if (type instanceof ParameterizedType) { + Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments(); + if (index < typeArgs.length) { + return getActualType(typeArgs[index]); + } + } + return null; + } + + public static Class getActualType(Type type) { + if (type instanceof Class) { + return (Class) type; + } + if (type instanceof ParameterizedType) { + return getActualType(((ParameterizedType) type).getRawType()); + } + if (type instanceof TypeVariable) { + return getActualType(((TypeVariable) type).getBounds()[0]); + } + if (type instanceof WildcardType) { + return getActualType(((WildcardType) type).getUpperBounds()[0]); + } + if (type instanceof GenericArrayType) { + Type componentType = ((GenericArrayType) type).getGenericComponentType(); + return Array.newInstance(getActualType(componentType), 0).getClass(); + } + return null; + } + + public static Type getActualGenericType(Type type) { + if (type instanceof TypeVariable) { + return ((TypeVariable) type).getBounds()[0]; + } + if (type instanceof WildcardType) { + return ((WildcardType) type).getUpperBounds()[0]; + } + return type; + } + + public static Object nullDefault(Class targetClass) { + if (targetClass == long.class) { + return 0L; + } + if (targetClass == int.class) { + return 0; + } + if (targetClass == boolean.class) { + return Boolean.FALSE; + } + if (targetClass == double.class) { + return 0D; + } + if (targetClass == float.class) { + return 0F; + } + if (targetClass == byte.class) { + return (byte) 0; + } + if (targetClass == short.class) { + return (short) 0; + } + if (targetClass == char.class) { + return (char) 0; + } + if (targetClass == Optional.class) { + return Optional.empty(); + } + return null; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java new file mode 100644 index 00000000000..f93c667a336 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/DefaultRequestRouter.java @@ -0,0 +1,62 @@ +/* + * 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.route; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.http12.HttpChannel; +import org.apache.dubbo.remoting.http12.HttpMetadata; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; +import org.apache.dubbo.remoting.http12.RequestMetadata; +import org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.RpcInvocationBuildContext; +import org.apache.dubbo.rpc.protocol.tri.TripleConstant; + +import java.util.List; + +public final class DefaultRequestRouter implements RequestRouter { + + private final HttpMessageAdapterFactory httpMessageAdapterFactory; + private final List requestHandlerMappings; + + @SuppressWarnings("unchecked") + public DefaultRequestRouter(FrameworkModel frameworkModel) { + httpMessageAdapterFactory = frameworkModel.getFirstActivateExtension(HttpMessageAdapterFactory.class); + requestHandlerMappings = frameworkModel.getActivateExtensions(RequestHandlerMapping.class); + } + + @Override + public RpcInvocationBuildContext route(URL url, RequestMetadata metadata, HttpChannel httpChannel) { + HttpRequest request = httpMessageAdapterFactory.adaptRequest(metadata, httpChannel); + HttpResponse response = httpMessageAdapterFactory.adaptResponse(request, metadata); + + for (int i = 0, size = requestHandlerMappings.size(); i < size; i++) { + RequestHandlerMapping mapping = requestHandlerMappings.get(i); + RequestHandler handler = mapping.getRequestHandler(url, request, response); + if (handler == null) { + continue; + } + handler.setAttribute(TripleConstant.HANDLER_TYPE_KEY, mapping.getType()); + handler.setAttribute(TripleConstant.HTTP_REQUEST_KEY, request); + handler.setAttribute(TripleConstant.HTTP_RESPONSE_KEY, response); + return handler; + } + + return null; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestHandler.java new file mode 100644 index 00000000000..bc23bb5530e --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestHandler.java @@ -0,0 +1,128 @@ +/* + * 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.route; + +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; +import org.apache.dubbo.remoting.http12.message.MethodMetadata; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.model.MethodDescriptor; +import org.apache.dubbo.rpc.model.ServiceDescriptor; +import org.apache.dubbo.rpc.protocol.tri.RpcInvocationBuildContext; + +import java.util.HashMap; +import java.util.Map; + +public final class RequestHandler implements RpcInvocationBuildContext { + + private final Invoker invoker; + private boolean hasStub; + private String methodName; + private MethodDescriptor methodDescriptor; + private MethodMetadata methodMetadata; + private ServiceDescriptor serviceDescriptor; + private HttpMessageDecoder httpMessageDecoder; + private HttpMessageEncoder httpMessageEncoder; + private Map attributes = new HashMap<>(); + + public RequestHandler(Invoker invoker) { + this.invoker = invoker; + } + + @Override + public Invoker getInvoker() { + return invoker; + } + + @Override + public boolean isHasStub() { + return hasStub; + } + + public void setHasStub(boolean hasStub) { + this.hasStub = hasStub; + } + + @Override + public String getMethodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + @Override + public MethodDescriptor getMethodDescriptor() { + return methodDescriptor; + } + + @Override + public MethodMetadata getMethodMetadata() { + return methodMetadata; + } + + @Override + public void setMethodMetadata(MethodMetadata methodMetadata) { + this.methodMetadata = methodMetadata; + } + + @Override + public void setMethodDescriptor(MethodDescriptor methodDescriptor) { + this.methodDescriptor = methodDescriptor; + } + + @Override + public ServiceDescriptor getServiceDescriptor() { + return serviceDescriptor; + } + + public void setServiceDescriptor(ServiceDescriptor serviceDescriptor) { + this.serviceDescriptor = serviceDescriptor; + } + + @Override + public HttpMessageDecoder getHttpMessageDecoder() { + return httpMessageDecoder; + } + + public void setHttpMessageDecoder(HttpMessageDecoder httpMessageDecoder) { + this.httpMessageDecoder = httpMessageDecoder; + } + + @Override + public HttpMessageEncoder getHttpMessageEncoder() { + return httpMessageEncoder; + } + + public void setHttpMessageEncoder(HttpMessageEncoder httpMessageEncoder) { + this.httpMessageEncoder = httpMessageEncoder; + } + + @Override + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public void setAttribute(String key, Object value) { + attributes.put(key, value); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestHandlerMapping.java new file mode 100644 index 00000000000..aefddef39a4 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestHandlerMapping.java @@ -0,0 +1,31 @@ +/* + * 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.route; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.remoting.http12.HttpRequest; +import org.apache.dubbo.remoting.http12.HttpResponse; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface RequestHandlerMapping { + + RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response); + + String getType(); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestRouter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestRouter.java new file mode 100644 index 00000000000..99eae00b503 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/route/RequestRouter.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.route; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.http12.HttpChannel; +import org.apache.dubbo.remoting.http12.RequestMetadata; +import org.apache.dubbo.rpc.protocol.tri.RpcInvocationBuildContext; + +public interface RequestRouter { + + RpcInvocationBuildContext route(URL url, RequestMetadata metadata, HttpChannel httpChannel); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java index b45a40ef069..2cd7c4c5646 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/service/TriBuiltinService.java @@ -34,8 +34,8 @@ import io.grpc.health.v1.Health; import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE; +import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_BUILTIN_SERVICE_INIT; import static org.apache.dubbo.rpc.Constants.PROXY_KEY; -import static org.apache.dubbo.rpc.Constants.TRI_BUILTIN_SERVICE_INIT; /** * tri internal service like grpc internal service @@ -77,7 +77,7 @@ public void init() { } public boolean enable() { - return config.getBoolean(TRI_BUILTIN_SERVICE_INIT, false); + return config.getBoolean(H2_SETTINGS_BUILTIN_SERVICE_INIT, false); } private void addSingleBuiltinService(String serviceName, T impl, Class interfaceClass) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/StreamUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/StreamUtils.java index b1ec7306a00..3258a3f3474 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/StreamUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/StreamUtils.java @@ -18,6 +18,7 @@ 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.JsonUtils; import org.apache.dubbo.common.utils.LRU2Cache; import org.apache.dubbo.remoting.http12.HttpHeaders; @@ -28,17 +29,22 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collections; +import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.BiConsumer; +import io.netty.handler.codec.DateFormatter; import io.netty.handler.codec.http2.DefaultHttp2Headers; +import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_PARSE; import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_UNSUPPORTED; -public class StreamUtils { +public final class StreamUtils { - protected static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(StreamUtils.class); + private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(StreamUtils.class); private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder(); private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder().withoutPadding(); @@ -47,163 +53,188 @@ public class StreamUtils { private static final Map lruHeaderMap = new LRU2Cache<>(MAX_LRU_HEADER_MAP_SIZE); + private StreamUtils() {} + public static String encodeBase64ASCII(byte[] in) { - byte[] bytes = encodeBase64(in); - return new String(bytes, StandardCharsets.US_ASCII); + return new String(encodeBase64(in), StandardCharsets.US_ASCII); } public static byte[] encodeBase64(byte[] in) { return BASE64_ENCODER.encode(in); } - public static byte[] decodeASCIIByte(CharSequence value) { - return BASE64_DECODER.decode(value.toString().getBytes(StandardCharsets.US_ASCII)); + public static byte[] decodeASCIIByte(String value) { + return BASE64_DECODER.decode(value.getBytes(StandardCharsets.US_ASCII)); } - public static Map toAttachments(Map origin) { - if (origin == null || origin.isEmpty()) { - return Collections.emptyMap(); - } - Map res = new HashMap<>(origin.size()); - origin.forEach((k, v) -> { - if (TripleHeaderEnum.containsExcludeAttachments(k)) { - return; - } - res.put(k, v); - }); - return res; + /** + * Parse and put attachments into headers. + * Ignore Http2 PseudoHeaderName and internal name. + * Only strings, dates, and byte arrays are allowed. + * + * @param headers the headers + * @param attachments the attachments + * @param needConvertHeaderKey whether need to convert the header key to lower-case + */ + public static void putHeaders( + DefaultHttp2Headers headers, Map attachments, boolean needConvertHeaderKey) { + putHeaders(attachments, needConvertHeaderKey, headers::set); } /** - * Parse and put the KV pairs into metadata. Ignore Http2 PseudoHeaderName and internal name. - * Only raw byte array or string value will be put. + * Parse and put attachments into headers. + * Ignore Http2 PseudoHeaderName and internal name. + * Only strings, dates, and byte arrays are allowed. * - * @param headers the metadata holder - * @param attachments KV pairs - * @param needConvertHeaderKey convert flag + * @param headers the headers + * @param attachments the attachments + * @param needConvertHeaderKey whether need to convert the header key to lower-case */ - public static void convertAttachment( - DefaultHttp2Headers headers, Map attachments, boolean needConvertHeaderKey) { - if (attachments == null) { - return; - } - Map needConvertKey = new HashMap<>(); - for (Map.Entry entry : attachments.entrySet()) { - String key = lruHeaderMap.get(entry.getKey()); - if (key == null) { - final String lowerCaseKey = entry.getKey().toLowerCase(Locale.ROOT); - lruHeaderMap.put(entry.getKey(), lowerCaseKey); - key = lowerCaseKey; - } - if (TripleHeaderEnum.containsExcludeAttachments(key)) { - continue; - } - final Object v = entry.getValue(); - if (v == null) { - continue; - } - if (needConvertHeaderKey && !key.equals(entry.getKey())) { - needConvertKey.put(key, entry.getKey()); - } - convertSingleAttachment(headers, key, v); - } - if (!needConvertKey.isEmpty()) { - String needConvertJson = JsonUtils.toJson(needConvertKey); - headers.add(TripleHeaderEnum.TRI_HEADER_CONVERT.getHeader(), TriRpcStatus.encodeMessage(needConvertJson)); - } + public static void putHeaders(HttpHeaders headers, Map attachments, boolean needConvertHeaderKey) { + putHeaders(attachments, needConvertHeaderKey, headers::set); } - public static void convertAttachment( - HttpHeaders headers, Map attachments, boolean needConvertHeaderKey) { - if (attachments == null) { + private static void putHeaders( + Map attachments, boolean needConvertHeaderKey, BiConsumer consumer) { + if (CollectionUtils.isEmptyMap(attachments)) { return; } - Map needConvertKey = new HashMap<>(); + Map needConvertKeys = new HashMap<>(); for (Map.Entry entry : attachments.entrySet()) { - String key = lruHeaderMap.get(entry.getKey()); - if (key == null) { - final String lowerCaseKey = entry.getKey().toLowerCase(Locale.ROOT); - lruHeaderMap.put(entry.getKey(), lowerCaseKey); - key = lowerCaseKey; - } - if (TripleHeaderEnum.containsExcludeAttachments(key)) { + Object value = entry.getValue(); + if (value == null) { continue; } - final Object v = entry.getValue(); - if (v == null) { + + String key = entry.getKey(); + String lowerCaseKey = lruHeaderMap.computeIfAbsent(key, k -> k.toLowerCase(Locale.ROOT)); + if (TripleHeaderEnum.containsExcludeAttachments(lowerCaseKey)) { continue; } - if (needConvertHeaderKey && !key.equals(entry.getKey())) { - needConvertKey.put(key, entry.getKey()); + if (needConvertHeaderKey && !lowerCaseKey.equals(key)) { + needConvertKeys.put(lowerCaseKey, key); } - convertSingleAttachment(headers, key, v); + putHeader(consumer, lowerCaseKey, value); } - if (!needConvertKey.isEmpty()) { - String needConvertJson = JsonUtils.toJson(needConvertKey); - headers.set(TripleHeaderEnum.TRI_HEADER_CONVERT.getHeader(), TriRpcStatus.encodeMessage(needConvertJson)); + if (needConvertKeys.isEmpty()) { + return; } - } - - public static void convertAttachment(DefaultHttp2Headers headers, Map attachments) { - convertAttachment(headers, attachments, false); + consumer.accept( + TripleHeaderEnum.TRI_HEADER_CONVERT.getHeader(), + TriRpcStatus.encodeMessage(JsonUtils.toJson(needConvertKeys))); } /** - * Convert each user's attach value to metadata + * Put a KV pairs into headers. * - * @param headers outbound headers - * @param key metadata key - * @param v metadata value (Metadata Only string and byte arrays are allowed) + * @param consumer outbound headers consumer + * @param key the key of the attachment + * @param value the value of the attachment (Only strings, dates, and byte arrays are allowed in the attachment value.) */ - private static void convertSingleAttachment(DefaultHttp2Headers headers, String key, Object v) { + private static void putHeader(BiConsumer consumer, String key, Object value) { try { - if (v instanceof String || v instanceof Number || v instanceof Boolean) { - String str = v.toString(); - headers.set(key, str); - } else if (v instanceof byte[]) { - String str = encodeBase64ASCII((byte[]) v); - headers.set(key + TripleConstant.HEADER_BIN_SUFFIX, str); + if (value instanceof CharSequence || value instanceof Number || value instanceof Boolean) { + String str = value.toString(); + consumer.accept(key, str); + } else if (value instanceof Date) { + consumer.accept(key, DateFormatter.format((Date) value)); + } else if (value instanceof byte[]) { + String str = encodeBase64ASCII((byte[]) value); + consumer.accept(key + TripleConstant.HEADER_BIN_SUFFIX, str); } else { LOGGER.warn( PROTOCOL_UNSUPPORTED, "", "", "Unsupported attachment k: " + key + " class: " - + v.getClass().getName()); + + value.getClass().getName()); } } catch (Throwable t) { LOGGER.warn( PROTOCOL_UNSUPPORTED, "", "", - "Meet exception when convert single attachment key:" + key + " value=" + v, + "Meet exception when convert single attachment key:" + key + " value=" + value, t); } } - private static void convertSingleAttachment(HttpHeaders headers, String key, Object v) { - try { - if (v instanceof String || v instanceof Number || v instanceof Boolean) { - String str = v.toString(); - headers.set(key, str); - } else if (v instanceof byte[]) { - String str = encodeBase64ASCII((byte[]) v); - headers.set(key + TripleConstant.HEADER_BIN_SUFFIX, str); + /** + * Convert the given map to attachments. Ignore Http2 PseudoHeaderName and internal name. + * + * @param map The map + * @return the attachments + */ + public static Map toAttachments(Map map) { + if (CollectionUtils.isEmptyMap(map)) { + return Collections.emptyMap(); + } + Map res = CollectionUtils.newHashMap(map.size()); + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + if (TripleHeaderEnum.containsExcludeAttachments(key)) { + continue; + } + res.put(key, entry.getValue()); + } + return res; + } + + /** + * Parse and convert headers to attachments. Ignore Http2 PseudoHeaderName and internal name. + * + * @param headers the headers + * @return the attachments + */ + public static Map toAttachments(HttpHeaders headers) { + if (headers == null) { + return Collections.emptyMap(); + } + + Map attachments = CollectionUtils.newHashMap(headers.size()); + for (Map.Entry> entry : headers.entrySet()) { + String key = entry.getKey(); + String value = CollectionUtils.first(entry.getValue()); + int len = key.length() - TripleConstant.HEADER_BIN_SUFFIX.length(); + if (len > 0 && TripleConstant.HEADER_BIN_SUFFIX.equals(key.substring(len))) { + try { + putAttachment(attachments, key.substring(0, len), value == null ? null : decodeASCIIByte(value)); + } catch (Exception e) { + LOGGER.error(PROTOCOL_FAILED_PARSE, "", "", "Failed to parse response attachment key=" + key, e); + } } else { - LOGGER.warn( - PROTOCOL_UNSUPPORTED, - "", - "", - "Unsupported attachment k: " + key + " class: " - + v.getClass().getName()); + putAttachment(attachments, key, value); } - } catch (Throwable t) { - LOGGER.warn( - PROTOCOL_UNSUPPORTED, - "", - "", - "Meet exception when convert single attachment key:" + key + " value=" + v, - t); } + + // try converting upper key + String converted = headers.getFirst(TripleHeaderEnum.TRI_HEADER_CONVERT.getHeader()); + if (converted == null) { + return attachments; + } + String json = TriRpcStatus.decodeMessage(converted); + Map map = JsonUtils.toJavaObject(json, Map.class); + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + Object value = attachments.remove(key); + if (value != null) { + putAttachment(attachments, entry.getValue(), value); + } + } + return attachments; + } + + /** + * Put a KV pairs into attachments. + * + * @param attachments the map to which the attachment will be added + * @param key the key of the header + * @param value the value of the header + */ + private static void putAttachment(Map attachments, String key, Object value) { + if (TripleHeaderEnum.containsExcludeAttachments(key)) { + return; + } + attachments.put(key, value); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/transport/AbstractH2TransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/transport/AbstractH2TransportListener.java index fe5c341b206..1033ea26cbd 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/transport/AbstractH2TransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/transport/AbstractH2TransportListener.java @@ -56,7 +56,7 @@ protected Map headersToMap(Http2Headers trailers, Supplier TripleConstant.HEADER_BIN_SUFFIX.length()) { try { String realKey = key.substring(0, key.length() - TripleConstant.HEADER_BIN_SUFFIX.length()); - byte[] value = StreamUtils.decodeASCIIByte(header.getValue()); + byte[] value = StreamUtils.decodeASCIIByte(header.getValue().toString()); attachments.put(realKey, value); } catch (Exception e) { LOGGER.error(PROTOCOL_FAILED_PARSE, "", "", "Failed to parse response attachment key=" + key, e); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter new file mode 100644 index 00000000000..204897e33ff --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter @@ -0,0 +1,2 @@ +http-context=org.apache.dubbo.rpc.protocol.tri.HttpContextFilter +rest-extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionExecutionFilter \ No newline at end of file diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol index 3cb3ffb1163..46b1e346494 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol @@ -1,2 +1,3 @@ tri=org.apache.dubbo.rpc.protocol.tri.TripleProtocol -grpc=org.apache.dubbo.rpc.protocol.tri.GrpcProtocol \ No newline at end of file +grpc=org.apache.dubbo.rpc.protocol.tri.GrpcProtocol +rest2=org.apache.dubbo.rpc.protocol.tri.RestProtocol 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 new file mode 100644 index 00000000000..3d2d4e50736 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver @@ -0,0 +1 @@ +misc=org.apache.dubbo.rpc.protocol.tri.rest.argument.MiscArgumentResolver 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 new file mode 100644 index 00000000000..3f44b63ea22 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping @@ -0,0 +1,3 @@ +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 diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/GeneralTypeConverterTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/GeneralTypeConverterTest.java new file mode 100644 index 00000000000..ff82a089734 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/GeneralTypeConverterTest.java @@ -0,0 +1,50 @@ +/* + * 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.common.utils.JsonUtils; +import org.apache.dubbo.rpc.protocol.tri.rest.argument.GeneralTypeConverter; +import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +class GeneralTypeConverterTest { + + public List[] items; + + @Test + void convert() throws NoSuchFieldException { + GeneralTypeConverter smartConverter = new GeneralTypeConverter(); + smartConverter.convert( + "23,56", GeneralTypeConverterTest.class.getField("items").getGenericType()); + } + + @Test + void convert1() { + Object convert = JsonUtils.toJavaObject("[1,\"aa\"]", List.class); + System.out.println(convert); + } + + @Test + void convert2() throws NoSuchFieldException { + Class type = TypeUtils.getActualType( + GeneralTypeConverterTest.class.getField("items").getGenericType()); + System.out.println(type); + } +} 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/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/PathUtilsTest.java new file mode 100644 index 00000000000..db2ec6804e5 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/PathUtilsTest.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.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; + +import org.junit.jupiter.api.Test; + +class PathUtilsTest { + + @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)); + } + + @Test + void isDirectPath() {} + + @Test + void combine() {} + + @Test + void normalize() {} +} 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 new file mode 100644 index 00000000000..bd3142c0efe --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.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.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 new file mode 100644 index 00000000000..cb8616fc83b --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParserTest.java @@ -0,0 +1,49 @@ +/* + * 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/stream/StreamUtilsTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/stream/StreamUtilsTest.java index 07d08513e0e..fc79f4c683c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/stream/StreamUtilsTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/stream/StreamUtilsTest.java @@ -55,7 +55,7 @@ void testConvertAttachment() throws InterruptedException { attachments.put("Upper", "Upper"); attachments.put("obj", new Object()); - StreamUtils.convertAttachment(headers, attachments, false); + StreamUtils.putHeaders(headers, attachments, false); Assertions.assertNull(headers.get(TripleHeaderEnum.PATH_KEY.getHeader())); Assertions.assertNull(headers.get("Upper")); Assertions.assertNull(headers.get("obj")); @@ -63,7 +63,7 @@ void testConvertAttachment() throws InterruptedException { headers = new DefaultHttp2Headers(); headers.add("key", "value"); - StreamUtils.convertAttachment(headers, attachments, true); + StreamUtils.putHeaders(headers, attachments, true); Assertions.assertNull(headers.get(TripleHeaderEnum.PATH_KEY.getHeader())); Assertions.assertNull(headers.get("Upper")); Assertions.assertNull(headers.get("obj")); @@ -89,7 +89,7 @@ void testConvertAttachment() throws InterruptedException { executorService.execute(() -> { DefaultHttp2Headers headers2 = new DefaultHttp2Headers(); headers2.add("key", "value"); - StreamUtils.convertAttachment(headers2, attachments2, true); + StreamUtils.putHeaders(headers2, attachments2, true); if (headers2.get(TripleHeaderEnum.PATH_KEY.getHeader()) != null) { return; 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 5493dcd995d..f9fdce872bf 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,6 +27,7 @@ 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.TripleConfig; @@ -373,6 +374,12 @@ public static class RpcConfig { @NestedConfigurationProperty private TripleConfig tri; + /** + * The rest config. + */ + @NestedConfigurationProperty + private RestConfig rest; + public TripleConfig getTri() { return tri; } @@ -380,5 +387,13 @@ public TripleConfig getTri() { public void setTri(TripleConfig tri) { this.tri = tri; } + + public RestConfig getRest() { + return rest; + } + + public void setRest(RestConfig rest) { + this.rest = rest; + } } } diff --git a/dubbo-test/dubbo-dependencies-all/pom.xml b/dubbo-test/dubbo-dependencies-all/pom.xml index bac453aa165..dc961a62890 100644 --- a/dubbo-test/dubbo-dependencies-all/pom.xml +++ b/dubbo-test/dubbo-dependencies-all/pom.xml @@ -256,6 +256,21 @@ dubbo-plugin-proxy-bytebuddy ${project.version} + + org.apache.dubbo + dubbo-rest-jaxrs + ${project.version} + + + org.apache.dubbo + dubbo-rest-servlet + ${project.version} + + + org.apache.dubbo + dubbo-rest-spring + ${project.version} +