1
0
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:
mercyblitz
2019-02-19 15:05:25 +08:00
parent 0a4cd85255
commit fc8d8e3628
62 changed files with 3408 additions and 230 deletions

View File

@@ -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>

View File

@@ -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
View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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 总文件数量|

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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|

View File

@@ -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}";
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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 " && ";
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 " || ";
}
}

View File

@@ -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 " && ";
}
}

View File

@@ -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);
}

View File

@@ -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 " || ";
}
}

View File

@@ -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 " && ";
}
}

View File

@@ -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 " || ";
}
}

View File

@@ -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 " || ";
}
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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]))
);
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 +
'}';
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<>();
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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());

View File

@@ -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

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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)));
}
}

View File

@@ -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());
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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());
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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)));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
//
// }
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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次查看是否被限流

View File

@@ -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: