From fc8d8e36284f65c708e7c435299b79887adfa8c4 Mon Sep 17 00:00:00 2001 From: mercyblitz Date: Tue, 19 Feb 2019 15:05:25 +0800 Subject: [PATCH] Polish spring-cloud-incubator/spring-cloud-alibaba#348 : @DubboTransported supports RestTemplate (part 1) --- README-zh.md | 2 +- README.md | 2 +- pom.xml | 18 +- spring-cloud-alibaba-dependencies/pom.xml | 10 +- .../src/main/asciidoc-zh/nacos-discovery.adoc | 6 +- .../src/main/asciidoc-zh/sentinel.adoc | 4 +- .../main/asciidoc/dependency-management.adoc | 2 +- .../src/main/asciidoc/nacos-discovery.adoc | 6 +- .../src/main/asciidoc/sentinel.adoc | 2 +- .../dubbo/annotation/DubboTransported.java | 2 +- ...BalancedRestTemplateAutoConfiguration.java | 126 +++++++++++ ...MetadataRegistrationAutoConfiguration.java | 31 ++- .../DubboAdapterLoadBalancerInterceptor.java | 109 ++++++++++ .../loadbalancer/DubboClientHttpResponse.java | 82 +++++++ .../loadbalancer/DubboHttpOutputMessage.java | 42 ++++ .../dubbo/http/DefaultHttpRequest.java | 130 +++++++++++ .../matcher/AbstractHttpRequestMatcher.java | 76 +++++++ .../matcher/AbstractMediaTypeExpression.java | 91 ++++++++ .../matcher/AbstractNameValueExpression.java | 148 +++++++++++++ .../matcher/CompositeHttpRequestMatcher.java | 73 +++++++ .../matcher/ConsumeMediaTypeExpression.java | 44 ++++ .../dubbo/http/matcher/HeaderExpression.java | 60 ++++++ .../matcher/HttpRequestConsumersMatcher.java | 123 +++++++++++ .../matcher/HttpRequestHeadersMatcher.java | 71 ++++++ .../http/matcher/HttpRequestMatcher.java | 35 +++ .../matcher/HttpRequestMethodsMatcher.java | 82 +++++++ .../matcher/HttpRequestParamsMatcher.java | 72 +++++++ .../http/matcher/HttpRequestPathMatcher.java | 117 ++++++++++ .../matcher/HttpRequestProducesMatcher.java | 119 ++++++++++ .../http/matcher/MediaTypeExpression.java | 35 +++ .../http/matcher/NameValueExpression.java | 38 ++++ .../dubbo/http/matcher/ParamExpression.java | 62 ++++++ .../matcher/ProduceMediaTypeExpression.java | 55 +++++ .../http/matcher/RequestMetadataMatcher.java | 46 ++++ .../alibaba/dubbo/http/util/HttpUtils.java | 187 ++++++++++++++++ .../dubbo/metadata/MethodMetadata.java | 4 + .../metadata/MethodParameterMetadata.java | 3 + .../dubbo/metadata/RequestMetadata.java | 203 +++++++++++++++--- .../dubbo/metadata/RestMethodMetadata.java | 141 +++++++++++- .../dubbo/metadata/ServiceRestMetadata.java | 16 +- .../DubboServiceMetadataRepository.java | 110 ++++++++-- .../DubboServiceBeanMetadataResolver.java | 8 +- .../metadata/resolver/ParameterResolver.java | 107 +++++++++ .../DubboInvocationHandlerFactory.java | 103 --------- .../openfeign/TargeterInvocationHandler.java | 15 +- .../main/resources/META-INF/spring.factories | 3 +- .../bootstrap/DubboSpringCloudBootstrap.java | 38 +++- .../AbstractHttpRequestMatcherTest.java | 37 ++++ .../AbstractMediaTypeExpressionTest.java | 71 ++++++ .../AbstractNameValueExpressionTest.java | 77 +++++++ .../ConsumeMediaTypeExpressionTest.java | 45 ++++ .../http/matcher/HeaderExpressionTest.java | 60 ++++++ .../HttpRequestMethodsMatcherTest.java | 49 +++++ .../matcher/HttpRequestParamsMatcherTest.java | 98 +++++++++ .../http/matcher/ParamExpressionTest.java | 60 ++++++ .../ProduceMediaTypeExpressionTest.java | 40 ++++ .../dubbo/http/util/HttpUtilsTest.java | 41 ++++ .../dubbo/metadata/RequestMetadataTest.java | 136 ++++++++++++ .../dubbo/service/DefaultEchoService.java | 52 ++++- .../alibaba/dubbo/service/EchoService.java | 9 + .../sentinel-dubbo-example/readme-zh.md | 2 +- .../sentinel-dubbo-example/readme.md | 2 +- 62 files changed, 3408 insertions(+), 230 deletions(-) create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboLoadBalancedRestTemplateAutoConfiguration.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboAdapterLoadBalancerInterceptor.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboClientHttpResponse.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboHttpOutputMessage.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/DefaultHttpRequest.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractHttpRequestMatcher.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractMediaTypeExpression.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractNameValueExpression.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/CompositeHttpRequestMatcher.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ConsumeMediaTypeExpression.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HeaderExpression.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestConsumersMatcher.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestHeadersMatcher.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMatcher.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMethodsMatcher.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestParamsMatcher.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestPathMatcher.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestProducesMatcher.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/MediaTypeExpression.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/NameValueExpression.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ParamExpression.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ProduceMediaTypeExpression.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/RequestMetadataMatcher.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/util/HttpUtils.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/ParameterResolver.java delete mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboInvocationHandlerFactory.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractHttpRequestMatcherTest.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractMediaTypeExpressionTest.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractNameValueExpressionTest.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ConsumeMediaTypeExpressionTest.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HeaderExpressionTest.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMethodsMatcherTest.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestParamsMatcherTest.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ParamExpressionTest.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ProduceMediaTypeExpressionTest.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/util/HttpUtilsTest.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadataTest.java diff --git a/README-zh.md b/README-zh.md index bfe834aa..034c47ca 100644 --- a/README-zh.md +++ b/README-zh.md @@ -87,7 +87,7 @@ Spring Cloud 使用 Maven 来构建,最快的使用方式是将本项目 clone spring-snapshot Spring Snapshot Repository - https://repo.spring.io/snapshot + https://repo.spring.io/snapshot true diff --git a/README.md b/README.md index ad4672cd..9706e7c2 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ If you want to use the latest BUILD-SNAPSHOT version, add `Spring Snapshot Repos spring-snapshot Spring Snapshot Repository - https://repo.spring.io/snapshot + https://repo.spring.io/snapshot true diff --git a/pom.xml b/pom.xml index 57652f34..117982d6 100644 --- a/pom.xml +++ b/pom.xml @@ -22,13 +22,13 @@ Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt + http://www.apache.org/licenses/LICENSE-2.0.txt repo - https://github.com/spring-cloud-incubator/spring-cloud-alibaba + https://github.com/spring-cloud-incubator/spring-cloud-alibaba scm:git:git://github.com/spring-cloud-incubator/spring-cloud-alibaba.git @@ -60,7 +60,7 @@ Mercy Ma mercyblitz@gmail.com Alibaba - https://github.com/mercyblitz + https://github.com/mercyblitz @@ -246,7 +246,7 @@ spring-snapshots Spring Snapshots - https://repo.spring.io/libs-snapshot-local + https://repo.spring.io/libs-snapshot-local true @@ -257,7 +257,7 @@ spring-milestones Spring Milestones - https://repo.spring.io/libs-milestone-local + https://repo.spring.io/libs-milestone-local false @@ -265,7 +265,7 @@ spring-releases Spring Releases - https://repo.spring.io/release + https://repo.spring.io/release false @@ -275,7 +275,7 @@ spring-snapshots Spring Snapshots - https://repo.spring.io/libs-snapshot-local + https://repo.spring.io/libs-snapshot-local true @@ -286,7 +286,7 @@ spring-milestones Spring Milestones - https://repo.spring.io/libs-milestone-local + https://repo.spring.io/libs-milestone-local false @@ -294,7 +294,7 @@ spring-releases Spring Releases - https://repo.spring.io/libs-release-local + https://repo.spring.io/libs-release-local false diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index 9034205a..60f5868c 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -405,7 +405,7 @@ spring-snapshots Spring Snapshots - https://repo.spring.io/libs-snapshot-local + https://repo.spring.io/libs-snapshot-local true @@ -416,7 +416,7 @@ spring-milestones Spring Milestones - https://repo.spring.io/libs-milestone-local + https://repo.spring.io/libs-milestone-local false @@ -424,7 +424,7 @@ spring-releases Spring Releases - https://repo.spring.io/release + https://repo.spring.io/release false @@ -434,7 +434,7 @@ spring-snapshots Spring Snapshots - https://repo.spring.io/libs-snapshot-local + https://repo.spring.io/libs-snapshot-local true @@ -445,7 +445,7 @@ spring-milestones Spring Milestones - https://repo.spring.io/libs-milestone-local + https://repo.spring.io/libs-milestone-local false diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc index bd88310a..57b3a656 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc @@ -261,9 +261,9 @@ public class NacosConsumerApp { public String echoAppName(){ //使用 LoadBalanceClient 和 RestTemolate 结合的方式来访问 ServiceInstance serviceInstance = loadBalancerClient.choose("nacos-provider"); - String url = String.format("http://%s:%s/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName); - System.out.println("request url:"+url); - return restTemplate.getForObject(url,String.class); + String path = String.format("http://%s:%s/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName); + System.out.println("request path:"+path); + return restTemplate.getForObject(path,String.class); } } diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc index 5d106bd3..b5961fc4 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sentinel.adoc @@ -185,7 +185,7 @@ Sentinel RestTemplate 限流的资源规则提供两种粒度: * `schema://host:port`:协议、主机和端口 -NOTE: 以 `https://www.taobao.com/test` 这个 url 为例。对应的资源名有两种粒度,分别是 `https://www.taobao.com` 以及 `https://www.taobao.com/test` +NOTE: 以 `https://www.taobao.com/test` 这个 path 为例。对应的资源名有两种粒度,分别是 `https://www.taobao.com` 以及 `https://www.taobao.com/test` ### 动态数据源支持 @@ -324,7 +324,7 @@ NOTE: 默认情况下,xml 格式是不支持的。需要添加 `jackson-datafo |`spring.cloud.sentinel.transport.dashboard`|Sentinel 控制台地址| |`spring.cloud.sentinel.transport.heartbeatIntervalMs`|应用与Sentinel控制台的心跳间隔时间| |`spring.cloud.sentinel.filter.order`|Servlet Filter的加载顺序。Starter内部会构造这个filter|Integer.MIN_VALUE -|`spring.cloud.sentinel.filter.spring.url-patterns`|数据类型是数组。表示Servlet Filter的url pattern集合|/* +|`spring.cloud.sentinel.filter.spring.path-patterns`|数据类型是数组。表示Servlet Filter的url pattern集合|/* |`spring.cloud.sentinel.metric.charset`|metric文件字符集|UTF-8 |`spring.cloud.sentinel.metric.fileSingleSize`|Sentinel metric 单个文件的大小| |`spring.cloud.sentinel.metric.fileTotalCount`|Sentinel metric 总文件数量| diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc/dependency-management.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc/dependency-management.adoc index 07b4dd40..ac027a63 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc/dependency-management.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc/dependency-management.adoc @@ -29,7 +29,7 @@ If you want to use the latest BUILD-SNAPSHOT version, add Spring Snapshot Reposi spring-snapshot Spring Snapshot Repository - https://repo.spring.io/snapshot + https://repo.spring.io/snapshot true diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc index 44f2222f..21f0b144 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc @@ -261,9 +261,9 @@ public class NacosConsumerApp { public String echoAppName(){ //Access through the combination of LoadBalanceClient and RestTemolate ServiceInstance serviceInstance = loadBalancerClient.choose("nacos-provider"); - String url = String.format("http://%s:%s/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName); - System.out.println("request url:" +url); - return restTemplate.getForObject(url,String.class); + String path = String.format("http://%s:%s/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName); + System.out.println("request path:" +path); + return restTemplate.getForObject(path,String.class); } } diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc/sentinel.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc/sentinel.adoc index 40478e4b..664d478f 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc/sentinel.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc/sentinel.adoc @@ -326,7 +326,7 @@ The following table shows all the configurations of Spring Cloud Alibaba Sentine |`spring.cloud.sentinel.transport.dashboard`|Sentinel dashboard address| |`spring.cloud.sentinel.transport.heartbeatIntervalMs`|Hearbeat interval between the application and Sentinel dashboard| |`spring.cloud.sentinel.filter.order`|Loading order of Servlet Filter. The filter will be constructed in the Starter|Integer.MIN_VALUE -|`spring.cloud.sentinel.filter.spring.url-patterns`|Data type is array. Refers to the collection of Servlet Filter ULR patterns|/* +|`spring.cloud.sentinel.filter.spring.path-patterns`|Data type is array. Refers to the collection of Servlet Filter ULR patterns|/* |`spring.cloud.sentinel.metric.charset`|metric file character set|UTF-8 |`spring.cloud.sentinel.metric.fileSingleSize`|Sentinel metric single file size| |`spring.cloud.sentinel.metric.fileTotalCount`|Sentinel metric total file number| diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/annotation/DubboTransported.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/annotation/DubboTransported.java index 54947a92..2714923d 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/annotation/DubboTransported.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/annotation/DubboTransported.java @@ -61,7 +61,7 @@ public @interface DubboTransported { /** * The cluster of Dubbo transport whose value could be used the placeholder "dubbo.transport.cluster" * - * @return the default protocol is "failover" + * @return the default cluster is "failover" */ String cluster() default "${dubbo.transport.cluster:failover}"; } diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboLoadBalancedRestTemplateAutoConfiguration.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboLoadBalancedRestTemplateAutoConfiguration.java new file mode 100644 index 00000000..2a8587d6 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboLoadBalancedRestTemplateAutoConfiguration.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.springframework.cloud.alibaba.dubbo.autoconfigure; + +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported; +import org.springframework.cloud.alibaba.dubbo.client.loadbalancer.DubboAdapterLoadBalancerInterceptor; +import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; +import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; +import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; +import org.springframework.core.type.MethodMetadata; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Dubbo Auto-{@link Configuration} for {@link LoadBalanced @LoadBalanced} {@link RestTemplate} + * + * @author Mercy + */ +@Configuration +@ConditionalOnClass(RestTemplate.class) +@AutoConfigureAfter(LoadBalancerAutoConfiguration.class) +public class DubboLoadBalancedRestTemplateAutoConfiguration { + + private static final Class DUBBO_TRANSPORTED_CLASS = DubboTransported.class; + + private static final String DUBBO_TRANSPORTED_CLASS_NAME = DUBBO_TRANSPORTED_CLASS.getName(); + + @Autowired + private DubboServiceMetadataRepository repository; + + @Autowired + private LoadBalancerInterceptor loadBalancerInterceptor; + + @Autowired + private ConfigurableListableBeanFactory beanFactory; + + @LoadBalanced + @Autowired(required = false) + private Map restTemplates = Collections.emptyMap(); + + /** + * Adapt the {@link RestTemplate} beans that are annotated {@link LoadBalanced @LoadBalanced} and + * {@link LoadBalanced @LoadBalanced} when Spring Boot application started + * (after the callback of {@link SmartInitializingSingleton} beans or + * {@link RestTemplateCustomizer#customize(RestTemplate) customization}) + */ + @EventListener(ApplicationStartedEvent.class) + public void adaptRestTemplates() { + for (Map.Entry entry : restTemplates.entrySet()) { + String beanName = entry.getKey(); + if (isDubboTranslatedAnnotated(beanName)) { + adaptRestTemplate(entry.getValue()); + } + } + } + + /** + * Judge {@link RestTemplate} bean being annotated {@link DubboTransported @DubboTransported} or not + * + * @param beanName the bean name of {@link LoadBalanced @LoadBalanced} {@link RestTemplate} + * @return + */ + private boolean isDubboTranslatedAnnotated(String beanName) { + boolean annotated = false; + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + if (beanDefinition instanceof AnnotatedBeanDefinition) { + AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition; + MethodMetadata factoryMethodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata(); + annotated = factoryMethodMetadata != null && + !factoryMethodMetadata.getAnnotationAttributes(DUBBO_TRANSPORTED_CLASS_NAME).isEmpty(); + } + return annotated; + } + + + /** + * Adapt the instance of {@link DubboAdapterLoadBalancerInterceptor} to the {@link LoadBalancerInterceptor} Bean. + * + * @param restTemplate {@link LoadBalanced @LoadBalanced} {@link RestTemplate} Bean + */ + private void adaptRestTemplate(RestTemplate restTemplate) { + + List interceptors = new ArrayList<>(restTemplate.getInterceptors()); + + int index = interceptors.indexOf(loadBalancerInterceptor); + + if (index > -1) { + interceptors.set(index, new DubboAdapterLoadBalancerInterceptor(repository, loadBalancerInterceptor, + restTemplate.getMessageConverters())); + } + + restTemplate.setInterceptors(interceptors); + } + +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestMetadataRegistrationAutoConfiguration.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestMetadataRegistrationAutoConfiguration.java index 7c7aa9fd..e75c1f73 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestMetadataRegistrationAutoConfiguration.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestMetadataRegistrationAutoConfiguration.java @@ -18,15 +18,16 @@ package org.springframework.cloud.alibaba.dubbo.autoconfigure; import com.alibaba.dubbo.config.spring.ServiceBean; import com.alibaba.dubbo.config.spring.context.event.ServiceBeanExportedEvent; -import com.fasterxml.jackson.core.JsonProcessingException; + import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata; import org.springframework.cloud.alibaba.dubbo.metadata.resolver.MetadataResolver; import org.springframework.cloud.alibaba.dubbo.metadata.service.MetadataConfigService; -import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent; import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; @@ -42,7 +43,7 @@ import java.util.Set; * @author Mercy */ @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) -@ConditionalOnMissingBean(value = { +@ConditionalOnBean(value = { MetadataResolver.class, MetadataConfigService.class }) @@ -62,25 +63,21 @@ public class DubboRestMetadataRegistrationAutoConfiguration { @Autowired private MetadataConfigService metadataConfigService; + @Value("${spring.application.name:application}") + private String currentApplicationName; + @EventListener(ServiceBeanExportedEvent.class) - public void recordRestMetadata(ServiceBeanExportedEvent event) throws JsonProcessingException { + public void recordRestMetadata(ServiceBeanExportedEvent event) { ServiceBean serviceBean = event.getServiceBean(); serviceRestMetadata.addAll(metadataResolver.resolveServiceRestMetadata(serviceBean)); } /** - * Pre-handle Spring Cloud application service registered: - *

