mirror of
https://gitee.com/mirrors/Spring-Cloud-Alibaba.git
synced 2021-06-26 13:25:11 +08:00
Polish spring-cloud-incubator/spring-cloud-alibaba#348 : @DubboTransported supports RestTemplate (part 1)
This commit is contained in:
@@ -87,7 +87,7 @@ Spring Cloud 使用 Maven 来构建,最快的使用方式是将本项目 clone
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<path>https://repo.spring.io/snapshot</path>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
|
@@ -86,7 +86,7 @@ If you want to use the latest BUILD-SNAPSHOT version, add `Spring Snapshot Repos
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<path>https://repo.spring.io/snapshot</path>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
|
18
pom.xml
18
pom.xml
@@ -22,13 +22,13 @@
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<path>http://www.apache.org/licenses/LICENSE-2.0.txt</path>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-cloud-incubator/spring-cloud-alibaba</url>
|
||||
<path>https://github.com/spring-cloud-incubator/spring-cloud-alibaba</path>
|
||||
<connection>
|
||||
scm:git:git://github.com/spring-cloud-incubator/spring-cloud-alibaba.git
|
||||
</connection>
|
||||
@@ -60,7 +60,7 @@
|
||||
<name>Mercy Ma</name>
|
||||
<email>mercyblitz@gmail.com</email>
|
||||
<organization>Alibaba</organization>
|
||||
<url>https://github.com/mercyblitz</url>
|
||||
<path>https://github.com/mercyblitz</path>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
@@ -246,7 +246,7 @@
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<path>https://repo.spring.io/libs-snapshot-local</path>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
@@ -257,7 +257,7 @@
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<path>https://repo.spring.io/libs-milestone-local</path>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
@@ -265,7 +265,7 @@
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<path>https://repo.spring.io/release</path>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
@@ -275,7 +275,7 @@
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<path>https://repo.spring.io/libs-snapshot-local</path>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
@@ -286,7 +286,7 @@
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<path>https://repo.spring.io/libs-milestone-local</path>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
@@ -294,7 +294,7 @@
|
||||
<pluginRepository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/libs-release-local</url>
|
||||
<path>https://repo.spring.io/libs-release-local</path>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
|
@@ -405,7 +405,7 @@
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<path>https://repo.spring.io/libs-snapshot-local</path>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
@@ -416,7 +416,7 @@
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<path>https://repo.spring.io/libs-milestone-local</path>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
@@ -424,7 +424,7 @@
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<path>https://repo.spring.io/release</path>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
@@ -434,7 +434,7 @@
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<path>https://repo.spring.io/libs-snapshot-local</path>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
@@ -445,7 +445,7 @@
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<path>https://repo.spring.io/libs-milestone-local</path>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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 总文件数量|
|
||||
|
@@ -29,7 +29,7 @@ If you want to use the latest BUILD-SNAPSHOT version, add Spring Snapshot Reposi
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<path>https://repo.spring.io/snapshot</path>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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|
|
||||
|
@@ -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}";
|
||||
}
|
||||
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(RestTemplate.class)
|
||||
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)
|
||||
public class DubboLoadBalancedRestTemplateAutoConfiguration {
|
||||
|
||||
private static final Class<DubboTransported> 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<String, RestTemplate> 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<String, RestTemplate> 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<ClientHttpRequestInterceptor> 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);
|
||||
}
|
||||
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
@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:
|
||||
* <p>
|
||||
* Put <code>restMetadata</code> with the JSON format into
|
||||
* {@link Registration#getMetadata() service instances' metadata}
|
||||
* <p>
|
||||
*
|
||||
* @param event {@link InstancePreRegisteredEvent} instance
|
||||
* Publish <code>serviceRestMetadata</code> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
* @see LoadBalancerInterceptor
|
||||
*/
|
||||
public class DubboAdapterLoadBalancerInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
private final ParameterResolver parameterResolver = new ParameterResolver();
|
||||
|
||||
private final DubboServiceMetadataRepository repository;
|
||||
|
||||
private final LoadBalancerInterceptor loadBalancerInterceptor;
|
||||
|
||||
private final List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
public DubboAdapterLoadBalancerInterceptor(DubboServiceMetadataRepository dubboServiceMetadataRepository,
|
||||
LoadBalancerInterceptor loadBalancerInterceptor,
|
||||
List<HttpMessageConverter<?>> 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<GenericService> 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;
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
class DubboHttpOutputMessage implements HttpOutputMessage {
|
||||
|
||||
@Override
|
||||
public OutputStream getBody() throws IOException {
|
||||
return new FastByteArrayOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return new HttpHeaders();
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
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<String, List<String>> params,
|
||||
Map<String, List<String>> 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<String, List<String>> 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<String, String> params = new LinkedMultiValueMap<>();
|
||||
|
||||
MultiValueMap<String, String> 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<String, List<String>> params) {
|
||||
this.params.putAll(params);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder headers(Map<String, List<String>> headers) {
|
||||
this.headers.putAll(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpRequest build() {
|
||||
return new DefaultHttpRequest(method, path, params, headers);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public abstract class AbstractHttpRequestMatcher implements HttpRequestMatcher {
|
||||
|
||||
/**
|
||||
* Return the discrete items a request condition is composed of.
|
||||
* <p>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.
|
||||
* <p>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();
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class AbstractMediaTypeExpression implements MediaTypeExpression, Comparable<AbstractMediaTypeExpression> {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
abstract class AbstractNameValueExpression<T> implements NameValueExpression<T> {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public abstract class CompositeHttpRequestMatcher extends AbstractHttpRequestMatcher {
|
||||
|
||||
private final List<HttpRequestMatcher> 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<HttpRequestMatcher> getMatchers() {
|
||||
return this.matchers;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<?> getContent() {
|
||||
List<Object> content = new LinkedList<>();
|
||||
for (HttpRequestMatcher matcher : getMatchers()) {
|
||||
if (matcher instanceof AbstractHttpRequestMatcher) {
|
||||
content.addAll(((AbstractHttpRequestMatcher) matcher).getContent());
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getToStringInfix() {
|
||||
return " && ";
|
||||
}
|
||||
}
|
@@ -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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
@@ -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.
|
||||
* <p>
|
||||
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
class HeaderExpression extends AbstractNameValueExpression<String> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class HttpRequestConsumersMatcher extends AbstractHttpRequestMatcher {
|
||||
|
||||
private final List<ConsumeMediaTypeExpression> 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<ConsumeMediaTypeExpression> 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<ConsumeMediaTypeExpression> parseExpressions(String[] consumes, String[] headers) {
|
||||
Set<ConsumeMediaTypeExpression> 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<ConsumeMediaTypeExpression> getContent() {
|
||||
return this.expressions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getToStringInfix() {
|
||||
return " || ";
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class HttpRequestHeadersMatcher extends AbstractHttpRequestMatcher {
|
||||
|
||||
private final Set<HeaderExpression> expressions;
|
||||
|
||||
public HttpRequestHeadersMatcher(String... headers) {
|
||||
this.expressions = parseExpressions(headers);
|
||||
}
|
||||
|
||||
private static Set<HeaderExpression> parseExpressions(String... headers) {
|
||||
Set<HeaderExpression> 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<HeaderExpression> getContent() {
|
||||
return this.expressions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getToStringInfix() {
|
||||
return " && ";
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public interface HttpRequestMatcher {
|
||||
|
||||
/**
|
||||
* Match {@link HttpRequest} or not
|
||||
*
|
||||
* @param request The {@link HttpRequest} instance
|
||||
* @return if matched, return <code>true</code>, or <code>false</code>.
|
||||
*/
|
||||
boolean match(HttpRequest request);
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class HttpRequestMethodsMatcher extends AbstractHttpRequestMatcher {
|
||||
|
||||
private final Set<HttpMethod> methods;
|
||||
|
||||
public HttpRequestMethodsMatcher(String... methods) {
|
||||
this.methods = resolveHttpMethods(methods);
|
||||
}
|
||||
|
||||
private Set<HttpMethod> resolveHttpMethods(String[] methods) {
|
||||
Set<HttpMethod> 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<HttpMethod> 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<HttpMethod> getContent() {
|
||||
return methods;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getToStringInfix() {
|
||||
return " || ";
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class HttpRequestParamsMatcher extends AbstractHttpRequestMatcher {
|
||||
|
||||
private final Set<ParamExpression> expressions;
|
||||
|
||||
/**
|
||||
* @param params The pattern of params :
|
||||
* <ul>
|
||||
* <li>name=value</li>
|
||||
* <li>name</li>
|
||||
* </ul>
|
||||
*/
|
||||
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<ParamExpression> parseExpressions(String... params) {
|
||||
Set<ParamExpression> expressions = new LinkedHashSet<>();
|
||||
for (String param : params) {
|
||||
expressions.add(new ParamExpression(param));
|
||||
}
|
||||
return expressions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<ParamExpression> getContent() {
|
||||
return this.expressions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getToStringInfix() {
|
||||
return " && ";
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class HttpRequestPathMatcher extends AbstractHttpRequestMatcher {
|
||||
|
||||
private final Set<String> 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<String> matches = getMatchingPatterns(request);
|
||||
return !matches.isEmpty();
|
||||
}
|
||||
|
||||
public List<String> getMatchingPatterns(HttpRequest request) {
|
||||
String path = getPath(request);
|
||||
List<String> matches = getMatchingPatterns(path);
|
||||
return matches;
|
||||
}
|
||||
|
||||
public List<String> getMatchingPatterns(String lookupPath) {
|
||||
List<String> 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<String> prependLeadingSlash(String[] patterns) {
|
||||
Set<String> 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<String> getContent() {
|
||||
return this.patterns;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getToStringInfix() {
|
||||
return " || ";
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class HttpRequestProducesMatcher extends AbstractHttpRequestMatcher {
|
||||
|
||||
private final List<ProduceMediaTypeExpression> 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<ProduceMediaTypeExpression> 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<MediaType> acceptedMediaTypes = httpHeaders.getAccept();
|
||||
|
||||
for (ProduceMediaTypeExpression expression : expressions) {
|
||||
if (!expression.match(acceptedMediaTypes)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Set<ProduceMediaTypeExpression> parseExpressions(String[] produces, String[] headers) {
|
||||
Set<ProduceMediaTypeExpression> 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<ProduceMediaTypeExpression> getContent() {
|
||||
return expressions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getToStringInfix() {
|
||||
return " || ";
|
||||
}
|
||||
}
|
@@ -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".
|
||||
* <p>
|
||||
* The source code is scratched from org.springframework.web.servlet.mvc.condition.MediaTypeExpression
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
interface MediaTypeExpression {
|
||||
|
||||
MediaType getMediaType();
|
||||
|
||||
boolean isNegated();
|
||||
|
||||
}
|
@@ -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
|
||||
* <p>
|
||||
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.NameValueExpression
|
||||
*
|
||||
* @param <T> the value type
|
||||
* @author Rossen Stoyanchev
|
||||
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
interface NameValueExpression<T> {
|
||||
|
||||
String getName();
|
||||
|
||||
T getValue();
|
||||
|
||||
boolean isNegated();
|
||||
|
||||
}
|
@@ -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.
|
||||
* <p>
|
||||
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.ParamsRequestCondition.ParamExpression
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
class ParamExpression extends AbstractNameValueExpression<String> {
|
||||
|
||||
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<String, String> parametersMap = getParameters(request);
|
||||
return parametersMap.containsKey(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchValue(HttpRequest request) {
|
||||
MultiValueMap<String, String> parametersMap = getParameters(request);
|
||||
String parameterValue = parametersMap.getFirst(this.name);
|
||||
return ObjectUtils.nullSafeEquals(this.value, parameterValue);
|
||||
}
|
||||
}
|
@@ -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.
|
||||
* <p>
|
||||
* 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<MediaType> acceptedMediaTypes) {
|
||||
boolean match = matchMediaType(acceptedMediaTypes);
|
||||
return (!isNegated() ? match : !match);
|
||||
}
|
||||
|
||||
private boolean matchMediaType(List<MediaType> acceptedMediaTypes) {
|
||||
for (MediaType acceptedMediaType : acceptedMediaTypes) {
|
||||
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
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]))
|
||||
);
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
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<String, String> getParameters(HttpRequest request) {
|
||||
URI uri = request.getURI();
|
||||
return getParameters(uri.getQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Parameters from the specified query string.
|
||||
* <p>
|
||||
*
|
||||
* @param queryString The query string
|
||||
* @return The query parameters
|
||||
*/
|
||||
public static MultiValueMap<String, String> getParameters(String queryString) {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
return getParameters(delimitedListToStringArray(queryString, AND));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Parameters from the specified pairs of name-value.
|
||||
* <p>
|
||||
*
|
||||
* @param pairs The pairs of name-value
|
||||
* @return The query parameters
|
||||
*/
|
||||
public static MultiValueMap<String, String> getParameters(Iterable<String> pairs) {
|
||||
MultiValueMap<String, String> 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.
|
||||
* <p>
|
||||
*
|
||||
* @param pairs The pairs of name-value
|
||||
* @return The query parameters
|
||||
*/
|
||||
public static MultiValueMap<String, String> 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<String> toNameAndValuesSet(Map<String, List<String>> nameAndValuesMap) {
|
||||
Set<String> nameAndValues = new LinkedHashSet<>();
|
||||
for (Map.Entry<String, List<String>> entry : nameAndValuesMap.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
List<String> values = entry.getValue();
|
||||
for (String value : values) {
|
||||
String nameAndValue = name + EQUAL + value;
|
||||
nameAndValues.add(nameAndValue);
|
||||
}
|
||||
}
|
||||
return nameAndValues;
|
||||
}
|
||||
|
||||
public static String[] toNameAndValues(Map<String, List<String>> 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<String, List<String>> 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<String, String> paramsMap, String name, String value) {
|
||||
String paramValue = trimAllWhitespace(value);
|
||||
if (!StringUtils.hasText(paramValue)) {
|
||||
paramValue = EMPTY_VALUE;
|
||||
}
|
||||
paramsMap.add(trimAllWhitespace(name), paramValue);
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class MethodMetadata {
|
||||
|
||||
private String name;
|
||||
|
||||
@JsonProperty("return-type")
|
||||
private String returnType;
|
||||
|
||||
private List<MethodParameterMetadata> params;
|
||||
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class MethodParameterMetadata {
|
||||
|
||||
private int index;
|
||||
|
@@ -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<String, Collection<String>> queries;
|
||||
@JsonProperty("params")
|
||||
private MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
|
||||
|
||||
private Map<String, Collection<String>> headers;
|
||||
@JsonProperty("headers")
|
||||
private HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private List<String> consumes = new LinkedList<>();
|
||||
|
||||
private List<String> 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<String, Collection<String>> getHeaders() {
|
||||
public MultiValueMap<String, String> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public void setParams(Map<String, List<String>> params) {
|
||||
params(params);
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public void setHeaders(Map<String, Collection<String>> headers) {
|
||||
this.headers = headers;
|
||||
public void setHeaders(Map<String, List<String>> headers) {
|
||||
headers(headers);
|
||||
}
|
||||
|
||||
public Map<String, Collection<String>> getQueries() {
|
||||
return queries;
|
||||
public List<String> getConsumes() {
|
||||
return consumes;
|
||||
}
|
||||
|
||||
public void setQueries(Map<String, Collection<String>> queries) {
|
||||
this.queries = queries;
|
||||
public void setConsumes(List<String> consumes) {
|
||||
this.consumes = consumes;
|
||||
}
|
||||
|
||||
public List<String> getProduces() {
|
||||
return produces;
|
||||
}
|
||||
|
||||
public void setProduces(List<String> produces) {
|
||||
this.produces = produces;
|
||||
}
|
||||
|
||||
// @JsonIgnore properties
|
||||
@JsonIgnore
|
||||
public Set<String> getParamNames() {
|
||||
return params.keySet();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Set<String> getHeaderNames() {
|
||||
return headers.keySet();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<MediaType> getConsumeMediaTypes() {
|
||||
return toMediaTypes(consumes);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<MediaType> 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 <T extends Collection<String>> RequestMetadata params(Map<String, T> params) {
|
||||
addAll(params, this.params);
|
||||
return this;
|
||||
}
|
||||
|
||||
private <T extends Collection<String>> RequestMetadata headers(Map<String, T> 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 <code>null</code>
|
||||
*/
|
||||
public static RequestMetadata getBestMatch(NavigableMap<RequestMetadata, RequestMetadata> requestMetadataMap,
|
||||
RequestMetadata requestMetadata) {
|
||||
|
||||
RequestMetadata key = requestMetadata;
|
||||
|
||||
RequestMetadata result = requestMetadataMap.get(key);
|
||||
|
||||
if (result == null) {
|
||||
SortedMap<RequestMetadata, RequestMetadata> headMap = requestMetadataMap.headMap(key, true);
|
||||
result = headMap.isEmpty() ? null : requestMetadataMap.get(headMap.lastKey());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void add(String key, String value, MultiValueMap<String, String> destination) {
|
||||
destination.add(key, value);
|
||||
}
|
||||
|
||||
private static <T extends Collection<String>> void addAll(Map<String, T> source,
|
||||
MultiValueMap<String, String> destination) {
|
||||
for (Map.Entry<String, T> 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<String> destination) {
|
||||
List<String> value = httpHeaders.get(headerName);
|
||||
List<MediaType> mediaTypes = parseMediaTypes(value);
|
||||
destination.addAll(toMediaTypeValues(mediaTypes));
|
||||
}
|
||||
|
||||
private static List<String> toMediaTypeValues(List<MediaType> mediaTypes) {
|
||||
List<String> list = new ArrayList<>(mediaTypes.size());
|
||||
for (MediaType mediaType : mediaTypes) {
|
||||
list.add(mediaType.toString());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static List<MediaType> toMediaTypes(List<String> 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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@@ -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<Integer, Collection<String>> indexToName;
|
||||
|
||||
@JsonProperty("form-params")
|
||||
private List<String> formParams;
|
||||
|
||||
@JsonProperty("index-to-encoded")
|
||||
private Map<Integer, Boolean> 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<String> getFormParams() {
|
||||
return formParams;
|
||||
}
|
||||
|
||||
public void setFormParams(List<String> formParams) {
|
||||
this.formParams = formParams;
|
||||
}
|
||||
|
||||
public Map<Integer, Boolean> getIndexToEncoded() {
|
||||
return indexToEncoded;
|
||||
}
|
||||
|
||||
public void setIndexToEncoded(Map<Integer, Boolean> 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);
|
||||
}
|
||||
}
|
||||
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
@@ -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<RequestMetadata, ReferenceBean<GenericService>>
|
||||
*/
|
||||
private Map<String, Map<RequestMetadata, ReferenceBean<GenericService>>> referenceBeansRepository = new HashMap<>();
|
||||
private Map<String, Map<RequestMetadataMatcher, ReferenceBean<GenericService>>> referenceBeansRepository = newHashMap();
|
||||
|
||||
private Map<String, Map<RequestMetadata, MethodMetadata>> methodMetadataRepository = new HashMap<>();
|
||||
/**
|
||||
* Key is application name
|
||||
* Value is Map<RequestMetadata, RestMethodMetadata>
|
||||
*/
|
||||
private Map<String, Map<RequestMetadataMatcher, RestMethodMetadata>> 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<RequestMetadata, ReferenceBean<GenericService>> genericServicesMap = referenceBeansRepository.computeIfAbsent(serviceName, k -> new HashMap<>());
|
||||
|
||||
Map<RequestMetadata, MethodMetadata> methodMetadataMap = methodMetadataRepository.computeIfAbsent(serviceName, k -> new HashMap<>());
|
||||
if (referenceBeansRepository.containsKey(serviceName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<ServiceRestMetadata> 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<RequestMetadataMatcher, ReferenceBean<GenericService>> genericServicesMap = getReferenceBeansMap(serviceName);
|
||||
|
||||
Map<RequestMetadataMatcher, RestMethodMetadata> restMethodMetadataMap = getRestMethodMetadataMap(serviceName);
|
||||
|
||||
for (ServiceRestMetadata serviceRestMetadata : serviceRestMetadataSet) {
|
||||
|
||||
ReferenceBean<GenericService> 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<GenericService> 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> T match(Map<String, Map<RequestMetadataMatcher, T>> repository, String serviceName,
|
||||
RequestMetadata requestMetadata) {
|
||||
Map<RequestMetadataMatcher, T> 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<RequestMetadataMatcher, T> 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<GenericService> adaptReferenceBean(ServiceRestMetadata serviceRestMetadata) {
|
||||
@@ -99,11 +156,28 @@ public class DubboServiceMetadataRepository {
|
||||
return referenceBean;
|
||||
}
|
||||
|
||||
private Map<RequestMetadata, ReferenceBean<GenericService>> getReferenceBeansMap(String serviceName) {
|
||||
return referenceBeansRepository.getOrDefault(serviceName, Collections.emptyMap());
|
||||
private Map<RequestMetadataMatcher, ReferenceBean<GenericService>> getReferenceBeansMap(String serviceName) {
|
||||
return getMap(referenceBeansRepository, serviceName);
|
||||
}
|
||||
|
||||
private Map<RequestMetadata, MethodMetadata> getMethodMetadataMap(String serviceName) {
|
||||
return methodMetadataRepository.getOrDefault(serviceName, Collections.emptyMap());
|
||||
private Map<RequestMetadataMatcher, RestMethodMetadata> getRestMethodMetadataMap(String serviceName) {
|
||||
return getMap(restMethodMetadataRepository, serviceName);
|
||||
}
|
||||
|
||||
private static <K, V> Map<K, V> getMap(Map<String, Map<K, V>> repository, String key) {
|
||||
return getOrDefault(repository, key, newHashMap());
|
||||
}
|
||||
|
||||
private static <K, V> V getOrDefault(Map<K, V> source, K key, V defaultValue) {
|
||||
V value = source.get(key);
|
||||
if (value == null) {
|
||||
value = defaultValue;
|
||||
source.put(key, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static <K, V> Map<K, V> newHashMap() {
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
}
|
||||
|
@@ -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<Method> 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;
|
||||
}
|
||||
|
||||
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class ParameterResolver {
|
||||
|
||||
|
||||
public Object[] resolveParameters(RestMethodMetadata restMethodMetadata, HttpRequest request, UriComponents uriComponents) {
|
||||
|
||||
MethodMetadata methodMetadata = restMethodMetadata.getMethod();
|
||||
|
||||
RequestMetadata requestMetadata = restMethodMetadata.getRequest();
|
||||
|
||||
Map<Integer, Collection<String>> indexToName = restMethodMetadata.getIndexToName();
|
||||
|
||||
List<MethodParameterMetadata> 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<String> paramNames = requestMetadata.getParamNames();
|
||||
Set<String> 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<Integer, Collection<String>> indexToName, int index) {
|
||||
Collection<String> names = indexToName.get(index);
|
||||
String name = null;
|
||||
if (!CollectionUtils.isEmpty(names)) {
|
||||
Iterator<String> 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<MethodParameterMetadata> 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;
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
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<Method, MethodHandler> dispatch) {
|
||||
// The target class annotated @FeignClient
|
||||
Class<?> targetType = target.type();
|
||||
// Resolve metadata from current @FeignClient type
|
||||
Map<Method, RequestMetadata> methodRequestMetadataMap = resolveMethodRequestMetadataMap(targetType, dispatch.keySet());
|
||||
// @FeignClient specifies the service name
|
||||
String serviceName = target.name();
|
||||
// Update specified metadata
|
||||
dubboServiceRepository.updateMetadata(serviceName);
|
||||
|
||||
Map<Method, GenericService> genericServicesMap = new HashMap<>();
|
||||
|
||||
Map<Method, org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata> methodMetadataMap = new HashMap<>();
|
||||
|
||||
methodRequestMetadataMap.forEach((method, requestMetadata) -> {
|
||||
ReferenceBean<GenericService> 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<Method, RequestMetadata> resolveMethodRequestMetadataMap(Class<?> targetType, Set<Method> methods) {
|
||||
Map<String, RequestMetadata> requestMetadataMap = resolveRequestMetadataMap(targetType);
|
||||
return methods.stream().collect(Collectors.toMap(method -> method, method ->
|
||||
requestMetadataMap.get(configKey(targetType, method))
|
||||
));
|
||||
}
|
||||
|
||||
private Map<String, RequestMetadata> 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());
|
||||
}
|
||||
}
|
@@ -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<Method, org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata> methodMetadataMap = new HashMap<>();
|
||||
|
||||
Map<Method, GenericService> genericServicesMap = new HashMap<>();
|
||||
|
||||
methodRequestMetadataMap.forEach((dubboTransportedMethodMetadata, requestMetadata) -> {
|
||||
ReferenceBean<GenericService> referenceBean = dubboServiceMetadataRepository.getReferenceBean(serviceName, requestMetadata);
|
||||
org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata methodMetadata =
|
||||
dubboServiceMetadataRepository.getMethodMetadata(serviceName, requestMetadata);
|
||||
ReferenceBean<GenericService> 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());
|
||||
|
@@ -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
|
||||
|
@@ -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<String, Object> 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);
|
||||
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public abstract class AbstractHttpRequestMatcherTest<T extends AbstractHttpRequestMatcher> {
|
||||
|
||||
@Test
|
||||
public abstract void testEqualsAndHashCode();
|
||||
|
||||
@Test
|
||||
public abstract void testGetContent();
|
||||
|
||||
@Test
|
||||
public abstract void testGetToStringInfix();
|
||||
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public abstract class AbstractMediaTypeExpressionTest<T extends AbstractMediaTypeExpression> {
|
||||
|
||||
protected T createExpression(String expression) {
|
||||
ResolvableType resolvableType = ResolvableType.forType(getClass().getGenericSuperclass());
|
||||
Class<T> firstGenericType = (Class<T>) resolvableType.resolveGeneric(0);
|
||||
Constructor<T> 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)));
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public abstract class AbstractNameValueExpressionTest<T extends AbstractNameValueExpression> {
|
||||
|
||||
protected T createExpression(String expression) {
|
||||
ResolvableType resolvableType = ResolvableType.forType(getClass().getGenericSuperclass());
|
||||
Class<T> firstGenericType = (Class<T>) resolvableType.resolveGeneric(0);
|
||||
Constructor<T> 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());
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class ConsumeMediaTypeExpressionTest extends AbstractMediaTypeExpressionTest<ConsumeMediaTypeExpression> {
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class HeaderExpressionTest extends AbstractNameValueExpressionTest<HeaderExpression> {
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class HttpRequestMethodsMatcherTest extends AbstractHttpRequestMatcherTest<HttpRequestMethodsMatcher> {
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class HttpRequestParamsMatcherTest {
|
||||
|
||||
// @Test
|
||||
// public void testGetParams() {
|
||||
//
|
||||
// HttpRequestParamsMatcher matcher = new HttpRequestParamsMatcher(
|
||||
// "a ",
|
||||
// "a =1",
|
||||
// "b = 2",
|
||||
// "b = 3 ",
|
||||
// " c = 4 ",
|
||||
// " d"
|
||||
// );
|
||||
//
|
||||
// Map<String, List<String>> 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<String> 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));
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class ParamExpressionTest extends AbstractNameValueExpressionTest<ParamExpression> {
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class ProduceMediaTypeExpressionTest extends AbstractMediaTypeExpressionTest<ProduceMediaTypeExpression> {
|
||||
|
||||
@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)));
|
||||
}
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class HttpUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testEncodeAndDecode() {
|
||||
|
||||
String whitespace = " ";
|
||||
|
||||
String encodedValue = HttpUtils.encode(" ");
|
||||
|
||||
String decodedValue = HttpUtils.decode(encodedValue);
|
||||
|
||||
Assert.assertEquals(whitespace, decodedValue);
|
||||
}
|
||||
|
||||
}
|
@@ -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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
|
||||
*/
|
||||
public class RequestMetadataTest {
|
||||
|
||||
private String method = "GET";
|
||||
|
||||
private String url = "/echo";
|
||||
|
||||
private Set<String> paramNames = new LinkedHashSet<>(Arrays.asList("a", "b", "c"));
|
||||
|
||||
private Set<String> 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<RequestMetadata, RequestMetadata> 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);
|
||||
//
|
||||
// }
|
||||
|
||||
}
|
@@ -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<String, Object> 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;
|
||||
}
|
||||
}
|
||||
|
@@ -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<String, Object> data);
|
||||
|
||||
String header(String header);
|
||||
|
||||
String form(String form);
|
||||
|
||||
String paramAndHeader(String param, String header);
|
||||
}
|
||||
|
@@ -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次查看是否被限流:
|
||||
|
@@ -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:
|
||||
|
Reference in New Issue
Block a user