- * Put restMetadata with the JSON format into - * {@link Registration#getMetadata() service instances' metadata} - *

- * - * @param event {@link InstancePreRegisteredEvent} instance + * Publish serviceRestMetadata with the JSON format into + * {@link Registration#getMetadata() service instances' metadata} when The Spring Application is started. */ - @EventListener(InstancePreRegisteredEvent.class) - public void registerRestMetadata(InstancePreRegisteredEvent event) throws Exception { - Registration registration = event.getRegistration(); - metadataConfigService.publishServiceRestMetadata(registration.getServiceId(), serviceRestMetadata); + @EventListener(ApplicationStartedEvent.class) + public void registerRestMetadata() { + metadataConfigService.publishServiceRestMetadata(currentApplicationName, serviceRestMetadata); } - } diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboAdapterLoadBalancerInterceptor.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboAdapterLoadBalancerInterceptor.java new file mode 100644 index 00000000..7654c79b --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboAdapterLoadBalancerInterceptor.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.springframework.cloud.alibaba.dubbo.client.loadbalancer; + +import com.alibaba.dubbo.config.spring.ReferenceBean; +import com.alibaba.dubbo.rpc.service.GenericService; + +import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata; +import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata; +import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata; +import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository; +import org.springframework.cloud.alibaba.dubbo.metadata.resolver.ParameterResolver; +import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.IOException; +import java.net.URI; +import java.util.List; + +/** + * Dubbo {@link ClientHttpRequestInterceptor} implementation to adapt {@link LoadBalancerInterceptor} + * + * @author Mercy + * @see LoadBalancerInterceptor + */ +public class DubboAdapterLoadBalancerInterceptor implements ClientHttpRequestInterceptor { + + private final ParameterResolver parameterResolver = new ParameterResolver(); + + private final DubboServiceMetadataRepository repository; + + private final LoadBalancerInterceptor loadBalancerInterceptor; + + private final List> messageConverters; + + public DubboAdapterLoadBalancerInterceptor(DubboServiceMetadataRepository dubboServiceMetadataRepository, + LoadBalancerInterceptor loadBalancerInterceptor, + List> messageConverters) { + this.repository = dubboServiceMetadataRepository; + this.loadBalancerInterceptor = loadBalancerInterceptor; + this.messageConverters = messageConverters; + } + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + + URI originalUri = request.getURI(); + + UriComponents uriComponents = UriComponentsBuilder.fromUri(originalUri).build(true); + + String serviceName = originalUri.getHost(); + + repository.initialize(serviceName); + + RequestMetadata requestMetadata = buildRequestMetadata(request, uriComponents); + + ReferenceBean referenceBean = + repository.getReferenceBean(serviceName, requestMetadata); + + RestMethodMetadata restMethodMetadata = repository.getRestMethodMetadata(serviceName, requestMetadata); + + if (referenceBean == null || restMethodMetadata == null) { + return loadBalancerInterceptor.intercept(request, body, execution); + } + + MethodMetadata methodMetadata = restMethodMetadata.getMethod(); + + String methodName = methodMetadata.getName(); + + String[] parameterTypes = parameterResolver.resolveParameterTypes(methodMetadata); + + Object[] parameters = parameterResolver.resolveParameters(restMethodMetadata, request, uriComponents); + + GenericService genericService = referenceBean.get(); + + Object result = genericService.$invoke(methodName, parameterTypes, parameters); + + return null; + } + + public static RequestMetadata buildRequestMetadata(HttpRequest request, UriComponents uriComponents) { + RequestMetadata requestMetadata = new RequestMetadata(); + requestMetadata.setPath(uriComponents.getPath()); + requestMetadata.setMethod(request.getMethod().name()); + requestMetadata.setParams(uriComponents.getQueryParams()); + requestMetadata.setHeaders(request.getHeaders()); + return requestMetadata; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboClientHttpResponse.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboClientHttpResponse.java new file mode 100644 index 00000000..3d0d537d --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboClientHttpResponse.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.springframework.cloud.alibaba.dubbo.client.loadbalancer; + +import com.alibaba.dubbo.rpc.service.GenericException; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Dubbo {@link ClientHttpResponse} implementation + * + * @author Mercy + * @see DubboAdapterLoadBalancerInterceptor + */ +class DubboClientHttpResponse implements ClientHttpResponse { + + private final Object result; + + private final GenericException exception; + + private final HttpStatus httpStatus; + + private final String statusText; + + private final HttpHeaders httpHeaders = new HttpHeaders(); + + public DubboClientHttpResponse(Object result, GenericException exception) { + this.result = result; + this.exception = exception; + this.httpStatus = exception != null ? HttpStatus.INTERNAL_SERVER_ERROR : HttpStatus.OK; + this.statusText = exception != null ? exception.getExceptionMessage() : httpStatus.getReasonPhrase(); + } + + @Override + public HttpStatus getStatusCode() throws IOException { + return httpStatus; + } + + @Override + public int getRawStatusCode() throws IOException { + return httpStatus.value(); + } + + @Override + public String getStatusText() throws IOException { + return statusText; + } + + @Override + public void close() { + + } + + @Override + public InputStream getBody() throws IOException { + return null; + } + + @Override + public HttpHeaders getHeaders() { + return httpHeaders; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboHttpOutputMessage.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboHttpOutputMessage.java new file mode 100644 index 00000000..047045ee --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/client/loadbalancer/DubboHttpOutputMessage.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.springframework.cloud.alibaba.dubbo.client.loadbalancer; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpOutputMessage; +import org.springframework.util.FastByteArrayOutputStream; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Dubbo {@link HttpOutputMessage} implementation + * + * @author Mercy + */ +class DubboHttpOutputMessage implements HttpOutputMessage { + + @Override + public OutputStream getBody() throws IOException { + return new FastByteArrayOutputStream(); + } + + @Override + public HttpHeaders getHeaders() { + return new HttpHeaders(); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/DefaultHttpRequest.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/DefaultHttpRequest.java new file mode 100644 index 00000000..3f1f376d --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/DefaultHttpRequest.java @@ -0,0 +1,130 @@ +/* + * 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.springframework.cloud.alibaba.dubbo.http; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import static org.springframework.web.util.UriComponentsBuilder.fromPath; + +/** + * Default {@link HttpRequest} implementation + * + * @author Mercy + */ +public class DefaultHttpRequest implements HttpRequest { + + private final String method; + + private final URI uri; + + private final HttpHeaders headers = new HttpHeaders(); + + public DefaultHttpRequest(String method, String path, Map> params, + Map> headers) { + this.method = method == null ? HttpMethod.GET.name() : method.toUpperCase(); + this.uri = buildURI(path, params); + this.headers.putAll(headers); + } + + private URI buildURI(String path, Map> params) { + UriComponentsBuilder builder = fromPath(path) + .queryParams(new LinkedMultiValueMap<>(params)); + return builder.build().toUri(); + } + + @Override + public HttpMethod getMethod() { + return HttpMethod.resolve(getMethodValue()); + } + + public String getMethodValue() { + return method; + } + + @Override + public URI getURI() { + return uri; + } + + @Override + public HttpHeaders getHeaders() { + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + /** + * {@link HttpRequest} Builder + */ + public static class Builder { + + String method; + + String path; + + MultiValueMap params = new LinkedMultiValueMap<>(); + + MultiValueMap headers = new LinkedMultiValueMap<>(); + + public Builder method(String method) { + this.method = method; + return this; + } + + public Builder path(String path) { + this.path = path; + return this; + } + + public Builder param(String name, String value) { + this.params.add(name, value); + return this; + } + + public Builder header(String name, String value) { + this.headers.add(name, value); + return this; + } + + public Builder params(Map> params) { + this.params.putAll(params); + return this; + } + + public Builder headers(Map> headers) { + this.headers.putAll(headers); + return this; + } + + public HttpRequest build() { + return new DefaultHttpRequest(method, path, params, headers); + } + } + + +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractHttpRequestMatcher.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractHttpRequestMatcher.java new file mode 100644 index 00000000..8b4da7bb --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractHttpRequestMatcher.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.lang.Nullable; + +import java.util.Collection; +import java.util.Iterator; + +/** + * Abstract {@link HttpRequestMatcher} implementation + * + * @author Rossen Stoyanchev + * @author Mercy + */ +public abstract class AbstractHttpRequestMatcher implements HttpRequestMatcher { + + /** + * Return the discrete items a request condition is composed of. + *

For example URL patterns, HTTP request methods, param expressions, etc. + * + * @return a collection of objects, never {@code null} + */ + protected abstract Collection getContent(); + + /** + * The notation to use when printing discrete items of content. + *

For example {@code " || "} for URL patterns or {@code " && "} + * for param expressions. + */ + protected abstract String getToStringInfix(); + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + return getContent().equals(((AbstractHttpRequestMatcher) other).getContent()); + } + + @Override + public int hashCode() { + return getContent().hashCode(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("["); + for (Iterator iterator = getContent().iterator(); iterator.hasNext(); ) { + Object expression = iterator.next(); + builder.append(expression.toString()); + if (iterator.hasNext()) { + builder.append(getToStringInfix()); + } + } + builder.append("]"); + return builder.toString(); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractMediaTypeExpression.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractMediaTypeExpression.java new file mode 100644 index 00000000..de5d9f84 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractMediaTypeExpression.java @@ -0,0 +1,91 @@ +/* + * 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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.MediaType; + +/** + * The some source code is scratched from org.springframework.web.servlet.mvc.condition.AbstractMediaTypeExpression + * + * @author Arjen Poutsma + * @author Rossen Stoyanchev + * @author Mercy + */ +public class AbstractMediaTypeExpression implements MediaTypeExpression, Comparable { + + private final MediaType mediaType; + + private final boolean negated; + + AbstractMediaTypeExpression(String expression) { + if (expression.startsWith("!")) { + this.negated = true; + expression = expression.substring(1); + } else { + this.negated = false; + } + this.mediaType = MediaType.parseMediaType(expression); + } + + AbstractMediaTypeExpression(MediaType mediaType, boolean negated) { + this.mediaType = mediaType; + this.negated = negated; + } + + @Override + public MediaType getMediaType() { + return this.mediaType; + } + + @Override + public boolean isNegated() { + return this.negated; + } + + + @Override + public int compareTo(AbstractMediaTypeExpression other) { + return MediaType.SPECIFICITY_COMPARATOR.compare(this.getMediaType(), other.getMediaType()); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + AbstractMediaTypeExpression otherExpr = (AbstractMediaTypeExpression) other; + return (this.mediaType.equals(otherExpr.mediaType) && this.negated == otherExpr.negated); + } + + @Override + public int hashCode() { + return this.mediaType.hashCode(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (this.negated) { + builder.append('!'); + } + builder.append(this.mediaType.toString()); + return builder.toString(); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractNameValueExpression.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractNameValueExpression.java new file mode 100644 index 00000000..a08cee30 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractNameValueExpression.java @@ -0,0 +1,148 @@ +/* + * 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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.HttpRequest; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import static org.springframework.util.StringUtils.trimWhitespace; + +/** + * The some source code is scratched from org.springframework.web.servlet.mvc.condition.AbstractNameValueExpression + * + * @author Rossen Stoyanchev + * @author Arjen Poutsma + * @author Mercy + */ +abstract class AbstractNameValueExpression implements NameValueExpression { + + protected final String name; + + protected final T value; + + protected final boolean negated; + + AbstractNameValueExpression(String expression) { + int separator = expression.indexOf('='); + if (separator == -1) { + this.negated = expression.startsWith("!"); + this.name = trimWhitespace((this.negated ? expression.substring(1) : expression)); + this.value = null; + } else { + this.negated = (separator > 0) && (expression.charAt(separator - 1) == '!'); + this.name = trimWhitespace((this.negated ? expression.substring(0, separator - 1) + : expression.substring(0, separator))); + String valueExpression = getValueExpression(expression, separator); + this.value = isExcludedValue(valueExpression) ? null : parseValue(valueExpression); + } + } + + private String getValueExpression(String expression, int separator) { + return trimWhitespace(expression.substring(separator + 1)); + } + + /** + * Exclude the pattern value Expression: "{value}", subclass could override this method. + * + * @param valueExpression + * @return + */ + protected boolean isExcludedValue(String valueExpression) { + return StringUtils.hasText(valueExpression) && + valueExpression.startsWith("{") + && valueExpression.endsWith("}"); + } + + @Override + public String getName() { + return this.name; + } + + @Override + @Nullable + public T getValue() { + return this.value; + } + + @Override + public boolean isNegated() { + return this.negated; + } + + public final boolean match(HttpRequest request) { + boolean isMatch; + if (this.value != null) { + isMatch = matchValue(request); + } else { + isMatch = matchName(request); + } + return (this.negated ? !isMatch : isMatch); + } + + + protected abstract boolean isCaseSensitiveName(); + + protected abstract T parseValue(String valueExpression); + + protected abstract boolean matchName(HttpRequest request); + + protected abstract boolean matchValue(HttpRequest request); + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + AbstractNameValueExpression that = (AbstractNameValueExpression) other; + return ((isCaseSensitiveName() ? this.name.equals(that.name) : this.name.equalsIgnoreCase(that.name)) && + ObjectUtils.nullSafeEquals(this.value, that.value) && this.negated == that.negated); + } + + @Override + public int hashCode() { + int result = (isCaseSensitiveName() ? this.name.hashCode() : this.name.toLowerCase().hashCode()); + result = 31 * result + (this.value != null ? this.value.hashCode() : 0); + result = 31 * result + (this.negated ? 1 : 0); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (this.value != null) { + builder.append(this.name); + if (this.negated) { + builder.append('!'); + } + builder.append('='); + builder.append(this.value); + } else { + if (this.negated) { + builder.append('!'); + } + builder.append(this.name); + } + return builder.toString(); + } + +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/CompositeHttpRequestMatcher.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/CompositeHttpRequestMatcher.java new file mode 100644 index 00000000..181edf2b --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/CompositeHttpRequestMatcher.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.HttpRequest; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +/** + * Composite {@link HttpRequestMatcher} implementation + * + * @author Mercy + */ +public abstract class CompositeHttpRequestMatcher extends AbstractHttpRequestMatcher { + + private final List matchers = new LinkedList<>(); + + public CompositeHttpRequestMatcher(HttpRequestMatcher... matchers) { + this.matchers.addAll(Arrays.asList(matchers)); + } + + public CompositeHttpRequestMatcher and(HttpRequestMatcher matcher) { + this.matchers.add(matcher); + return this; + } + + @Override + public boolean match(HttpRequest request) { + for (HttpRequestMatcher matcher : matchers) { + if (!matcher.match(request)) { + return false; + } + } + return true; + } + + protected List getMatchers() { + return this.matchers; + } + + @Override + protected Collection getContent() { + List content = new LinkedList<>(); + for (HttpRequestMatcher matcher : getMatchers()) { + if (matcher instanceof AbstractHttpRequestMatcher) { + content.addAll(((AbstractHttpRequestMatcher) matcher).getContent()); + } + } + return content; + } + + @Override + protected String getToStringInfix() { + return " && "; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ConsumeMediaTypeExpression.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ConsumeMediaTypeExpression.java new file mode 100644 index 00000000..3fe1f016 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ConsumeMediaTypeExpression.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.MediaType; + +/** + * Parses and matches a single media type expression to a request's 'Content-Type' header. + *

+ * The source code is scratched from + * org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition.ConsumeMediaTypeExpression + * + * @author Rossen Stoyanchev + * @author Arjen Poutsma + */ +class ConsumeMediaTypeExpression extends AbstractMediaTypeExpression { + + ConsumeMediaTypeExpression(String expression) { + super(expression); + } + + ConsumeMediaTypeExpression(MediaType mediaType, boolean negated) { + super(mediaType, negated); + } + + public final boolean match(MediaType contentType) { + boolean match = getMediaType().includes(contentType); + return (!isNegated() ? match : !match); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HeaderExpression.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HeaderExpression.java new file mode 100644 index 00000000..d831a3b9 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HeaderExpression.java @@ -0,0 +1,60 @@ +/* + * 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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.util.ObjectUtils; + +/** + * Parses and matches a single header expression to a request. + *

+ * The some source code is scratched from org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression + * + * @author Arjen Poutsma + * @author Rossen Stoyanchev + * @author Mercy + */ +class HeaderExpression extends AbstractNameValueExpression { + + HeaderExpression(String expression) { + super(expression); + } + + @Override + protected boolean isCaseSensitiveName() { + return false; + } + + @Override + protected String parseValue(String valueExpression) { + return valueExpression; + } + + @Override + protected boolean matchName(HttpRequest request) { + HttpHeaders httpHeaders = request.getHeaders(); + return httpHeaders.containsKey(this.name); + } + + @Override + protected boolean matchValue(HttpRequest request) { + HttpHeaders httpHeaders = request.getHeaders(); + String headerValue = httpHeaders.getFirst(this.name); + return ObjectUtils.nullSafeEquals(this.value, headerValue); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestConsumersMatcher.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestConsumersMatcher.java new file mode 100644 index 00000000..e4d8296a --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestConsumersMatcher.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.MediaType; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * {@link HttpRequest} 'Content-Type' header {@link HttpRequestMatcher matcher} + * + * @author Arjen Poutsma + * @author Rossen Stoyanchev + * @author Mercy + */ +public class HttpRequestConsumersMatcher extends AbstractHttpRequestMatcher { + + private final List expressions; + + /** + * Creates a new instance from 0 or more "consumes" expressions. + * + * @param consumes consumes expressions if 0 expressions are provided, + * the condition will match to every request + */ + public HttpRequestConsumersMatcher(String... consumes) { + this(consumes, null); + } + + /** + * Creates a new instance with "consumes" and "header" expressions. + * "Header" expressions where the header name is not 'Content-Type' or have + * no header value defined are ignored. If 0 expressions are provided in + * total, the condition will match to every request + * + * @param consumes consumes expressions + * @param headers headers expressions + */ + public HttpRequestConsumersMatcher(String[] consumes, String[] headers) { + this(parseExpressions(consumes, headers)); + } + + /** + * Private constructor accepting parsed media type expressions. + */ + private HttpRequestConsumersMatcher(Collection expressions) { + this.expressions = new ArrayList<>(expressions); + Collections.sort(this.expressions); + } + + @Override + public boolean match(HttpRequest request) { + + if (expressions.isEmpty()) { + return true; + } + + HttpHeaders httpHeaders = request.getHeaders(); + + MediaType contentType = httpHeaders.getContentType(); + + if (contentType == null) { + contentType = MediaType.APPLICATION_OCTET_STREAM; + } + + for (ConsumeMediaTypeExpression expression : expressions) { + if (!expression.match(contentType)) { + return false; + } + } + + return true; + } + + private static Set parseExpressions(String[] consumes, String[] headers) { + Set result = new LinkedHashSet<>(); + if (headers != null) { + for (String header : headers) { + HeaderExpression expr = new HeaderExpression(header); + if ("Content-Type".equalsIgnoreCase(expr.name) && expr.value != null) { + for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) { + result.add(new ConsumeMediaTypeExpression(mediaType, expr.negated)); + } + } + } + } + for (String consume : consumes) { + result.add(new ConsumeMediaTypeExpression(consume)); + } + return result; + } + + @Override + protected Collection getContent() { + return this.expressions; + } + + @Override + protected String getToStringInfix() { + return " || "; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestHeadersMatcher.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestHeadersMatcher.java new file mode 100644 index 00000000..83577bb5 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestHeadersMatcher.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * {@link HttpRequest} headers {@link HttpRequestMatcher matcher} + * + * @author Mercy + */ +public class HttpRequestHeadersMatcher extends AbstractHttpRequestMatcher { + + private final Set expressions; + + public HttpRequestHeadersMatcher(String... headers) { + this.expressions = parseExpressions(headers); + } + + private static Set parseExpressions(String... headers) { + Set expressions = new LinkedHashSet<>(); + for (String header : headers) { + HeaderExpression expr = new HeaderExpression(header); + if (HttpHeaders.ACCEPT.equalsIgnoreCase(expr.name) || + HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(expr.name)) { + continue; + } + expressions.add(expr); + } + return expressions; + } + + @Override + public boolean match(HttpRequest request) { + for (HeaderExpression expression : this.expressions) { + if (!expression.match(request)) { + return false; + } + } + return true; + } + + @Override + protected Collection getContent() { + return this.expressions; + } + + @Override + protected String getToStringInfix() { + return " && "; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMatcher.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMatcher.java new file mode 100644 index 00000000..ddc395e7 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMatcher.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.HttpRequest; + +/** + * {@link HttpRequest} Matcher + * + * @author Mercy + */ +public interface HttpRequestMatcher { + + /** + * Match {@link HttpRequest} or not + * + * @param request The {@link HttpRequest} instance + * @return if matched, return true, or false. + */ + boolean match(HttpRequest request); +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMethodsMatcher.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMethodsMatcher.java new file mode 100644 index 00000000..dd130b87 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMethodsMatcher.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +import static org.springframework.http.HttpMethod.resolve; + +/** + * {@link HttpRequest} {@link HttpMethod methods} {@link HttpRequestMatcher matcher} + * + * @author Mercy + */ +public class HttpRequestMethodsMatcher extends AbstractHttpRequestMatcher { + + private final Set methods; + + public HttpRequestMethodsMatcher(String... methods) { + this.methods = resolveHttpMethods(methods); + } + + private Set resolveHttpMethods(String[] methods) { + Set httpMethods = new LinkedHashSet<>(methods.length); + for (String method : methods) { + if (!StringUtils.hasText(method)) { + continue; + } + HttpMethod httpMethod = resolve(method); + httpMethods.add(httpMethod); + } + return httpMethods; + } + + public Set getMethods() { + return methods; + } + + @Override + public boolean match(HttpRequest request) { + boolean matched = false; + HttpMethod httpMethod = request.getMethod(); + if (httpMethod != null) { + for (HttpMethod method : getMethods()) { + if (httpMethod.equals(method)) { + matched = true; + break; + } + } + } + return matched; + } + + @Override + protected Collection getContent() { + return methods; + } + + @Override + protected String getToStringInfix() { + return " || "; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestParamsMatcher.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestParamsMatcher.java new file mode 100644 index 00000000..72137075 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestParamsMatcher.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.HttpRequest; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * {@link HttpRequest} parameters {@link HttpRequestMatcher matcher} + * + * @author Mercy + */ +public class HttpRequestParamsMatcher extends AbstractHttpRequestMatcher { + + private final Set expressions; + + /** + * @param params The pattern of params : + *

    + *
  • name=value
  • + *
  • name
  • + *
+ */ + public HttpRequestParamsMatcher(String... params) { + this.expressions = parseExpressions(params); + } + + @Override + public boolean match(HttpRequest request) { + for (ParamExpression paramExpression : expressions) { + if (!paramExpression.match(request)) { + return false; + } + } + return true; + } + + private static Set parseExpressions(String... params) { + Set expressions = new LinkedHashSet<>(); + for (String param : params) { + expressions.add(new ParamExpression(param)); + } + return expressions; + } + + @Override + protected Collection getContent() { + return this.expressions; + } + + @Override + protected String getToStringInfix() { + return " && "; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestPathMatcher.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestPathMatcher.java new file mode 100644 index 00000000..a9df2159 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestPathMatcher.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.HttpRequest; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; +import org.springframework.util.StringUtils; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * {@link HttpRequest} {@link URI} {@link HttpRequestMatcher matcher} + * + * @author Mercy + */ +public class HttpRequestPathMatcher extends AbstractHttpRequestMatcher { + + private final Set patterns; + + private final PathMatcher pathMatcher; + + public HttpRequestPathMatcher(String... patterns) { + this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns)); + this.pathMatcher = new AntPathMatcher(); + } + + @Override + public boolean match(HttpRequest request) { + List matches = getMatchingPatterns(request); + return !matches.isEmpty(); + } + + public List getMatchingPatterns(HttpRequest request) { + String path = getPath(request); + List matches = getMatchingPatterns(path); + return matches; + } + + public List getMatchingPatterns(String lookupPath) { + List matches = new ArrayList<>(); + for (String pattern : this.patterns) { + String match = getMatchingPattern(pattern, lookupPath); + if (match != null) { + matches.add(match); + } + } + if (matches.size() > 1) { + matches.sort(this.pathMatcher.getPatternComparator(lookupPath)); + } + return matches; + } + + private String getMatchingPattern(String pattern, String lookupPath) { + if (pattern.equals(lookupPath)) { + return pattern; + } + boolean hasSuffix = pattern.indexOf('.') != -1; + if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) { + return pattern + ".*"; + } + if (this.pathMatcher.match(pattern, lookupPath)) { + return pattern; + } + + if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) { + return pattern + "/"; + } + return null; + } + + private String getPath(HttpRequest request) { + URI uri = request.getURI(); + return uri.getPath(); + } + + private static Set prependLeadingSlash(String[] patterns) { + Set result = new LinkedHashSet<>(patterns.length); + for (String pattern : patterns) { + if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) { + pattern = "/" + pattern; + } + result.add(pattern); + } + return result; + } + + @Override + protected Collection getContent() { + return this.patterns; + } + + @Override + protected String getToStringInfix() { + return " || "; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestProducesMatcher.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestProducesMatcher.java new file mode 100644 index 00000000..b607f122 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestProducesMatcher.java @@ -0,0 +1,119 @@ +/* + * 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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.MediaType; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * {@link HttpRequest} 'Accept' header {@link HttpRequestMatcher matcher} + * + * @author Arjen Poutsma + * @author Rossen Stoyanchev + * @author Mercy + */ +public class HttpRequestProducesMatcher extends AbstractHttpRequestMatcher { + + private final List expressions; + + /** + * Creates a new instance from "produces" expressions. If 0 expressions + * are provided in total, this condition will match to any request. + * + * @param produces produces expressions + */ + public HttpRequestProducesMatcher(String... produces) { + this(produces, null); + } + + /** + * Creates a new instance with "produces" and "header" expressions. "Header" + * expressions where the header name is not 'Accept' or have no header value + * defined are ignored. If 0 expressions are provided in total, this condition + * will match to any request. + * + * @param produces produces expressions + * @param headers headers expressions + */ + public HttpRequestProducesMatcher(String[] produces, String[] headers) { + this(parseExpressions(produces, headers)); + } + + /** + * Private constructor accepting parsed media type expressions. + */ + private HttpRequestProducesMatcher(Collection expressions) { + this.expressions = new ArrayList<>(expressions); + Collections.sort(this.expressions); + } + + @Override + public boolean match(HttpRequest request) { + + if (expressions.isEmpty()) { + return true; + } + + HttpHeaders httpHeaders = request.getHeaders(); + + List acceptedMediaTypes = httpHeaders.getAccept(); + + for (ProduceMediaTypeExpression expression : expressions) { + if (!expression.match(acceptedMediaTypes)) { + return false; + } + } + + return true; + } + + private static Set parseExpressions(String[] produces, String[] headers) { + Set result = new LinkedHashSet<>(); + if (headers != null) { + for (String header : headers) { + HeaderExpression expr = new HeaderExpression(header); + if (HttpHeaders.ACCEPT.equalsIgnoreCase(expr.name) && expr.value != null) { + for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) { + result.add(new ProduceMediaTypeExpression(mediaType, expr.negated)); + } + } + } + } + for (String produce : produces) { + result.add(new ProduceMediaTypeExpression(produce)); + } + return result; + } + + @Override + protected Collection getContent() { + return expressions; + } + + @Override + protected String getToStringInfix() { + return " || "; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/MediaTypeExpression.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/MediaTypeExpression.java new file mode 100644 index 00000000..8597b619 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/MediaTypeExpression.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.MediaType; + +/** + * A contract for media type expressions (e.g. "text/plain", "!text/plain") as + * defined in the for "consumes" and "produces". + *

+ * The source code is scratched from org.springframework.web.servlet.mvc.condition.MediaTypeExpression + * + * @author Rossen Stoyanchev + */ +interface MediaTypeExpression { + + MediaType getMediaType(); + + boolean isNegated(); + +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/NameValueExpression.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/NameValueExpression.java new file mode 100644 index 00000000..4f22cc9c --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/NameValueExpression.java @@ -0,0 +1,38 @@ +/* + * 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.springframework.cloud.alibaba.dubbo.http.matcher; + + +/** + * A contract for {@code "name!=value"} style expression used to specify request + * parameters and request header in HTTP request + *

+ * The some source code is scratched from org.springframework.web.servlet.mvc.condition.NameValueExpression + * + * @param the value type + * @author Rossen Stoyanchev + * @author Mercy + */ +interface NameValueExpression { + + String getName(); + + T getValue(); + + boolean isNegated(); + +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ParamExpression.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ParamExpression.java new file mode 100644 index 00000000..90bfccc5 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ParamExpression.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.HttpRequest; +import org.springframework.util.MultiValueMap; +import org.springframework.util.ObjectUtils; + +import static org.springframework.cloud.alibaba.dubbo.http.util.HttpUtils.getParameters; + +/** + * Parses and matches a single param expression to a request. + *

+ * The some source code is scratched from org.springframework.web.servlet.mvc.condition.ParamsRequestCondition.ParamExpression + * + * @author Arjen Poutsma + * @author Rossen Stoyanchev + * @author Mercy + */ +class ParamExpression extends AbstractNameValueExpression { + + ParamExpression(String expression) { + super(expression); + } + + @Override + protected boolean isCaseSensitiveName() { + return true; + } + + @Override + protected String parseValue(String valueExpression) { + return valueExpression; + } + + @Override + protected boolean matchName(HttpRequest request) { + MultiValueMap parametersMap = getParameters(request); + return parametersMap.containsKey(this.name); + } + + @Override + protected boolean matchValue(HttpRequest request) { + MultiValueMap parametersMap = getParameters(request); + String parameterValue = parametersMap.getFirst(this.name); + return ObjectUtils.nullSafeEquals(this.value, parameterValue); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ProduceMediaTypeExpression.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ProduceMediaTypeExpression.java new file mode 100644 index 00000000..2e223bf9 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ProduceMediaTypeExpression.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.http.MediaType; + +import java.util.List; + +/** + * Parses and matches a single media type expression to a request's 'Accept' header. + *

+ * The source code is scratched from + * org.springframework.web.servlet.mvc.condition.ProducesRequestCondition.ProduceMediaTypeExpression + * + * @author Rossen Stoyanchev + * @author Arjen Poutsma + */ +class ProduceMediaTypeExpression extends AbstractMediaTypeExpression { + + ProduceMediaTypeExpression(String expression) { + super(expression); + } + + ProduceMediaTypeExpression(MediaType mediaType, boolean negated) { + super(mediaType, negated); + } + + public final boolean match(List acceptedMediaTypes) { + boolean match = matchMediaType(acceptedMediaTypes); + return (!isNegated() ? match : !match); + } + + private boolean matchMediaType(List acceptedMediaTypes) { + for (MediaType acceptedMediaType : acceptedMediaTypes) { + if (getMediaType().isCompatibleWith(acceptedMediaType)) { + return true; + } + } + return false; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/RequestMetadataMatcher.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/RequestMetadataMatcher.java new file mode 100644 index 00000000..6e3e9519 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/matcher/RequestMetadataMatcher.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata; + +import static org.springframework.cloud.alibaba.dubbo.http.util.HttpUtils.toNameAndValues; + +/** + * {@link RequestMetadata} {@link HttpRequestMatcher} implementation + * + * @author Mercy + */ +public class RequestMetadataMatcher extends CompositeHttpRequestMatcher { + + public RequestMetadataMatcher(RequestMetadata metadata) { + super( + // method + new HttpRequestMethodsMatcher(metadata.getMethod()), + // url + new HttpRequestPathMatcher(metadata.getPath()), + // params + new HttpRequestParamsMatcher(toNameAndValues(metadata.getParams())), + // headers + new HttpRequestHeadersMatcher(toNameAndValues(metadata.getHeaders())), + // consumes + new HttpRequestConsumersMatcher(metadata.getConsumes().toArray(new String[0])), + // produces + new HttpRequestProducesMatcher(metadata.getProduces().toArray(new String[0])) + ); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/util/HttpUtils.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/util/HttpUtils.java new file mode 100644 index 00000000..41dc0de6 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/util/HttpUtils.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.springframework.cloud.alibaba.dubbo.http.util; + +import org.springframework.http.HttpRequest; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.springframework.util.StringUtils.delimitedListToStringArray; +import static org.springframework.util.StringUtils.trimAllWhitespace; + +/** + * Http Utilities class + * + * @author Mercy + */ +public abstract class HttpUtils { + + private static final String UTF_8 = "UTF-8"; + + private static final String EQUAL = "="; + + private static final String AND = "&"; + + /** + * The empty value + */ + private static final String EMPTY_VALUE = ""; + + /** + * Get Parameters from the specified {@link HttpRequest request} + * + * @param request the specified {@link HttpRequest request} + * @return + */ + public static MultiValueMap getParameters(HttpRequest request) { + URI uri = request.getURI(); + return getParameters(uri.getQuery()); + } + + /** + * Get Parameters from the specified query string. + *

+ * + * @param queryString The query string + * @return The query parameters + */ + public static MultiValueMap getParameters(String queryString) { + MultiValueMap parameters = new LinkedMultiValueMap<>(); + return getParameters(delimitedListToStringArray(queryString, AND)); + } + + /** + * Get Parameters from the specified pairs of name-value. + *

+ * + * @param pairs The pairs of name-value + * @return The query parameters + */ + public static MultiValueMap getParameters(Iterable pairs) { + MultiValueMap parameters = new LinkedMultiValueMap<>(); + if (pairs != null) { + for (String pair : pairs) { + String[] nameAndValue = delimitedListToStringArray(pair, EQUAL); + String name = decode(nameAndValue[0]); + String value = nameAndValue.length < 2 ? null : nameAndValue[1]; + value = decode(value); + addParam(parameters, name, value); + } + } + return parameters; + } + + /** + * Get Parameters from the specified pairs of name-value. + *

+ * + * @param pairs The pairs of name-value + * @return The query parameters + */ + public static MultiValueMap getParameters(String... pairs) { + return getParameters(Arrays.asList(pairs)); + } + + /** + * To the name and value line sets + * + * @param nameAndValuesMap {@link MultiValueMap} the map of name and values + * @return non-null + */ + public static Set toNameAndValuesSet(Map> nameAndValuesMap) { + Set nameAndValues = new LinkedHashSet<>(); + for (Map.Entry> entry : nameAndValuesMap.entrySet()) { + String name = entry.getKey(); + List values = entry.getValue(); + for (String value : values) { + String nameAndValue = name + EQUAL + value; + nameAndValues.add(nameAndValue); + } + } + return nameAndValues; + } + + public static String[] toNameAndValues(Map> nameAndValuesMap) { + return toNameAndValuesSet(nameAndValuesMap).toArray(new String[0]); + } + + /** + * Generate a string of query string from the specified request parameters {@link Map} + * + * @param params the specified request parameters {@link Map} + * @return non-null + */ + public static String toQueryString(Map> params) { + StringBuilder builder = new StringBuilder(); + for (String line : toNameAndValuesSet(params)) { + builder.append(line).append(AND); + } + return builder.toString(); + } + + /** + * Decode value + * + * @param value the value requires to decode + * @return the decoded value + */ + public static String decode(String value) { + if (value == null) { + return value; + } + String decodedValue = value; + try { + decodedValue = URLDecoder.decode(value, UTF_8); + } catch (UnsupportedEncodingException ex) { + } + return decodedValue; + } + + /** + * encode value + * + * @param value the value requires to encode + * @return the encoded value + */ + public static String encode(String value) { + String encodedValue = value; + try { + encodedValue = URLEncoder.encode(value, UTF_8); + } catch (UnsupportedEncodingException ex) { + } + return encodedValue; + } + + private static void addParam(MultiValueMap paramsMap, String name, String value) { + String paramValue = trimAllWhitespace(value); + if (!StringUtils.hasText(paramValue)) { + paramValue = EMPTY_VALUE; + } + paramsMap.add(trimAllWhitespace(name), paramValue); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodMetadata.java index 18abf0e9..ec62c8a5 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodMetadata.java @@ -17,6 +17,8 @@ package org.springframework.cloud.alibaba.dubbo.metadata; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.ClassUtils; import java.lang.reflect.Method; @@ -32,10 +34,12 @@ import java.util.Objects; * * @author Mercy */ +@JsonInclude(JsonInclude.Include.NON_NULL) public class MethodMetadata { private String name; + @JsonProperty("return-type") private String returnType; private List params; diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodParameterMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodParameterMetadata.java index fbcaad00..a7f58e46 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodParameterMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodParameterMetadata.java @@ -16,6 +16,8 @@ */ package org.springframework.cloud.alibaba.dubbo.metadata; +import com.fasterxml.jackson.annotation.JsonInclude; + import java.lang.reflect.Method; import java.util.Objects; @@ -24,6 +26,7 @@ import java.util.Objects; * * @author Mercy */ +@JsonInclude(JsonInclude.Include.NON_NULL) public class MethodParameterMetadata { private int index; diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadata.java index ae3d10dc..56b7c9a5 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadata.java @@ -16,11 +16,26 @@ */ package org.springframework.cloud.alibaba.dubbo.metadata; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import feign.RequestTemplate; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.Objects; +import java.util.Set; +import java.util.SortedMap; + +import static org.springframework.http.MediaType.parseMediaTypes; /** * Request Metadata @@ -31,20 +46,26 @@ public class RequestMetadata { private String method; - private String url; + private String path; - private Map> queries; + @JsonProperty("params") + private MultiValueMap params = new LinkedMultiValueMap<>(); - private Map> headers; + @JsonProperty("headers") + private HttpHeaders headers = new HttpHeaders(); + + private List consumes = new LinkedList<>(); + + private List produces = new LinkedList<>(); public RequestMetadata() { } public RequestMetadata(RequestTemplate requestTemplate) { - this.method = requestTemplate.method(); - this.url = requestTemplate.url(); - this.queries = requestTemplate.queries(); - this.headers = requestTemplate.headers(); + setMethod(requestTemplate.method()); + setPath(requestTemplate.url()); + params(requestTemplate.queries()); + headers(requestTemplate.headers()); } public String getMethod() { @@ -52,46 +73,180 @@ public class RequestMetadata { } public void setMethod(String method) { - this.method = method; + this.method = method.toUpperCase(); } - public String getUrl() { - return url; + public String getPath() { + return path; } - public void setUrl(String url) { - this.url = url; + public void setPath(String path) { + this.path = path; } - public Map> getHeaders() { + public MultiValueMap getParams() { + return params; + } + + public void setParams(Map> params) { + params(params); + } + + public Map> getHeaders() { return headers; } - public void setHeaders(Map> headers) { - this.headers = headers; + public void setHeaders(Map> headers) { + headers(headers); } - public Map> getQueries() { - return queries; + public List getConsumes() { + return consumes; } - public void setQueries(Map> queries) { - this.queries = queries; + public void setConsumes(List consumes) { + this.consumes = consumes; + } + + public List getProduces() { + return produces; + } + + public void setProduces(List produces) { + this.produces = produces; + } + + // @JsonIgnore properties + @JsonIgnore + public Set getParamNames() { + return params.keySet(); + } + + @JsonIgnore + public Set getHeaderNames() { + return headers.keySet(); + } + + @JsonIgnore + public List getConsumeMediaTypes() { + return toMediaTypes(consumes); + } + + @JsonIgnore + public List getProduceMediaTypes() { + return toMediaTypes(produces); + } + + public RequestMetadata addParam(String name, String value) { + add(name, value, this.params); + return this; + } + + public RequestMetadata addHeader(String name, String value) { + add(name, value, this.headers); + return this; + } + + private > RequestMetadata params(Map params) { + addAll(params, this.params); + return this; + } + + private > RequestMetadata headers(Map headers) { + HttpHeaders httpHeaders = new HttpHeaders(); + // Add all headers + addAll(headers, httpHeaders); + // Handles "Content-Type" and "Accept" headers if present + mediaTypes(httpHeaders, HttpHeaders.CONTENT_TYPE, this.consumes); + mediaTypes(httpHeaders, HttpHeaders.ACCEPT, this.produces); + this.headers.putAll(httpHeaders); + return this; + } + + /** + * Get the best matched {@link RequestMetadata} via specified {@link RequestMetadata} + * + * @param requestMetadataMap the source of {@link NavigableMap} + * @param requestMetadata the match object + * @return if not matched, return null + */ + public static RequestMetadata getBestMatch(NavigableMap requestMetadataMap, + RequestMetadata requestMetadata) { + + RequestMetadata key = requestMetadata; + + RequestMetadata result = requestMetadataMap.get(key); + + if (result == null) { + SortedMap headMap = requestMetadataMap.headMap(key, true); + result = headMap.isEmpty() ? null : requestMetadataMap.get(headMap.lastKey()); + } + + return result; + } + + private static void add(String key, String value, MultiValueMap destination) { + destination.add(key, value); + } + + private static > void addAll(Map source, + MultiValueMap destination) { + for (Map.Entry entry : source.entrySet()) { + String key = entry.getKey(); + for (String value : entry.getValue()) { + add(key, value, destination); + } + } + } + + private static void mediaTypes(HttpHeaders httpHeaders, String headerName, List destination) { + List value = httpHeaders.get(headerName); + List mediaTypes = parseMediaTypes(value); + destination.addAll(toMediaTypeValues(mediaTypes)); + } + + private static List toMediaTypeValues(List mediaTypes) { + List list = new ArrayList<>(mediaTypes.size()); + for (MediaType mediaType : mediaTypes) { + list.add(mediaType.toString()); + } + return list; + } + + private static List toMediaTypes(List mediaTypeValues) { + if (mediaTypeValues.isEmpty()) { + return Collections.singletonList(MediaType.ALL); + } + return parseMediaTypes(mediaTypeValues); } @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof RequestMetadata)) return false; RequestMetadata that = (RequestMetadata) o; return Objects.equals(method, that.method) && - Objects.equals(url, that.url) && - Objects.equals(queries, that.queries) && - Objects.equals(headers, that.headers); + Objects.equals(path, that.path) && + Objects.equals(params, that.params) && + Objects.equals(headers, that.headers) && + Objects.equals(consumes, that.consumes) && + Objects.equals(produces, that.produces); } @Override public int hashCode() { - return Objects.hash(method, url, queries, headers); + return Objects.hash(method, path, params, headers, consumes, produces); + } + + @Override + public String toString() { + return "RequestMetadata{" + + "method='" + method + '\'' + + ", path='" + path + '\'' + + ", params=" + params + + ", headers=" + headers + + ", consumes=" + consumes + + ", produces=" + produces + + '}'; } } diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RestMethodMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RestMethodMetadata.java index 5024fefa..3f971880 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RestMethodMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RestMethodMetadata.java @@ -16,21 +16,72 @@ */ package org.springframework.cloud.alibaba.dubbo.metadata; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Objects; /** * Method Request Metadata */ +@JsonInclude(JsonInclude.Include.NON_NULL) public class RestMethodMetadata { private MethodMetadata method; private RequestMetadata request; + @JsonProperty("url-index") + private Integer urlIndex; + + @JsonProperty("body-index") + private Integer bodyIndex; + + @JsonProperty("header-map-index") + private Integer headerMapIndex; + + @JsonProperty("query-map-index") + private Integer queryMapIndex; + + @JsonProperty("query-map-encoded") + private boolean queryMapEncoded; + + @JsonProperty("return-type") + private String returnType; + + @JsonProperty("body-type") + private String bodyType; + + @JsonProperty("index-to-name") private Map> indexToName; + @JsonProperty("form-params") + private List formParams; + + @JsonProperty("index-to-encoded") + private Map indexToEncoded; + + public RestMethodMetadata() { + } + + public RestMethodMetadata(feign.MethodMetadata methodMetadata) { + this.request = new RequestMetadata(methodMetadata.template()); + this.urlIndex = methodMetadata.urlIndex(); + this.bodyIndex = methodMetadata.bodyIndex(); + this.headerMapIndex = methodMetadata.headerMapIndex(); + this.queryMapEncoded = methodMetadata.queryMapEncoded(); + this.queryMapEncoded = methodMetadata.queryMapEncoded(); + this.returnType = methodMetadata.returnType() == null ? null : methodMetadata.returnType().toString(); + this.bodyType = methodMetadata.bodyType() == null ? null : methodMetadata.bodyType().toString(); + this.indexToName = methodMetadata.indexToName(); + this.formParams = methodMetadata.formParams(); + this.indexToEncoded = methodMetadata.indexToEncoded(); + } + public MethodMetadata getMethod() { return method; } @@ -55,18 +106,100 @@ public class RestMethodMetadata { this.indexToName = indexToName; } + public Integer getUrlIndex() { + return urlIndex; + } + + public void setUrlIndex(Integer urlIndex) { + this.urlIndex = urlIndex; + } + + public Integer getBodyIndex() { + return bodyIndex; + } + + public void setBodyIndex(Integer bodyIndex) { + this.bodyIndex = bodyIndex; + } + + public Integer getHeaderMapIndex() { + return headerMapIndex; + } + + public void setHeaderMapIndex(Integer headerMapIndex) { + this.headerMapIndex = headerMapIndex; + } + + public Integer getQueryMapIndex() { + return queryMapIndex; + } + + public void setQueryMapIndex(Integer queryMapIndex) { + this.queryMapIndex = queryMapIndex; + } + + public boolean isQueryMapEncoded() { + return queryMapEncoded; + } + + public void setQueryMapEncoded(boolean queryMapEncoded) { + this.queryMapEncoded = queryMapEncoded; + } + + public String getReturnType() { + return returnType; + } + + public void setReturnType(String returnType) { + this.returnType = returnType; + } + + public String getBodyType() { + return bodyType; + } + + public void setBodyType(String bodyType) { + this.bodyType = bodyType; + } + + public List getFormParams() { + return formParams; + } + + public void setFormParams(List formParams) { + this.formParams = formParams; + } + + public Map getIndexToEncoded() { + return indexToEncoded; + } + + public void setIndexToEncoded(Map indexToEncoded) { + this.indexToEncoded = indexToEncoded; + } + @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof RestMethodMetadata)) return false; RestMethodMetadata that = (RestMethodMetadata) o; - return Objects.equals(method, that.method) && + return queryMapEncoded == that.queryMapEncoded && + Objects.equals(method, that.method) && Objects.equals(request, that.request) && - Objects.equals(indexToName, that.indexToName); + Objects.equals(urlIndex, that.urlIndex) && + Objects.equals(bodyIndex, that.bodyIndex) && + Objects.equals(headerMapIndex, that.headerMapIndex) && + Objects.equals(queryMapIndex, that.queryMapIndex) && + Objects.equals(returnType, that.returnType) && + Objects.equals(bodyType, that.bodyType) && + Objects.equals(indexToName, that.indexToName) && + Objects.equals(formParams, that.formParams) && + Objects.equals(indexToEncoded, that.indexToEncoded); } @Override public int hashCode() { - return Objects.hash(method, request, indexToName); + return Objects.hash(method, request, urlIndex, bodyIndex, headerMapIndex, queryMapIndex, queryMapEncoded, + returnType, bodyType, indexToName, formParams, indexToEncoded); } } diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/ServiceRestMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/ServiceRestMetadata.java index d5ba11f4..42efe71d 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/ServiceRestMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/ServiceRestMetadata.java @@ -16,6 +16,9 @@ */ package org.springframework.cloud.alibaba.dubbo.metadata; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.Objects; import java.util.Set; /** @@ -24,6 +27,7 @@ import java.util.Set; * @author Mercy * @see RestMethodMetadata */ +@JsonInclude(JsonInclude.Include.NON_NULL) public class ServiceRestMetadata { private String name; @@ -49,18 +53,14 @@ public class ServiceRestMetadata { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - + if (!(o instanceof ServiceRestMetadata)) return false; ServiceRestMetadata that = (ServiceRestMetadata) o; - - if (name != null ? !name.equals(that.name) : that.name != null) return false; - return meta != null ? meta.equals(that.meta) : that.meta == null; + return Objects.equals(name, that.name) && + Objects.equals(meta, that.meta); } @Override public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (meta != null ? meta.hashCode() : 0); - return result; + return Objects.hash(name, meta); } } diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/repository/DubboServiceMetadataRepository.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/repository/DubboServiceMetadataRepository.java index 3bdafef6..30cb0cdb 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/repository/DubboServiceMetadataRepository.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/repository/DubboServiceMetadataRepository.java @@ -19,22 +19,27 @@ package org.springframework.cloud.alibaba.dubbo.metadata.repository; import com.alibaba.dubbo.config.spring.ReferenceBean; import com.alibaba.dubbo.rpc.service.GenericService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata; +import org.springframework.cloud.alibaba.dubbo.http.matcher.RequestMetadataMatcher; import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata; +import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata; import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata; import org.springframework.cloud.alibaba.dubbo.metadata.service.MetadataConfigService; +import org.springframework.http.HttpRequest; import org.springframework.stereotype.Repository; -import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import static org.springframework.cloud.alibaba.dubbo.http.DefaultHttpRequest.builder; import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceGroup; import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceInterface; import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceSegments; import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceVersion; +import static org.springframework.util.CollectionUtils.isEmpty; /** * Dubbo Service Metadata {@link Repository} @@ -44,43 +49,95 @@ import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegist @Repository public class DubboServiceMetadataRepository { + private final Logger logger = LoggerFactory.getLogger(getClass()); + /** * Key is application name * Value is Map> */ - private Map>> referenceBeansRepository = new HashMap<>(); + private Map>> referenceBeansRepository = newHashMap(); - private Map> methodMetadataRepository = new HashMap<>(); + /** + * Key is application name + * Value is Map + */ + private Map> restMethodMetadataRepository = newHashMap(); @Autowired private MetadataConfigService metadataConfigService; - public void updateMetadata(String serviceName) { + /** + * Initialize the specified service's Dubbo Service Metadata + * + * @param serviceName the service name + */ + public void initialize(String serviceName) { - Map> genericServicesMap = referenceBeansRepository.computeIfAbsent(serviceName, k -> new HashMap<>()); - - Map methodMetadataMap = methodMetadataRepository.computeIfAbsent(serviceName, k -> new HashMap<>()); + if (referenceBeansRepository.containsKey(serviceName)) { + return; + } Set serviceRestMetadataSet = metadataConfigService.getServiceRestMetadata(serviceName); + if (isEmpty(serviceRestMetadataSet)) { + if (logger.isWarnEnabled()) { + logger.warn("The Spring Cloud application[name : {}] does not expose The REST metadata in the Dubbo services." + , serviceName); + } + return; + } + + Map> genericServicesMap = getReferenceBeansMap(serviceName); + + Map restMethodMetadataMap = getRestMethodMetadataMap(serviceName); + for (ServiceRestMetadata serviceRestMetadata : serviceRestMetadataSet) { ReferenceBean referenceBean = adaptReferenceBean(serviceRestMetadata); serviceRestMetadata.getMeta().forEach(restMethodMetadata -> { RequestMetadata requestMetadata = restMethodMetadata.getRequest(); - genericServicesMap.put(requestMetadata, referenceBean); - methodMetadataMap.put(requestMetadata, restMethodMetadata.getMethod()); + RequestMetadataMatcher matcher = new RequestMetadataMatcher(requestMetadata); + genericServicesMap.put(matcher, referenceBean); + restMethodMetadataMap.put(matcher, restMethodMetadata); }); } } public ReferenceBean getReferenceBean(String serviceName, RequestMetadata requestMetadata) { - return getReferenceBeansMap(serviceName).get(requestMetadata); + return match(referenceBeansRepository, serviceName, requestMetadata); } - public MethodMetadata getMethodMetadata(String serviceName, RequestMetadata requestMetadata) { - return getMethodMetadataMap(serviceName).get(requestMetadata); + public RestMethodMetadata getRestMethodMetadata(String serviceName, RequestMetadata requestMetadata) { + return match(restMethodMetadataRepository, serviceName, requestMetadata); + } + + private static T match(Map> repository, String serviceName, + RequestMetadata requestMetadata) { + Map map = repository.get(serviceName); + if (isEmpty(map)) { + return null; + } + RequestMetadataMatcher matcher = new RequestMetadataMatcher(requestMetadata); + T object = map.get(matcher); + if (object == null) { // Can't match exactly + // Require to match one by one + for (Map.Entry entry : map.entrySet()) { + RequestMetadataMatcher possibleMatcher = entry.getKey(); + HttpRequest request = builder() + .method(requestMetadata.getMethod()) + .path(requestMetadata.getPath()) + .params(requestMetadata.getParams()) + .headers(requestMetadata.getHeaders()) + .build(); + + if (possibleMatcher.match(request)) { + object = entry.getValue(); + break; + } + } + } + return object; } private ReferenceBean adaptReferenceBean(ServiceRestMetadata serviceRestMetadata) { @@ -99,11 +156,28 @@ public class DubboServiceMetadataRepository { return referenceBean; } - private Map> getReferenceBeansMap(String serviceName) { - return referenceBeansRepository.getOrDefault(serviceName, Collections.emptyMap()); + private Map> getReferenceBeansMap(String serviceName) { + return getMap(referenceBeansRepository, serviceName); } - private Map getMethodMetadataMap(String serviceName) { - return methodMetadataRepository.getOrDefault(serviceName, Collections.emptyMap()); + private Map getRestMethodMetadataMap(String serviceName) { + return getMap(restMethodMetadataRepository, serviceName); + } + + private static Map getMap(Map> repository, String key) { + return getOrDefault(repository, key, newHashMap()); + } + + private static V getOrDefault(Map source, K key, V defaultValue) { + V value = source.get(key); + if (value == null) { + value = defaultValue; + source.put(key, value); + } + return value; + } + + private static Map newHashMap() { + return new LinkedHashMap<>(); } } diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboServiceBeanMetadataResolver.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboServiceBeanMetadataResolver.java index 2b6f0404..da42e187 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboServiceBeanMetadataResolver.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboServiceBeanMetadataResolver.java @@ -27,7 +27,6 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.SmartInitializingSingleton; -import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata; import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata; import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata; import org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry; @@ -162,13 +161,8 @@ public class DubboServiceBeanMetadataResolver implements BeanClassLoaderAware, S List feignContractMethods) { String configKey = methodMetadata.configKey(); Method feignContractMethod = getMatchedFeignContractMethod(targetType, feignContractMethods, configKey); - - RestMethodMetadata metadata = new RestMethodMetadata(); - - metadata.setRequest(new RequestMetadata(methodMetadata.template())); + RestMethodMetadata metadata = new RestMethodMetadata(methodMetadata); metadata.setMethod(new org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata(feignContractMethod)); - metadata.setIndexToName(methodMetadata.indexToName()); - return metadata; } diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/ParameterResolver.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/ParameterResolver.java new file mode 100644 index 00000000..7f0b9da4 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/ParameterResolver.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.springframework.cloud.alibaba.dubbo.metadata.resolver; + +import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata; +import org.springframework.cloud.alibaba.dubbo.metadata.MethodParameterMetadata; +import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata; +import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata; +import org.springframework.http.HttpRequest; +import org.springframework.util.CollectionUtils; +import org.springframework.web.util.UriComponents; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Parameter Resolver + * + * @author Mercy + */ +public class ParameterResolver { + + + public Object[] resolveParameters(RestMethodMetadata restMethodMetadata, HttpRequest request, UriComponents uriComponents) { + + MethodMetadata methodMetadata = restMethodMetadata.getMethod(); + + RequestMetadata requestMetadata = restMethodMetadata.getRequest(); + + Map> indexToName = restMethodMetadata.getIndexToName(); + + List params = methodMetadata.getParams(); + + Object[] parameters = new Object[params.size()]; + + for (MethodParameterMetadata parameterMetadata : params) { + + int index = parameterMetadata.getIndex(); + + String name = getName(indexToName, index); + + parameters[index] = getValue(requestMetadata, request, uriComponents, name); + + } + + return parameters; + } + + private String getValue(RequestMetadata requestMetadata, HttpRequest request, UriComponents uriComponents, String name) { + + String value = null; + Set paramNames = requestMetadata.getParamNames(); + Set headerNames = requestMetadata.getHeaderNames(); + + if (paramNames.contains(name)) { + value = uriComponents.getQueryParams().getFirst(name); + } else if (headerNames.contains(name)) { + value = request.getHeaders().getFirst(name); + } + + return value; + } + + private String getName(Map> indexToName, int index) { + Collection names = indexToName.get(index); + String name = null; + if (!CollectionUtils.isEmpty(names)) { + Iterator iterator = names.iterator(); + while (iterator.hasNext()) { + name = iterator.next(); // choose the last one if more than one + } + } + return name; + } + + public String[] resolveParameterTypes(MethodMetadata methodMetadata) { + + List params = methodMetadata.getParams(); + + String[] parameterTypes = new String[params.size()]; + + for (MethodParameterMetadata parameterMetadata : params) { + int index = parameterMetadata.getIndex(); + String parameterType = parameterMetadata.getType(); + parameterTypes[index] = parameterType; + } + + return parameterTypes; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboInvocationHandlerFactory.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboInvocationHandlerFactory.java deleted file mode 100644 index 4df43ed2..00000000 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboInvocationHandlerFactory.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.alibaba.dubbo.openfeign; - -import com.alibaba.dubbo.config.spring.ReferenceBean; -import com.alibaba.dubbo.rpc.service.GenericService; - -import feign.Contract; -import feign.InvocationHandlerFactory; -import feign.MethodMetadata; -import feign.Target; -import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata; -import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import static feign.Feign.configKey; - -/** - * Dubbo {@link InvocationHandlerFactory} - * - * @author Mercy - */ -public class DubboInvocationHandlerFactory implements InvocationHandlerFactory { - - private final static InvocationHandlerFactory DEFAULT_INVOCATION_HANDLER_FACTORY = - new InvocationHandlerFactory.Default(); - - private final Contract contract; - - private final DubboServiceMetadataRepository dubboServiceRepository; - - public DubboInvocationHandlerFactory(Contract contract, DubboServiceMetadataRepository dubboServiceRepository) { - this.contract = contract; - this.dubboServiceRepository = dubboServiceRepository; - } - - @Override - public InvocationHandler create(Target target, Map dispatch) { - // The target class annotated @FeignClient - Class targetType = target.type(); - // Resolve metadata from current @FeignClient type - Map methodRequestMetadataMap = resolveMethodRequestMetadataMap(targetType, dispatch.keySet()); - // @FeignClient specifies the service name - String serviceName = target.name(); - // Update specified metadata - dubboServiceRepository.updateMetadata(serviceName); - - Map genericServicesMap = new HashMap<>(); - - Map methodMetadataMap = new HashMap<>(); - - methodRequestMetadataMap.forEach((method, requestMetadata) -> { - ReferenceBean referenceBean = dubboServiceRepository.getReferenceBean(serviceName, requestMetadata); - org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata methodMetadata = - dubboServiceRepository.getMethodMetadata(serviceName, requestMetadata); - genericServicesMap.put(method, referenceBean.get()); - methodMetadataMap.put(method, methodMetadata); - }); - - InvocationHandler defaultInvocationHandler = DEFAULT_INVOCATION_HANDLER_FACTORY.create(target, dispatch); - - DubboInvocationHandler invocationHandler = new DubboInvocationHandler(genericServicesMap, methodMetadataMap, - defaultInvocationHandler); - - return invocationHandler; - } - - private Map resolveMethodRequestMetadataMap(Class targetType, Set methods) { - Map requestMetadataMap = resolveRequestMetadataMap(targetType); - return methods.stream().collect(Collectors.toMap(method -> method, method -> - requestMetadataMap.get(configKey(targetType, method)) - )); - } - - private Map resolveRequestMetadataMap(Class targetType) { - return contract.parseAndValidatateMetadata(targetType) - .stream().collect(Collectors.toMap(MethodMetadata::configKey, this::requestMetadata)); - } - - private RequestMetadata requestMetadata(MethodMetadata methodMetadata) { - return new RequestMetadata(methodMetadata.template()); - } -} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterInvocationHandler.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterInvocationHandler.java index 22de894c..a86a51d7 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterInvocationHandler.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterInvocationHandler.java @@ -24,6 +24,7 @@ import feign.Contract; import feign.Target; import org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMethodMetadata; import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata; +import org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata; import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository; import org.springframework.cloud.alibaba.dubbo.metadata.resolver.DubboTransportedMethodMetadataResolver; import org.springframework.cloud.openfeign.FeignContext; @@ -48,13 +49,13 @@ class TargeterInvocationHandler implements InvocationHandler { private final Environment environment; - private final DubboServiceMetadataRepository dubboServiceMetadataRepository; + private final DubboServiceMetadataRepository repository; TargeterInvocationHandler(Object bean, Environment environment, - DubboServiceMetadataRepository dubboServiceMetadataRepository) { + DubboServiceMetadataRepository repository) { this.bean = bean; this.environment = environment; - this.dubboServiceMetadataRepository = dubboServiceMetadataRepository; + this.repository = repository; } @Override @@ -107,16 +108,16 @@ class TargeterInvocationHandler implements InvocationHandler { } // Update Metadata - dubboServiceMetadataRepository.updateMetadata(serviceName); + repository.initialize(serviceName); Map methodMetadataMap = new HashMap<>(); Map genericServicesMap = new HashMap<>(); methodRequestMetadataMap.forEach((dubboTransportedMethodMetadata, requestMetadata) -> { - ReferenceBean referenceBean = dubboServiceMetadataRepository.getReferenceBean(serviceName, requestMetadata); - org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata methodMetadata = - dubboServiceMetadataRepository.getMethodMetadata(serviceName, requestMetadata); + ReferenceBean referenceBean = repository.getReferenceBean(serviceName, requestMetadata); + RestMethodMetadata restMethodMetadata = repository.getRestMethodMetadata(serviceName, requestMetadata); + org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata methodMetadata = restMethodMetadata.getMethod(); referenceBean.setProtocol(dubboTransportedMethodMetadata.getProtocol()); referenceBean.setCluster(dubboTransportedMethodMetadata.getCluster()); genericServicesMap.put(dubboTransportedMethodMetadata.getMethod(), referenceBean.get()); diff --git a/spring-cloud-alibaba-dubbo/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-dubbo/src/main/resources/META-INF/spring.factories index c2627f86..b0e37c9c 100644 --- a/spring-cloud-alibaba-dubbo/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-alibaba-dubbo/src/main/resources/META-INF/spring.factories @@ -1,7 +1,8 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboMetadataAutoConfiguration,\ org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboOpenFeignAutoConfiguration,\ - org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboRestMetadataRegistrationAutoConfiguration + org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboRestMetadataRegistrationAutoConfiguration,\ + org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboLoadBalancedRestTemplateAutoConfiguration org.springframework.context.ApplicationContextInitializer=\ org.springframework.cloud.alibaba.dubbo.context.DubboServiceRegistrationApplicationContextInitializer diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java index 8cf9be03..5f254f4c 100644 --- a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java @@ -25,6 +25,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported; import org.springframework.cloud.alibaba.dubbo.service.EchoService; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.annotation.Bean; @@ -32,6 +33,13 @@ import org.springframework.context.annotation.Lazy; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import java.util.HashMap; +import java.util.Map; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; /** * Dubbo Spring Cloud Bootstrap @@ -53,6 +61,10 @@ public class DubboSpringCloudBootstrap { @Lazy private DubboFeignEchoService dubboFeignEchoService; + @Autowired + @LoadBalanced + private RestTemplate restTemplate; + @GetMapping(value = "/dubbo/call/echo") public String dubboEcho(@RequestParam("message") String message) { return echoService.echo(message); @@ -71,16 +83,16 @@ public class DubboSpringCloudBootstrap { @FeignClient("spring-cloud-alibaba-dubbo") public interface FeignEchoService { - @GetMapping(value = "/echo") + @GetMapping(value = "/echo", consumes = APPLICATION_JSON_VALUE) String echo(@RequestParam("message") String message); } @FeignClient("spring-cloud-alibaba-dubbo") + @DubboTransported public interface DubboFeignEchoService { - @GetMapping(value = "/echo") - @DubboTransported + @GetMapping(value = "/echo", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_UTF8_VALUE) String echo(@RequestParam("message") String message); } @@ -96,6 +108,26 @@ public class DubboSpringCloudBootstrap { }; } + @Bean + public ApplicationRunner restTemplateRunner() { + return arguments -> { + System.out.println(restTemplate.getForEntity("http://spring-cloud-alibaba-dubbo/echo?message=小马哥", String.class)); + Map data = new HashMap<>(); + data.put("name", "小马哥"); + data.put("age", 33); + data.put("height", 173); + System.out.println(restTemplate.postForEntity("http://spring-cloud-alibaba-dubbo/toString", data, String.class)); + }; + } + + + @Bean + @LoadBalanced + @DubboTransported + public RestTemplate restTemplate() { + return new RestTemplate(); + } + public static void main(String[] args) { new SpringApplicationBuilder(DubboSpringCloudBootstrap.class) .run(args); diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractHttpRequestMatcherTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractHttpRequestMatcherTest.java new file mode 100644 index 00000000..6e792a53 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractHttpRequestMatcherTest.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.junit.Test; + +/** + * {@link AbstractHttpRequestMatcher} Test + * + * @author Mercy + */ +public abstract class AbstractHttpRequestMatcherTest { + + @Test + public abstract void testEqualsAndHashCode(); + + @Test + public abstract void testGetContent(); + + @Test + public abstract void testGetToStringInfix(); + +} diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractMediaTypeExpressionTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractMediaTypeExpressionTest.java new file mode 100644 index 00000000..48588c73 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractMediaTypeExpressionTest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.BeanUtils; +import org.springframework.core.ResolvableType; +import org.springframework.http.MediaType; + +import java.lang.reflect.Constructor; + +/** + * {@link AbstractMediaTypeExpression} Test + * + * @author Mercy + */ +public abstract class AbstractMediaTypeExpressionTest { + + protected T createExpression(String expression) { + ResolvableType resolvableType = ResolvableType.forType(getClass().getGenericSuperclass()); + Class firstGenericType = (Class) resolvableType.resolveGeneric(0); + Constructor constructor = null; + try { + constructor = firstGenericType.getDeclaredConstructor(String.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + return BeanUtils.instantiateClass(constructor, expression); + } + + @Test + public void testGetMediaTypeAndNegated() { + // Normal + AbstractMediaTypeExpression expression = createExpression(MediaType.APPLICATION_JSON_VALUE); + Assert.assertEquals(MediaType.APPLICATION_JSON, expression.getMediaType()); + Assert.assertFalse(expression.isNegated()); + + // Negated + expression = createExpression("!" + MediaType.APPLICATION_JSON_VALUE); + Assert.assertEquals(MediaType.APPLICATION_JSON, expression.getMediaType()); + Assert.assertTrue(expression.isNegated()); + } + + @Test + public void testEqualsAndHashCode() { + Assert.assertEquals(createExpression(MediaType.APPLICATION_JSON_VALUE), createExpression(MediaType.APPLICATION_JSON_VALUE)); + Assert.assertEquals(createExpression(MediaType.APPLICATION_JSON_VALUE).hashCode(), + createExpression(MediaType.APPLICATION_JSON_VALUE).hashCode()); + } + + @Test + public void testCompareTo() { + Assert.assertEquals(0, + createExpression(MediaType.APPLICATION_JSON_VALUE).compareTo(createExpression(MediaType.APPLICATION_JSON_VALUE))); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractNameValueExpressionTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractNameValueExpressionTest.java new file mode 100644 index 00000000..407b7127 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/AbstractNameValueExpressionTest.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.BeanUtils; +import org.springframework.core.ResolvableType; + +import java.lang.reflect.Constructor; + +/** + * {@link AbstractNameValueExpression} Test + * + * @author Mercy + */ +public abstract class AbstractNameValueExpressionTest { + + protected T createExpression(String expression) { + ResolvableType resolvableType = ResolvableType.forType(getClass().getGenericSuperclass()); + Class firstGenericType = (Class) resolvableType.resolveGeneric(0); + Constructor constructor = null; + try { + constructor = firstGenericType.getDeclaredConstructor(String.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + return BeanUtils.instantiateClass(constructor, expression); + } + + @Test + public void testGetNameAndValue() { + // Normal Name and value + AbstractNameValueExpression expression = createExpression("a=1"); + Assert.assertEquals("a", expression.getName()); + Assert.assertFalse(expression.isNegated()); + + expression = createExpression("a=1"); + Assert.assertEquals("a", expression.getName()); + Assert.assertEquals("1", expression.getValue()); + Assert.assertFalse(expression.isNegated()); + + // Negated Name + expression = createExpression("!a"); + Assert.assertEquals("a", expression.getName()); + Assert.assertTrue(expression.isNegated()); + + expression = createExpression("a!=1"); + Assert.assertEquals("a", expression.getName()); + Assert.assertEquals("1", expression.getValue()); + Assert.assertTrue(expression.isNegated()); + } + + @Test + public void testEqualsAndHashCode() { + Assert.assertEquals(createExpression("a"), createExpression("a")); + Assert.assertEquals(createExpression("a").hashCode(), createExpression("a").hashCode()); + Assert.assertEquals(createExpression("a=1"), createExpression("a = 1 ")); + Assert.assertEquals(createExpression("a=1").hashCode(), createExpression("a = 1 ").hashCode()); + Assert.assertNotEquals(createExpression("a"), createExpression("b")); + Assert.assertNotEquals(createExpression("a").hashCode(), createExpression("b").hashCode()); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ConsumeMediaTypeExpressionTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ConsumeMediaTypeExpressionTest.java new file mode 100644 index 00000000..492a9f28 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ConsumeMediaTypeExpressionTest.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.http.MediaType; + +/** + * {@link ConsumeMediaTypeExpression} Test + * + * @author Mercy + */ +public class ConsumeMediaTypeExpressionTest extends AbstractMediaTypeExpressionTest { + + @Test + public void testMatch() { + ConsumeMediaTypeExpression expression = createExpression(MediaType.ALL_VALUE); + + Assert.assertTrue(expression.match(MediaType.APPLICATION_JSON_UTF8)); + + expression = createExpression(MediaType.APPLICATION_JSON_VALUE); + Assert.assertTrue(expression.match(MediaType.APPLICATION_JSON_UTF8)); + + expression = createExpression(MediaType.APPLICATION_JSON_VALUE + ";q=0.7"); + Assert.assertTrue(expression.match(MediaType.APPLICATION_JSON_UTF8)); + + expression = createExpression(MediaType.TEXT_HTML_VALUE); + Assert.assertFalse(expression.match(MediaType.APPLICATION_JSON_UTF8)); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HeaderExpressionTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HeaderExpressionTest.java new file mode 100644 index 00000000..1d2c7a76 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HeaderExpressionTest.java @@ -0,0 +1,60 @@ +/* + * 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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.http.HttpRequest; + +import static org.springframework.cloud.alibaba.dubbo.http.DefaultHttpRequest.builder; + +/** + * {@link HeaderExpression} Test + * + * @author Mercy + */ +public class HeaderExpressionTest extends AbstractNameValueExpressionTest { + + @Test + public void testIsCaseSensitiveName() { + Assert.assertFalse(createExpression("a=1").isCaseSensitiveName()); + Assert.assertFalse(createExpression("a=!1").isCaseSensitiveName()); + Assert.assertFalse(createExpression("b=1").isCaseSensitiveName()); + } + + @Test + public void testMatch() { + + HeaderExpression expression = createExpression("a=1"); + HttpRequest request = builder().build(); + + Assert.assertFalse(expression.match(request)); + + request = builder().header("a", "").build(); + Assert.assertFalse(expression.match(request)); + + request = builder().header("a", "2").build(); + Assert.assertFalse(expression.match(request)); + + request = builder().header("", "1").build(); + Assert.assertFalse(expression.match(request)); + + request = builder().header("a", "1").build(); + Assert.assertTrue(expression.match(request)); + } + +} diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMethodsMatcherTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMethodsMatcherTest.java new file mode 100644 index 00000000..4d28fc57 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestMethodsMatcherTest.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.junit.Assert; +import org.springframework.http.HttpMethod; + +import java.util.Arrays; +import java.util.HashSet; + +/** + * {@link HttpRequestMethodsMatcher} Test + * + * @author Mercy + */ +public class HttpRequestMethodsMatcherTest extends AbstractHttpRequestMatcherTest { + + HttpRequestMethodsMatcher matcher = new HttpRequestMethodsMatcher("GET"); + + @Override + public void testEqualsAndHashCode() { + Assert.assertEquals(new HashSet<>(Arrays.asList(HttpMethod.GET)), matcher.getMethods()); + } + + @Override + public void testGetContent() { + Assert.assertEquals(new HashSet<>(Arrays.asList(HttpMethod.GET)), matcher.getContent()); + } + + @Override + public void testGetToStringInfix() { + Assert.assertEquals(" || ", matcher.getToStringInfix()); + } + +} diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestParamsMatcherTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestParamsMatcherTest.java new file mode 100644 index 00000000..36476fae --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/HttpRequestParamsMatcherTest.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.mock.http.client.MockClientHttpRequest; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +/** + * {@link HttpRequestParamsMatcher} Test + * + * @author Mercy + */ +public class HttpRequestParamsMatcherTest { + +// @Test +// public void testGetParams() { +// +// HttpRequestParamsMatcher matcher = new HttpRequestParamsMatcher( +// "a ", +// "a =1", +// "b = 2", +// "b = 3 ", +// " c = 4 ", +// " d" +// ); +// +// Map> params = matcher.getParams(); +// Assert.assertEquals(4, params.size()); +// Assert.assertTrue(params.containsKey("a")); +// Assert.assertTrue(params.containsKey("b")); +// Assert.assertTrue(params.containsKey("c")); +// Assert.assertTrue(params.containsKey("d")); +// Assert.assertFalse(params.containsKey("e")); +// +// List values = params.get("a"); +// Assert.assertEquals(2, values.size()); +// Assert.assertEquals("", values.get(0)); +// Assert.assertEquals("1", values.get(1)); +// +// values = params.get("b"); +// Assert.assertEquals(2, values.size()); +// Assert.assertEquals("2", values.get(0)); +// Assert.assertEquals("3", values.get(1)); +// +// values = params.get("c"); +// Assert.assertEquals(1, values.size()); +// Assert.assertEquals("4", values.get(0)); +// +// values = params.get("d"); +// Assert.assertEquals(1, values.size()); +// Assert.assertEquals("", values.get(0)); +// } + + @Test + public void testEquals() { + + HttpRequestParamsMatcher matcher = new HttpRequestParamsMatcher("a ", "a = 1"); + + MockClientHttpRequest request = new MockClientHttpRequest(); + + request.setURI(URI.create("http://dummy/?a")); + Assert.assertTrue(matcher.match(request)); + request.setURI(URI.create("http://dummy/?a&a=1")); + Assert.assertTrue(matcher.match(request)); + + matcher = new HttpRequestParamsMatcher("a ", "a =1", "b", "b=2"); + request.setURI(URI.create("http://dummy/?a&a=1&b")); + Assert.assertTrue(matcher.match(request)); + request.setURI(URI.create("http://dummy/?a&a=1&b&b=2")); + Assert.assertTrue(matcher.match(request)); + + matcher = new HttpRequestParamsMatcher("a ", "a =1", "b", "b=2", "b = 3 "); + request.setURI(URI.create("http://dummy/?a&a=1&b&b=2&b=3")); + Assert.assertTrue(matcher.match(request)); + + request.setURI(URI.create("http://dummy/?d=1")); + Assert.assertFalse(matcher.match(request)); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ParamExpressionTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ParamExpressionTest.java new file mode 100644 index 00000000..5cab37ab --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ParamExpressionTest.java @@ -0,0 +1,60 @@ +/* + * 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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.http.HttpRequest; + +import static org.springframework.cloud.alibaba.dubbo.http.DefaultHttpRequest.builder; + +/** + * {@link ParamExpression} Test + * + * @author Mercy + */ +public class ParamExpressionTest extends AbstractNameValueExpressionTest { + + @Test + public void testIsCaseSensitiveName() { + Assert.assertTrue(createExpression("a=1").isCaseSensitiveName()); + Assert.assertTrue(createExpression("a=!1").isCaseSensitiveName()); + Assert.assertTrue(createExpression("b=1").isCaseSensitiveName()); + } + + @Test + public void testMatch() { + + ParamExpression expression = createExpression("a=1"); + HttpRequest request = builder().build(); + + Assert.assertFalse(expression.match(request)); + + request = builder().param("a", "").build(); + Assert.assertFalse(expression.match(request)); + + request = builder().param("a", "2").build(); + Assert.assertFalse(expression.match(request)); + + request = builder().param("", "1").build(); + Assert.assertFalse(expression.match(request)); + + request = builder().param("a", "1").build(); + Assert.assertTrue(expression.match(request)); + } + +} diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ProduceMediaTypeExpressionTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ProduceMediaTypeExpressionTest.java new file mode 100644 index 00000000..6cb033c9 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/matcher/ProduceMediaTypeExpressionTest.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.springframework.cloud.alibaba.dubbo.http.matcher; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.http.MediaType; + +import java.util.Arrays; + +/** + * {@link ProduceMediaTypeExpression} Test + * + * @author Mercy + */ +public class ProduceMediaTypeExpressionTest extends AbstractMediaTypeExpressionTest { + + @Test + public void testMatch() { + ProduceMediaTypeExpression expression = createExpression(MediaType.APPLICATION_JSON_VALUE); + Assert.assertTrue(expression.match(Arrays.asList(MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON))); + + expression = createExpression(MediaType.APPLICATION_JSON_VALUE); + Assert.assertTrue(expression.match(Arrays.asList(MediaType.APPLICATION_XML))); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/util/HttpUtilsTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/util/HttpUtilsTest.java new file mode 100644 index 00000000..789c18b8 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/http/util/HttpUtilsTest.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.springframework.cloud.alibaba.dubbo.http.util; + +import org.junit.Assert; +import org.junit.Test; + +/** + * {@link HttpUtils} Test + * + * @author Mercy + */ +public class HttpUtilsTest { + + @Test + public void testEncodeAndDecode() { + + String whitespace = " "; + + String encodedValue = HttpUtils.encode(" "); + + String decodedValue = HttpUtils.decode(encodedValue); + + Assert.assertEquals(whitespace, decodedValue); + } + +} diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadataTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadataTest.java new file mode 100644 index 00000000..704964e7 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadataTest.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.springframework.cloud.alibaba.dubbo.metadata; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * {@link RequestMetadata} Test + * + * @author Mercy + */ +public class RequestMetadataTest { + + private String method = "GET"; + + private String url = "/echo"; + + private Set paramNames = new LinkedHashSet<>(Arrays.asList("a", "b", "c")); + + private Set headerNames = new LinkedHashSet<>(Arrays.asList("d", "e", "f")); + + @Test + public void testEqualsAndHashCodeAndCompareTo() { + + RequestMetadata metadata = new RequestMetadata(); + RequestMetadata metadata2 = new RequestMetadata(); + + Assert.assertEquals(metadata, metadata2); + Assert.assertEquals(metadata.hashCode(), metadata2.hashCode()); + + metadata.setMethod(method); + metadata2.setMethod(method); + + Assert.assertEquals(metadata, metadata2); + Assert.assertEquals(metadata.hashCode(), metadata2.hashCode()); + + metadata.setPath(url); + metadata2.setPath(url); + + Assert.assertEquals(metadata, metadata2); + Assert.assertEquals(metadata.hashCode(), metadata2.hashCode()); + + metadata.addParam("a", "1").addParam("b", "2").addParam("c", "3"); + metadata2.addParam("a", "1a").addParam("b", "2b").addParam("c", "3c"); + + Assert.assertEquals(metadata, metadata2); + Assert.assertEquals(metadata.hashCode(), metadata2.hashCode()); + + metadata.addHeader("d", "1").addHeader("e", "2").addHeader("f", "3"); + metadata2.addHeader("d", "1").addHeader("e", "2"); + + Assert.assertEquals(metadata, metadata2); + Assert.assertEquals(metadata.hashCode(), metadata2.hashCode()); + } + +// @Test +// public void testBestMatch() { +// +// NavigableMap requestMetadataMap = new TreeMap<>(); +// +// RequestMetadata metadata = new RequestMetadata(); +// metadata.setMethod(method); +// +// RequestMetadata metadata1 = new RequestMetadata(); +// metadata1.setMethod(method); +// metadata1.setPath(url); +// +// RequestMetadata metadata2 = new RequestMetadata(); +// metadata2.setMethod(method); +// metadata2.setPath(url); +// metadata2.setParams(paramNames); +// +// RequestMetadata metadata3 = new RequestMetadata(); +// metadata3.setMethod(method); +// metadata3.setPath(url); +// metadata3.setParams(paramNames); +// metadata3.setHeaders(headerNames); +// +// requestMetadataMap.put(metadata, metadata); +// requestMetadataMap.put(metadata1, metadata1); +// requestMetadataMap.put(metadata2, metadata2); +// requestMetadataMap.put(metadata3, metadata3); +// +// RequestMetadata result = getBestMatch(requestMetadataMap, metadata); +// Assert.assertEquals(result, metadata); +// +// result = getBestMatch(requestMetadataMap, metadata1); +// Assert.assertEquals(result, metadata1); +// +// result = getBestMatch(requestMetadataMap, metadata2); +// Assert.assertEquals(result, metadata2); +// +// result = getBestMatch(requestMetadataMap, metadata3); +// Assert.assertEquals(result, metadata3); +// +// // REDO +// requestMetadataMap.clear(); +// +// requestMetadataMap.put(metadata1, metadata1); +// +// result = getBestMatch(requestMetadataMap, metadata2); +// Assert.assertEquals(metadata1, result); +// +// requestMetadataMap.put(metadata2, metadata2); +// +// result = getBestMatch(requestMetadataMap, metadata3); +// Assert.assertEquals(metadata2, result); +// +// result = getBestMatch(requestMetadataMap, new RequestMetadata()); +// Assert.assertNull(result); +// +// result = getBestMatch(requestMetadataMap, metadata); +// Assert.assertNull(result); +// +// } + +} diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/DefaultEchoService.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/DefaultEchoService.java index e1ad9a9d..b75a01a9 100644 --- a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/DefaultEchoService.java +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/DefaultEchoService.java @@ -19,18 +19,23 @@ package org.springframework.cloud.alibaba.dubbo.service; import com.alibaba.dubbo.config.annotation.Service; import com.alibaba.dubbo.rpc.RpcContext; -import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -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 java.util.Map; + +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; /** * Default {@link EchoService} @@ -43,14 +48,10 @@ import javax.ws.rs.QueryParam; public class DefaultEchoService implements EchoService { @Override - @GetMapping(value = "/echo" -// consumes = MediaType.APPLICATION_JSON_VALUE, -// produces = MediaType.APPLICATION_JSON_UTF8_VALUE - ) + @GetMapping(value = "/echo", produces = APPLICATION_JSON_UTF8_VALUE) @Path("/echo") @GET -// @Consumes("application/json") -// @Produces("application/json;charset=UTF-8") + @Produces(APPLICATION_JSON_UTF8_VALUE) public String echo(@RequestParam @QueryParam("message") String message) { return RpcContext.getContext().getUrl() + " [echo] : " + message; } @@ -60,6 +61,39 @@ public class DefaultEchoService implements EchoService { @Path("/plus") @POST public String plus(@RequestParam @QueryParam("a") int a, @RequestParam @QueryParam("b") int b) { - return null; + return String.valueOf(a + b); + } + + @Override + @PostMapping("/toString") + @Path("/toString") + @POST + public String toString(@RequestBody Map data) { + return data.toString(); + } + + @Override + @GetMapping("/header") + @Path("/header") + @GET + public String header(@RequestHeader("h") @HeaderParam("h") String header) { + return String.valueOf(header); + } + + @Override + @PostMapping("/form") + @Path("/form") + @POST + public String form(@RequestParam("f") @FormParam("f") String form) { + return String.valueOf(form); + } + + @Override + @GetMapping("/paramAndHeader") + @Path("/paramAndHeader") + @GET + public String paramAndHeader(@RequestHeader("h") @HeaderParam("h") @RequestParam("p") @QueryParam("p") String param, + @RequestHeader("h") @HeaderParam("h") String header) { + return param + header; } } diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/EchoService.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/EchoService.java index 10ace8ec..a4d04635 100644 --- a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/EchoService.java +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/EchoService.java @@ -16,6 +16,8 @@ */ package org.springframework.cloud.alibaba.dubbo.service; +import java.util.Map; + /** * Echo Service * @@ -27,4 +29,11 @@ public interface EchoService { String plus(int a, int b); + String toString(Map data); + + String header(String header); + + String form(String form); + + String paramAndHeader(String param, String header); } diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme-zh.md b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme-zh.md index 0a26836b..2a936115 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme-zh.md +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme-zh.md @@ -112,7 +112,7 @@ Consumer端在服务调用之前,先定义限流规则。 根据Provider端中发布的定义,使用Dubbo的@Reference注解注入服务对应的Bean: @Reference(version = "${foo.service.version}", application = "${dubbo.application.id}", - url = "dubbo://localhost:12345", timeout = 30000) + path = "dubbo://localhost:12345", timeout = 30000) private FooService fooService; 由于设置的qps是10。调用15次查看是否被限流: diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme.md b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme.md index 2bfcaec9..a69ea0b1 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme.md +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme.md @@ -110,7 +110,7 @@ Configure rules: Using the `@Reference` annotation to inject service: @Reference(version = "${foo.service.version}", application = "${dubbo.application.id}", - url = "dubbo://localhost:12345", timeout = 30000) + path = "dubbo://localhost:12345", timeout = 30000) private FooService fooService; Because QPS is 10, we can see that flow control takes effect in this invocation: