feat: support lane router (#1250)

pull/1255/head
shedfreewu 3 months ago committed by GitHub
parent 9672fd60b3
commit d4c8fff31a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -3,4 +3,5 @@
- [feat: support lossless register and deregister at #977](https://github.com/Tencent/spring-cloud-tencent/pull/1242)
- [feat: PolarisServiceRegistry#deregister support idempotency.](https://github.com/Tencent/spring-cloud-tencent/pull/1243)
- [feat: SCT元数据管理能力与polaris-java元数据管理能力进行下沉及整合](https://github.com/Tencent/spring-cloud-tencent/pull/1249)
- [feat: SCT元数据管理能力与polaris-java元数据管理能力进行下沉及整合](https://github.com/Tencent/spring-cloud-tencent/pull/1249)
- [feat: support lane router](https://github.com/Tencent/spring-cloud-tencent/pull/1250)

@ -19,6 +19,12 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-commons</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
</dependency>
<!-- Spring Cloud Tencent dependencies end -->
<dependency>
@ -56,6 +62,24 @@
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-test-mock-discovery</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-test-common</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -18,31 +18,21 @@
package com.tencent.cloud.metadata.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.netflix.zuul.ZuulFilter;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.metadata.core.DecodeTransferMetadataReactiveFilter;
import com.tencent.cloud.metadata.core.DecodeTransferMetadataServletFilter;
import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMedataScgFilter;
import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientFilter;
import com.tencent.cloud.metadata.core.EncodeTransferMetadataZuulFilter;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataScgEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMetadataZuulEnhancedPlugin;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import static javax.servlet.DispatcherType.ASYNC;
import static javax.servlet.DispatcherType.ERROR;
@ -100,11 +90,12 @@ public class MetadataTransferAutoConfiguration {
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "com.netflix.zuul.http.ZuulServlet")
@ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true)
protected static class MetadataTransferZuulFilterConfig {
@Bean
public ZuulFilter encodeTransferMetadataZuulFilter() {
return new EncodeTransferMetadataZuulFilter();
public EncodeTransferMetadataZuulEnhancedPlugin encodeTransferMedataZuulEnhancedPlugin() {
return new EncodeTransferMetadataZuulEnhancedPlugin();
}
}
@ -114,11 +105,12 @@ public class MetadataTransferAutoConfiguration {
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.GlobalFilter")
@ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true)
protected static class MetadataTransferScgFilterConfig {
@Bean
public GlobalFilter encodeTransferMedataScgFilter() {
return new EncodeTransferMedataScgFilter();
public EncodeTransferMedataScgEnhancedPlugin encodeTransferMedataScgEnhancedPlugin() {
return new EncodeTransferMedataScgEnhancedPlugin();
}
}
@ -127,11 +119,12 @@ public class MetadataTransferAutoConfiguration {
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "feign.Feign")
@ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true)
protected static class MetadataTransferFeignInterceptorConfig {
@Bean
public EncodeTransferMedataFeignInterceptor encodeTransferMedataFeignInterceptor() {
return new EncodeTransferMedataFeignInterceptor();
public EncodeTransferMedataFeignEnhancedPlugin encodeTransferMedataFeignEnhancedPlugin() {
return new EncodeTransferMedataFeignEnhancedPlugin();
}
}
@ -140,23 +133,12 @@ public class MetadataTransferAutoConfiguration {
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
@ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true)
protected static class MetadataTransferRestTemplateConfig {
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor() {
return new EncodeTransferMedataRestTemplateInterceptor();
}
@Bean
public SmartInitializingSingleton addEncodeTransferMetadataInterceptorForRestTemplate(EncodeTransferMedataRestTemplateInterceptor interceptor) {
return () -> restTemplates.forEach(restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
list.add(interceptor);
restTemplate.setInterceptors(list);
});
public EncodeTransferMedataRestTemplateEnhancedPlugin encodeTransferMedataRestTemplateEnhancedPlugin() {
return new EncodeTransferMedataRestTemplateEnhancedPlugin();
}
}
@ -165,20 +147,12 @@ public class MetadataTransferAutoConfiguration {
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.web.reactive.function.client.WebClient")
@ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true)
protected static class MetadataTransferWebClientConfig {
@Autowired(required = false)
private List<WebClient.Builder> webClientBuilder = Collections.emptyList();
@Bean
public EncodeTransferMedataWebClientFilter encodeTransferMedataWebClientFilter() {
return new EncodeTransferMedataWebClientFilter();
}
@Bean
public SmartInitializingSingleton addEncodeTransferMetadataFilterForWebClient(EncodeTransferMedataWebClientFilter filter) {
return () -> webClientBuilder.forEach(webClient -> {
webClient.filter(filter);
});
public EncodeTransferMedataWebClientEnhancedPlugin encodeTransferMedataWebClientEnhancedPlugin() {
return new EncodeTransferMedataWebClientEnhancedPlugin();
}
}
}

@ -18,8 +18,6 @@
package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
@ -27,6 +25,8 @@ import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.metadata.provider.ReactiveMetadataProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
@ -34,12 +34,10 @@ import reactor.core.publisher.Mono;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
@ -62,19 +60,16 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
// Get metadata string from http header.
ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest();
Map<String, String> internalTransitiveMetadata = getIntervalMetadata(serverHttpRequest, CUSTOM_METADATA);
Map<String, String> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(serverWebExchange);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
Map<String, String> internalDisposableMetadata = getIntervalMetadata(serverHttpRequest, CUSTOM_DISPOSABLE_METADATA);
Map<String, String> mergedDisposableMetadata = new HashMap<>(internalDisposableMetadata);
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata);
ReactiveMetadataProvider metadataProvider = new ReactiveMetadataProvider(serverHttpRequest);
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, metadataProvider);
// Save to ServerWebExchange.
serverWebExchange.getAttributes().put(
MetadataConstant.HeaderName.METADATA_CONTEXT,
@ -89,15 +84,7 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
private Map<String, String> getIntervalMetadata(ServerHttpRequest serverHttpRequest, String headerName) {
HttpHeaders httpHeaders = serverHttpRequest.getHeaders();
String customMetadataStr = httpHeaders.getFirst(headerName);
try {
if (StringUtils.hasText(customMetadataStr)) {
customMetadataStr = URLDecoder.decode(customMetadataStr, UTF_8);
}
}
catch (UnsupportedEncodingException e) {
LOG.error("Runtime system does not support utf-8 coding.", e);
}
String customMetadataStr = UrlUtils.decode(httpHeaders.getFirst(headerName));
LOG.debug("Get upstream metadata string: {}", customMetadataStr);
return JacksonUtils.deserialize2Map(customMetadataStr);

@ -19,8 +19,6 @@
package com.tencent.cloud.metadata.core;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
@ -32,15 +30,15 @@ import javax.servlet.http.HttpServletResponse;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.metadata.provider.ServletMetadataProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
@ -65,11 +63,10 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
Map<String, String> internalDisposableMetadata = getInternalMetadata(httpServletRequest, CUSTOM_DISPOSABLE_METADATA);
Map<String, String> mergedDisposableMetadata = new HashMap<>(internalDisposableMetadata);
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata);
ServletMetadataProvider metadataProvider = new ServletMetadataProvider(httpServletRequest);
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, metadataProvider);
TransHeadersTransfer.transfer(httpServletRequest);
try {
@ -83,15 +80,7 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
private Map<String, String> getInternalMetadata(HttpServletRequest httpServletRequest, String headerName) {
// Get custom metadata string from http header.
String customMetadataStr = httpServletRequest.getHeader(headerName);
try {
if (StringUtils.hasText(customMetadataStr)) {
customMetadataStr = URLDecoder.decode(customMetadataStr, UTF_8);
}
}
catch (UnsupportedEncodingException e) {
LOG.error("Runtime system does not support utf-8 coding.", e);
}
String customMetadataStr = UrlUtils.decode(httpServletRequest.getHeader(headerName));
LOG.debug("Get upstream metadata string: {}", customMetadataStr);
// create custom metadata.

@ -0,0 +1,140 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.metadata.core;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.ReflectionUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataType;
import feign.Request;
import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
/**
* Pre EnhancedPlugin for feign to encode transfer metadata.
*
* @author Shedfree Wu
*/
public class EncodeTransferMedataFeignEnhancedPlugin implements EnhancedPlugin {
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.PRE;
}
@Override
public void run(EnhancedPluginContext context) throws Throwable {
if (!(context.getOriginRequest() instanceof Request)) {
return;
}
Request request = (Request) context.getOriginRequest();
// get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
Map<String, String> transHeaders = metadataContext.getTransHeadersKV();
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(request, calleeTransitiveHeaders);
// build custom disposable metadata request header
this.buildMetadataHeader(request, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
// process custom metadata
this.buildMetadataHeader(request, customMetadata, CUSTOM_METADATA);
// set headers that need to be transmitted from the upstream
this.buildTransmittedHeader(request, transHeaders);
}
private void buildTransmittedHeader(Request request, Map<String, String> transHeaders) {
if (!CollectionUtils.isEmpty(transHeaders)) {
Map<String, Collection<String>> headers = getModifiableHeaders(request);
transHeaders.entrySet().stream().forEach(entry -> {
headers.remove(entry.getKey());
headers.put(entry.getKey(), Arrays.asList(entry.getValue()));
});
}
}
/**
* Set metadata into the request header for {@link Request} .
* @param request instance of {@link Request}
* @param metadata metadata map .
* @param headerName target metadata http header name .
*/
private void buildMetadataHeader(Request request, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
buildHeaderMap(request, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
}
}
/**
* Set headerMap into the request header for {@link Request} .
* @param request instance of {@link Request}
* @param headerMap header map .
*/
private void buildHeaderMap(Request request, Map<String, String> headerMap) {
if (!CollectionUtils.isEmpty(headerMap)) {
Map<String, Collection<String>> headers = getModifiableHeaders(request);
headerMap.forEach((key, value) -> headers.put(key, Arrays.asList(UrlUtils.encode(value))));
}
}
/**
* The value obtained directly from the headers method is an unmodifiable map.
* If the Feign client uses the URL, the original headers are unmodifiable.
* @param request feign request
* @return modifiable headers
*/
private Map<String, Collection<String>> getModifiableHeaders(Request request) {
Map<String, Collection<String>> headers;
headers = (Map<String, Collection<String>>) ReflectionUtils.getFieldValue(request, "headers");
if (!(headers instanceof LinkedHashMap)) {
headers = new LinkedHashMap<>(headers);
ReflectionUtils.setFieldValue(request, "headers", headers);
}
return headers;
}
@Override
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER;
}
}

@ -1,102 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
import static java.net.URLEncoder.encode;
/**
* Interceptor used for adding the metadata in http headers from context when web client
* is Feign.
*
* @author Haotian Zhang
*/
public class EncodeTransferMedataFeignInterceptor implements RequestInterceptor, Ordered {
private static final Logger LOG = LoggerFactory.getLogger(EncodeTransferMedataFeignInterceptor.class);
@Override
public int getOrder() {
return OrderConstant.Client.Feign.ENCODE_TRANSFER_METADATA_INTERCEPTOR_ORDER;
}
@Override
public void apply(RequestTemplate requestTemplate) {
// get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
Map<String, String> transHeaders = metadataContext.getTransHeadersKV();
this.buildMetadataHeader(requestTemplate, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
// process custom metadata
this.buildMetadataHeader(requestTemplate, customMetadata, CUSTOM_METADATA);
// set headers that need to be transmitted from the upstream
this.buildTransmittedHeader(requestTemplate, transHeaders);
}
private void buildTransmittedHeader(RequestTemplate requestTemplate, Map<String, String> transHeaders) {
if (!CollectionUtils.isEmpty(transHeaders)) {
transHeaders.entrySet().stream().forEach(entry -> {
requestTemplate.removeHeader(entry.getKey());
requestTemplate.header(entry.getKey(), entry.getValue());
});
}
}
/**
* Set metadata into the request header for {@link RestTemplate} .
* @param requestTemplate instance of {@link RestTemplate}
* @param metadata metadata map .
* @param headerName target metadata http header name .
*/
private void buildMetadataHeader(RequestTemplate requestTemplate, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
String encodedMetadata = JacksonUtils.serialize2Json(metadata);
requestTemplate.removeHeader(headerName);
try {
requestTemplate.header(headerName, encode(encodedMetadata, UTF_8));
}
catch (UnsupportedEncodingException e) {
LOG.error("Set header failed.", e);
requestTemplate.header(headerName, encodedMetadata);
}
}
}
}

@ -18,49 +18,53 @@
package com.tencent.cloud.metadata.core;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import com.tencent.cloud.common.constant.OrderConstant;
import com.google.common.collect.ImmutableMap;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataType;
import org.springframework.core.Ordered;
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.lang.NonNull;
import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
/**
* Interceptor used for adding the metadata in http headers from context when web client
* is RestTemplate.
* Pre EnhancedPlugin for rest template to encode transfer metadata.
*
* @author Haotian Zhang
* @author Shedfree Wu
*/
public class EncodeTransferMedataRestTemplateInterceptor implements ClientHttpRequestInterceptor, Ordered {
public class EncodeTransferMedataRestTemplateEnhancedPlugin implements EnhancedPlugin {
@Override
public int getOrder() {
return OrderConstant.Client.RestTemplate.ENCODE_TRANSFER_METADATA_INTERCEPTOR_ORDER;
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.PRE;
}
@Override
public ClientHttpResponse intercept(@NonNull HttpRequest httpRequest, @NonNull byte[] bytes,
@NonNull ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
public void run(EnhancedPluginContext context) throws Throwable {
if (!(context.getOriginRequest() instanceof HttpRequest)) {
return;
}
HttpRequest httpRequest = (HttpRequest) context.getOriginRequest();
// get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
Map<String, String> transHeaders = metadataContext.getTransHeadersKV();
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(httpRequest, calleeTransitiveHeaders);
// build custom disposable metadata request header
this.buildMetadataHeader(httpRequest, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
@ -70,8 +74,6 @@ public class EncodeTransferMedataRestTemplateInterceptor implements ClientHttpRe
// set headers that need to be transmitted from the upstream
this.buildTransmittedHeader(httpRequest, transHeaders);
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
private void buildTransmittedHeader(HttpRequest request, Map<String, String> transHeaders) {
@ -82,6 +84,12 @@ public class EncodeTransferMedataRestTemplateInterceptor implements ClientHttpRe
}
}
private void buildHeaderMap(HttpRequest request, Map<String, String> headerMap) {
if (!CollectionUtils.isEmpty(headerMap)) {
headerMap.forEach((key, value) -> request.getHeaders().set(key, UrlUtils.encode(value)));
}
}
/**
* Set metadata into the request header for {@link HttpRequest} .
*
@ -91,13 +99,12 @@ public class EncodeTransferMedataRestTemplateInterceptor implements ClientHttpRe
*/
private void buildMetadataHeader(HttpRequest request, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
String encodedMetadata = JacksonUtils.serialize2Json(metadata);
try {
request.getHeaders().set(headerName, URLEncoder.encode(encodedMetadata, UTF_8));
}
catch (UnsupportedEncodingException e) {
request.getHeaders().set(headerName, encodedMetadata);
}
buildHeaderMap(request, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
}
}
@Override
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER;
}
}

@ -18,42 +18,46 @@
package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import reactor.core.publisher.Mono;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataType;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
/**
* Scg filter used for writing metadata in HTTP request header.
* Pre EnhancedPlugin for scg to encode transfer metadata.
*
* @author Haotian Zhang
* @author Shedfree Wu
*/
public class EncodeTransferMedataScgFilter implements GlobalFilter, Ordered {
public class EncodeTransferMedataScgEnhancedPlugin implements EnhancedPlugin {
@Override
public int getOrder() {
return OrderConstant.Client.Scg.ENCODE_TRANSFER_METADATA_FILTER_ORDER;
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.PRE;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
public void run(EnhancedPluginContext context) throws Throwable {
if (!(context.getOriginRequest() instanceof ServerWebExchange)) {
return;
}
ServerWebExchange exchange = (ServerWebExchange) context.getOriginRequest();
// get request builder
ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
@ -66,11 +70,22 @@ public class EncodeTransferMedataScgFilter implements GlobalFilter, Ordered {
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(builder, calleeTransitiveHeaders);
this.buildMetadataHeader(builder, customMetadata, CUSTOM_METADATA);
this.buildMetadataHeader(builder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
TransHeadersTransfer.transfer(exchange.getRequest());
return chain.filter(exchange.mutate().request(builder.build()).build());
context.setOriginRequest(exchange.mutate().request(builder.build()).build());
}
private void buildHeaderMap(ServerHttpRequest.Builder builder, Map<String, String> headerMap) {
if (!CollectionUtils.isEmpty(headerMap)) {
headerMap.forEach((key, value) -> builder.header(key, UrlUtils.encode(value)));
}
}
/**
@ -81,13 +96,12 @@ public class EncodeTransferMedataScgFilter implements GlobalFilter, Ordered {
*/
private void buildMetadataHeader(ServerHttpRequest.Builder builder, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
String encodedMetadata = JacksonUtils.serialize2Json(metadata);
try {
builder.header(headerName, URLEncoder.encode(encodedMetadata, UTF_8));
}
catch (UnsupportedEncodingException e) {
builder.header(headerName, encodedMetadata);
}
buildHeaderMap(builder, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
}
}
@Override
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER;
}
}

@ -18,48 +18,61 @@
package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import reactor.core.publisher.Mono;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
/**
* web client filter used for writing metadata in HTTP request header.
* Pre EnhancedPlugin for web client to encode transfer metadata.
*
* @author sean yu
* @author Shedfree Wu
*/
public class EncodeTransferMedataWebClientFilter implements ExchangeFilterFunction {
public class EncodeTransferMedataWebClientEnhancedPlugin implements EnhancedPlugin {
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.PRE;
}
@Override
public Mono<ClientResponse> filter(ClientRequest clientRequest, ExchangeFunction next) {
public void run(EnhancedPluginContext context) throws Throwable {
if (!(context.getOriginRequest() instanceof ClientRequest)) {
return;
}
ClientRequest clientRequest = (ClientRequest) context.getOriginRequest();
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
Map<String, String> transHeaders = metadataContext.getTransHeadersKV();
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
ClientRequest.Builder requestBuilder = ClientRequest.from(clientRequest);
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(requestBuilder, calleeTransitiveHeaders);
this.buildMetadataHeader(requestBuilder, customMetadata, CUSTOM_METADATA);
this.buildMetadataHeader(requestBuilder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
this.buildTransmittedHeader(requestBuilder, transHeaders);
ClientRequest request = requestBuilder.build();
return next.exchange(request);
context.setOriginRequest(requestBuilder.build());
}
private void buildTransmittedHeader(ClientRequest.Builder requestBuilder, Map<String, String> transHeaders) {
@ -68,6 +81,11 @@ public class EncodeTransferMedataWebClientFilter implements ExchangeFilterFuncti
}
}
private void buildHeaderMap(ClientRequest.Builder requestBuilder, Map<String, String> headerMap) {
if (!CollectionUtils.isEmpty(headerMap)) {
headerMap.forEach((key, value) -> requestBuilder.header(key, UrlUtils.encode(value)));
}
}
/**
* Set metadata into the request header for {@link ClientRequest} .
@ -77,14 +95,12 @@ public class EncodeTransferMedataWebClientFilter implements ExchangeFilterFuncti
*/
private void buildMetadataHeader(ClientRequest.Builder requestBuilder, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
String encodedMetadata = JacksonUtils.serialize2Json(metadata);
try {
requestBuilder.header(headerName, URLEncoder.encode(encodedMetadata, UTF_8));
}
catch (UnsupportedEncodingException e) {
requestBuilder.header(headerName, encodedMetadata);
}
buildHeaderMap(requestBuilder, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
}
}
@Override
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER;
}
}

@ -18,63 +18,65 @@
package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import com.netflix.zuul.ZuulFilter;
import com.google.common.collect.ImmutableMap;
import com.netflix.zuul.context.RequestContext;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataType;
import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ROUTE_TYPE;
/**
* Zuul filter used for writing metadata in HTTP request header.
* Pre EnhancedPlugin for zuul to encode transfer metadata.
*
* @author Haotian Zhang
* @author Shedfree Wu
*/
public class EncodeTransferMetadataZuulFilter extends ZuulFilter {
@Override
public String filterType() {
return ROUTE_TYPE;
}
@Override
public int filterOrder() {
return OrderConstant.Client.Zuul.ENCODE_TRANSFER_METADATA_FILTER_ORDER;
}
public class EncodeTransferMetadataZuulEnhancedPlugin implements EnhancedPlugin {
@Override
public boolean shouldFilter() {
return true;
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.PRE;
}
@Override
public Object run() {
// get request context
RequestContext requestContext = RequestContext.getCurrentContext();
public void run(EnhancedPluginContext context) throws Throwable {
if (!(context.getOriginRequest() instanceof RequestContext)) {
return;
}
RequestContext requestContext = (RequestContext) context.getOriginRequest();
// get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(requestContext, calleeTransitiveHeaders);
// Rebuild Metadata Header
this.buildMetadataHeader(requestContext, customMetadata, CUSTOM_METADATA);
this.buildMetadataHeader(requestContext, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
TransHeadersTransfer.transfer(requestContext.getRequest());
return null;
}
private void buildHeaderMap(RequestContext context, Map<String, String> headerMap) {
if (!CollectionUtils.isEmpty(headerMap)) {
headerMap.forEach((key, value) -> context.addZuulRequestHeader(key, UrlUtils.encode(value)));
}
}
/**
@ -86,13 +88,12 @@ public class EncodeTransferMetadataZuulFilter extends ZuulFilter {
*/
private void buildMetadataHeader(RequestContext context, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
String encodedMetadata = JacksonUtils.serialize2Json(metadata);
try {
context.addZuulRequestHeader(headerName, URLEncoder.encode(encodedMetadata, UTF_8));
}
catch (UnsupportedEncodingException e) {
context.addZuulRequestHeader(headerName, encodedMetadata);
}
buildHeaderMap(context, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
}
}
@Override
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER;
}
}

@ -0,0 +1,66 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.metadata.provider;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataProvider;
import org.springframework.http.server.reactive.ServerHttpRequest;
/**
* MetadataProvider used for Reactive.
*
* @author Shedfree Wu
*/
public class ReactiveMetadataProvider implements MetadataProvider {
private ServerHttpRequest serverHttpRequest;
public ReactiveMetadataProvider(ServerHttpRequest serverHttpRequest) {
this.serverHttpRequest = serverHttpRequest;
}
@Override
public String getRawMetadataStringValue(String key) {
switch (key) {
case MessageMetadataContainer.LABEL_KEY_METHOD:
return serverHttpRequest.getMethodValue();
case MessageMetadataContainer.LABEL_KEY_PATH:
return UrlUtils.decode(serverHttpRequest.getPath().toString());
default:
return null;
}
}
@Override
public String getRawMetadataMapValue(String key, String mapKey) {
switch (key) {
case MessageMetadataContainer.LABEL_MAP_KEY_HEADER:
return UrlUtils.decode(SpringWebExpressionLabelUtils.getHeaderValue(serverHttpRequest, mapKey, null));
case MessageMetadataContainer.LABEL_MAP_KEY_COOKIE:
return UrlUtils.decode(SpringWebExpressionLabelUtils.getCookieValue(serverHttpRequest, mapKey, null));
case MessageMetadataContainer.LABEL_MAP_KEY_QUERY:
return UrlUtils.decode(SpringWebExpressionLabelUtils.getQueryValue(serverHttpRequest, mapKey, null));
default:
return null;
}
}
}

@ -0,0 +1,67 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.metadata.provider;
import javax.servlet.http.HttpServletRequest;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataProvider;
/**
* MetadataProvider used for Servlet.
*
* @author Shedfree Wu
*/
public class ServletMetadataProvider implements MetadataProvider {
private HttpServletRequest httpServletRequest;
public ServletMetadataProvider(HttpServletRequest httpServletRequest) {
this.httpServletRequest = httpServletRequest;
}
@Override
public String getRawMetadataStringValue(String key) {
switch (key) {
case MessageMetadataContainer.LABEL_KEY_METHOD:
return httpServletRequest.getMethod();
case MessageMetadataContainer.LABEL_KEY_PATH:
return UrlUtils.decode(httpServletRequest.getRequestURI());
default:
return null;
}
}
@Override
public String getRawMetadataMapValue(String key, String mapKey) {
switch (key) {
case MessageMetadataContainer.LABEL_MAP_KEY_HEADER:
return UrlUtils.decode(httpServletRequest.getHeader(mapKey));
case MessageMetadataContainer.LABEL_MAP_KEY_COOKIE:
return UrlUtils.decode(ServletExpressionLabelUtils.getCookieValue(httpServletRequest.getCookies(), mapKey, null));
case MessageMetadataContainer.LABEL_MAP_KEY_QUERY:
return UrlUtils.decode(ExpressionLabelUtils.getQueryValue(httpServletRequest.getQueryString(), mapKey, null));
default:
return null;
}
}
}

@ -18,25 +18,17 @@
package com.tencent.cloud.metadata.config;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientFilter;
import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientEnhancedPlugin;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@ -59,35 +51,10 @@ public class MetadataTransferAutoConfigurationTest {
this.applicationContextRunner.withConfiguration(AutoConfigurations.of(MetadataTransferAutoConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferFeignInterceptorConfig.class);
assertThat(context).hasSingleBean(EncodeTransferMedataFeignInterceptor.class);
assertThat(context).hasSingleBean(EncodeTransferMedataFeignEnhancedPlugin.class);
assertThat(context).hasSingleBean(EncodeTransferMedataRestTemplateEnhancedPlugin.class);
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig.class);
assertThat(context).hasSingleBean(EncodeTransferMedataRestTemplateInterceptor.class);
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferScgFilterConfig.class);
assertThat(context).hasSingleBean(GlobalFilter.class);
});
}
@Test
public void test2() {
this.applicationContextRunner
.withConfiguration(
AutoConfigurations.of(MetadataTransferAutoConfiguration.class, RestTemplateConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(EncodeTransferMedataFeignInterceptor.class);
EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor = context.getBean(EncodeTransferMedataRestTemplateInterceptor.class);
Map<String, RestTemplate> restTemplateMap = context.getBeansOfType(RestTemplate.class);
assertThat(restTemplateMap.size()).isEqualTo(2);
for (String beanName : Arrays.asList("restTemplate", "loadBalancedRestTemplate")) {
RestTemplate restTemplate = restTemplateMap.get(beanName);
assertThat(restTemplate).isNotNull();
List<ClientHttpRequestInterceptor> encodeTransferMedataFeignInterceptorList = restTemplate.getInterceptors()
.stream()
.filter(interceptor -> Objects.equals(interceptor, encodeTransferMedataRestTemplateInterceptor))
.collect(Collectors.toList());
//EncodeTransferMetadataFeignInterceptor is not added repeatedly
assertThat(encodeTransferMedataFeignInterceptorList.size()).isEqualTo(1);
}
});
}
@ -95,16 +62,15 @@ public class MetadataTransferAutoConfigurationTest {
* Reactive web application.
*/
@Test
public void test3() {
public void test2() {
this.reactiveWebApplicationContextRunner.withConfiguration(AutoConfigurations.of(MetadataTransferAutoConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferFeignInterceptorConfig.class);
assertThat(context).hasSingleBean(EncodeTransferMedataFeignInterceptor.class);
assertThat(context).hasSingleBean(EncodeTransferMedataFeignEnhancedPlugin.class);
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig.class);
assertThat(context).hasSingleBean(EncodeTransferMedataRestTemplateInterceptor.class);
assertThat(context).hasSingleBean(EncodeTransferMedataRestTemplateEnhancedPlugin.class);
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferScgFilterConfig.class);
assertThat(context).hasSingleBean(GlobalFilter.class);
assertThat(context).hasSingleBean(EncodeTransferMedataWebClientFilter.class);
assertThat(context).hasSingleBean(EncodeTransferMedataWebClientEnhancedPlugin.class);
});
}

@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
/**
* Test for {@link EncodeTransferMedataFeignInterceptor}.
* Test for {@link EncodeTransferMedataFeignEnhancedPlugin}.
*
* @author Haotian Zhang
*/

@ -17,8 +17,14 @@
package com.tencent.cloud.metadata.core;
import java.net.URI;
import java.util.Arrays;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateInterceptor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -26,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@ -39,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* Test for {@link EncodeTransferMedataRestTemplateInterceptor}.
* Test for {@link EncodeTransferMedataRestTemplateEnhancedPlugin}.
*
* @author Haotian Zhang
*/
@ -72,7 +79,13 @@ public class EncodeTransferMedataRestTemplateInterceptorTest {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
EncodeTransferMedataRestTemplateEnhancedPlugin plugin = new EncodeTransferMedataRestTemplateEnhancedPlugin();
EnhancedRestTemplateInterceptor interceptor = new EnhancedRestTemplateInterceptor(
new DefaultEnhancedPluginRunner(Arrays.asList(plugin), new MockRegistration(), null));
RestTemplate template = new RestTemplate();
template.setInterceptors(Arrays.asList(interceptor));
return template;
}
@RequestMapping("/test")
@ -80,4 +93,37 @@ public class EncodeTransferMedataRestTemplateInterceptorTest {
return MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "b");
}
}
static class MockRegistration implements Registration {
@Override
public String getServiceId() {
return "test";
}
@Override
public String getHost() {
return "localhost";
}
@Override
public int getPort() {
return 0;
}
@Override
public boolean isSecure() {
return false;
}
@Override
public URI getUri() {
return null;
}
@Override
public Map<String, String> getMetadata() {
return null;
}
}
}

@ -18,59 +18,129 @@
package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Map;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.scg.EnhancedGatewayGlobalFilter;
import org.assertj.core.util.Maps;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.server.ServerWebExchange;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
/**
* @author quan
* Test for {@link EncodeTransferMedataScgEnhancedPlugin}.
* @author quan, Shedfree Wu
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = RANDOM_PORT, classes = EncodeTransferMedataScgFilterTest.TestApplication.class,
properties = {"spring.config.location = classpath:application-test.yml",
"spring.main.web-application-type = reactive"})
@ExtendWith(MockitoExtension.class)
public class EncodeTransferMedataScgFilterTest {
@Autowired
private ApplicationContext applicationContext;
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@Mock
Registration registration;
@Mock
private GatewayFilterChain chain;
GatewayFilterChain chain;
@Test
public void testTransitiveMetadataFromApplicationConfig() throws UnsupportedEncodingException {
EncodeTransferMedataScgFilter filter = applicationContext.getBean(EncodeTransferMedataScgFilter.class);
MockServerHttpRequest.BaseBuilder<?> builder = MockServerHttpRequest.get("");
MockServerWebExchange exchange = MockServerWebExchange.from(builder);
filter.filter(exchange, chain);
String metadataStr = exchange.getRequest().getHeaders().getFirst(MetadataConstant.HeaderName.CUSTOM_METADATA);
String decode = URLDecoder.decode(metadataStr, UTF_8);
Map<String, String> transitiveMap = JacksonUtils.deserialize2Map(decode);
assertThat(transitiveMap.size()).isEqualTo(1);
assertThat(transitiveMap.get("b")).isEqualTo("2");
@BeforeAll
static void beforeAll() {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
ApplicationContext applicationContext = mock(ApplicationContext.class);
MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class);
StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class);
doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class);
doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class);
mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext)
.thenReturn(applicationContext);
}
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
}
@SpringBootApplication
protected static class TestApplication {
@BeforeEach
void setUp() {
MetadataContext.LOCAL_NAMESPACE = NAMESPACE_TEST;
MetadataContext.LOCAL_SERVICE = SERVICE_PROVIDER;
}
@Test
public void testRun() throws URISyntaxException {
Route route = mock(Route.class);
URI uri = new URI("http://TEST/");
doReturn(uri).when(route).getUri();
MetadataContext metadataContext = MetadataContextHolder.get();
metadataContext.setTransitiveMetadata(Maps.newHashMap("t-key", "t-value"));
metadataContext.setDisposableMetadata(Maps.newHashMap("d-key", "d-value"));
MockServerHttpRequest mockServerHttpRequest = MockServerHttpRequest.get("/test").build();
EncodeTransferMedataScgEnhancedPlugin plugin = new EncodeTransferMedataScgEnhancedPlugin();
plugin.getOrder();
EnhancedGatewayGlobalFilter filter = new EnhancedGatewayGlobalFilter(new DefaultEnhancedPluginRunner(Arrays.asList(plugin), registration, null));
filter.getOrder();
MockServerWebExchange mockServerWebExchange = MockServerWebExchange.builder(mockServerHttpRequest).build();
mockServerWebExchange.getAttributes().put(GATEWAY_ROUTE_ATTR, route);
mockServerWebExchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, new URI("http://0.0.0.0/"));
mockServerWebExchange.getAttributes().put(MetadataConstant.HeaderName.METADATA_CONTEXT, metadataContext);
doReturn(Mono.empty()).when(chain).filter(any());
filter.filter(mockServerWebExchange, chain).block();
ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor.forClass(ServerWebExchange.class);
// capture the result exchange
Mockito.verify(chain).filter(captor.capture());
ServerWebExchange filteredExchange = captor.getValue();
assertThat(filteredExchange.getRequest().getHeaders().get(CUSTOM_METADATA)).isNotNull();
assertThat(filteredExchange.getRequest().getHeaders().get(CUSTOM_DISPOSABLE_METADATA)).isNotNull();
// test metadataContext init in EnhancedPlugin
mockServerWebExchange.getAttributes().remove(MetadataConstant.HeaderName.METADATA_CONTEXT);
assertThatCode(() -> filter.filter(mockServerWebExchange, chain).block()).doesNotThrowAnyException();
}
}

@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* Test for {@link EncodeTransferMedataWebClientFilter}.
* Test for {@link EncodeTransferMedataWebClientEnhancedPlugin}.
*
* @author sean yu
*/

@ -23,9 +23,13 @@ import java.net.URLDecoder;
import java.util.Map;
import com.netflix.zuul.context.RequestContext;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.netflix.zuul.exception.ZuulException;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedPreZuulFilter;
import org.assertj.core.api.Assertions;
import org.assertj.core.util.Maps;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -38,12 +42,16 @@ import org.springframework.mock.web.MockMultipartHttpServletRequest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY;
/**
* Test for {@link EncodeTransferMetadataZuulFilter}.
* Test for {@link EncodeTransferMetadataZuulEnhancedPlugin}.
*
* @author quan
* @author quan, Shedfree Wu
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
@ -64,17 +72,30 @@ public class EncodeTransferMetadataZuulFilterTest {
}
@Test
public void multiplePartNamesWithMultipleParts() throws UnsupportedEncodingException {
EncodeTransferMetadataZuulFilter filter = applicationContext.getBean(EncodeTransferMetadataZuulFilter.class);
public void testRun() throws ZuulException, UnsupportedEncodingException {
EnhancedPreZuulFilter filter = applicationContext.getBean(EnhancedPreZuulFilter.class);
RequestContext context = RequestContext.getCurrentContext();
context.set(SERVICE_ID_KEY, "test-service");
MetadataContext metadataContext = MetadataContextHolder.get();
metadataContext.setTransitiveMetadata(Maps.newHashMap("t-key", "t-value"));
metadataContext.setDisposableMetadata(Maps.newHashMap("d-key", "d-value"));
filter.run();
final RequestContext ctx = RequestContext.getCurrentContext();
Map<String, String> zuulRequestHeaders = ctx.getZuulRequestHeaders();
String metadata = zuulRequestHeaders.get(MetadataConstant.HeaderName.CUSTOM_METADATA.toLowerCase());
// convert header to lower case in com.netflix.zuul.context.RequestContext.addZuulRequestHeader
assertThat(zuulRequestHeaders.get(CUSTOM_METADATA.toLowerCase())).isNotNull();
assertThat(zuulRequestHeaders.get(CUSTOM_DISPOSABLE_METADATA.toLowerCase())).isNotNull();
String metadata = zuulRequestHeaders.get(CUSTOM_METADATA.toLowerCase());
Assertions.assertThat(metadata).isNotNull();
String decode = URLDecoder.decode(metadata, UTF_8);
Map<String, String> transitiveMap = JacksonUtils.deserialize2Map(decode);
Assertions.assertThat(transitiveMap.size()).isEqualTo(1);
// expect {"b":"2","t-key":"t-value"}
Assertions.assertThat(transitiveMap.size()).isEqualTo(2);
Assertions.assertThat(transitiveMap.get("b")).isEqualTo("2");
}

@ -0,0 +1,135 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.metadata.provider;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import org.junit.Test;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpMethod;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.MockCookie;
import org.springframework.mock.web.MockHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link ReactiveMetadataProvider} and {@link ServletMetadataProvider}.
*
* @author quan, Shedfree Wu
*/
public class MetadataProviderTest {
private static final String notExistKey = "empty";
@Test
public void testReactiveMetadataProvider() {
String headerKey1 = "header1";
String headerKey2 = "header2";
String headerValue1 = "value1";
String headerValue2 = "value2/test";
String queryKey1 = "qk1";
String queryKey2 = "qk2";
String queryValue1 = "qv1";
String queryValue2 = "qv2/test";
String cookieKey1 = "ck1";
String cookieKey2 = "ck2";
String cookieValue1 = "cv1";
String cookieValue2 = "cv2/test";
String path = "/echo/test";
MockServerHttpRequest request = MockServerHttpRequest.get(path)
.header(headerKey1, headerValue1)
.header(headerKey2, UrlUtils.encode(headerValue2))
.queryParam(queryKey1, queryValue1)
.queryParam(queryKey2, UrlUtils.encode(queryValue2))
.cookie(new HttpCookie(cookieKey1, cookieValue1))
.cookie(new HttpCookie(cookieKey2, UrlUtils.encode(cookieValue2)))
.build();
ReactiveMetadataProvider reactiveMetadataProvider = new ReactiveMetadataProvider(request);
assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey1)).isEqualTo(headerValue1);
assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey2)).isEqualTo(headerValue2);
// com.tencent.polaris.metadata.core.manager.ComposeMetadataProvider.getRawMetadataMapValue need return null when key don't exist
assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, notExistKey)).isNull();
assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey1)).isEqualTo(cookieValue1);
assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey2)).isEqualTo(cookieValue2);
assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, notExistKey)).isNull();
assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey1)).isEqualTo(queryValue1);
assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey2)).isEqualTo(queryValue2);
assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, notExistKey)).isNull();
assertThat(reactiveMetadataProvider.getRawMetadataMapValue(notExistKey, queryKey1)).isNull();
assertThat(reactiveMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_METHOD)).isEqualTo("GET");
assertThat(reactiveMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo(path);
assertThat(reactiveMetadataProvider.getRawMetadataStringValue(notExistKey)).isNull();
request = MockServerHttpRequest.get("/echo/" + UrlUtils.decode("a@b")).build();
reactiveMetadataProvider = new ReactiveMetadataProvider(request);
assertThat(reactiveMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo("/echo/a@b");
}
@Test
public void testServletMetadataProvider() {
String headerKey1 = "header1";
String headerKey2 = "header2";
String headerValue1 = "value1";
String headerValue2 = "value2/test";
String queryKey1 = "qk1";
String queryKey2 = "qk2";
String queryValue1 = "qv1";
String queryValue2 = "qv2/test";
String cookieKey1 = "ck1";
String cookieKey2 = "ck2";
String cookieValue1 = "cv1";
String cookieValue2 = "cv2/test";
String path = "/echo/test";
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader(headerKey1, headerValue1);
request.addHeader(headerKey2, UrlUtils.encode(headerValue2));
request.setCookies(new MockCookie(cookieKey1, cookieValue1), new MockCookie(cookieKey2, UrlUtils.encode(cookieValue2)));
request.setMethod(HttpMethod.GET.name());
request.setRequestURI(path);
request.setQueryString(queryKey1 + "=" + queryValue1 + "&" + queryKey2 + "=" + UrlUtils.encode(queryValue2));
ServletMetadataProvider servletMetadataProvider = new ServletMetadataProvider(request);
assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey1)).isEqualTo(headerValue1);
assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey2)).isEqualTo(headerValue2);
// com.tencent.polaris.metadata.core.manager.ComposeMetadataProvider.getRawMetadataMapValue need return null when key don't exist
assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, notExistKey)).isNull();
assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey1)).isEqualTo(cookieValue1);
assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey2)).isEqualTo(cookieValue2);
assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, notExistKey)).isNull();
assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey1)).isEqualTo(queryValue1);
assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey2)).isEqualTo(queryValue2);
assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, notExistKey)).isNull();
assertThat(servletMetadataProvider.getRawMetadataMapValue(notExistKey, queryKey1)).isNull();
assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_METHOD)).isEqualTo("GET");
assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo(path);
assertThat(servletMetadataProvider.getRawMetadataStringValue(notExistKey)).isNull();
request.setRequestURI("/echo/" + UrlUtils.decode("a@b"));
assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo("/echo/a@b");
}
}

@ -48,6 +48,10 @@
<groupId>com.tencent.polaris</groupId>
<artifactId>router-nearby</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-lane</artifactId>
</dependency>
<!-- Polaris dependencies end -->
<dependency>

@ -36,10 +36,6 @@ public class OrderConstant {
* Order constant for Feign.
*/
public static class Feign {
/**
* Order of encode transfer metadata interceptor.
*/
public static final int ENCODE_TRANSFER_METADATA_INTERCEPTOR_ORDER = Ordered.LOWEST_PRECEDENCE - 1;
/**
* Order of encode router label interceptor.
@ -51,10 +47,6 @@ public class OrderConstant {
* Order constant for RestTemplate.
*/
public static class RestTemplate {
/**
* Order of encode transfer metadata interceptor.
*/
public static final int ENCODE_TRANSFER_METADATA_INTERCEPTOR_ORDER = Ordered.LOWEST_PRECEDENCE - 1;
/**
* Order of encode router label interceptor.
@ -67,12 +59,6 @@ public class OrderConstant {
*/
public static class Scg {
/**
* Order of encode transfer metadata filter.
* {@link ReactiveLoadBalancerClientFilter}.LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150.
*/
public static final int ENCODE_TRANSFER_METADATA_FILTER_ORDER = 10150 + 1;
/**
* Order of enhanced filter.
* {@link ReactiveLoadBalancerClientFilter}.LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150.
@ -85,11 +71,6 @@ public class OrderConstant {
*/
public static class Zuul {
/**
* Order of encode transfer metadata filter.
*/
public static final int ENCODE_TRANSFER_METADATA_FILTER_ORDER = RIBBON_ROUTING_FILTER_ORDER - 1;
/**
* Order of enhanced ROUTE filter.
*/

@ -24,7 +24,9 @@ import java.util.Optional;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataContainer;
import com.tencent.polaris.metadata.core.MetadataProvider;
import com.tencent.polaris.metadata.core.MetadataType;
import com.tencent.polaris.metadata.core.TransitiveType;
@ -129,8 +131,10 @@ public final class MetadataContextHolder {
*
* @param dynamicTransitiveMetadata custom metadata collection
* @param dynamicDisposableMetadata custom disposable metadata connection
* @param callerMetadataProvider caller metadata provider
*/
public static void init(Map<String, String> dynamicTransitiveMetadata, Map<String, String> dynamicDisposableMetadata) {
public static void init(Map<String, String> dynamicTransitiveMetadata, Map<String, String> dynamicDisposableMetadata,
MetadataProvider callerMetadataProvider) {
com.tencent.polaris.metadata.core.manager.MetadataContextHolder.refresh(MetadataContextHolder::createMetadataManager, metadataManager -> {
MetadataContainer metadataContainerUpstream = metadataManager.getMetadataContainer(MetadataType.CUSTOM, false);
if (!CollectionUtils.isEmpty(dynamicTransitiveMetadata)) {
@ -144,6 +148,10 @@ public final class MetadataContextHolder {
metadataContainerDownstream.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.DISPOSABLE);
}
}
if (callerMetadataProvider != null) {
MessageMetadataContainer callerMessageContainer = metadataManager.getMetadataContainer(MetadataType.MESSAGE, true);
callerMessageContainer.setMetadataProvider(callerMetadataProvider);
}
});
}

@ -68,4 +68,20 @@ public final class ReflectionUtils extends org.springframework.util.ReflectionUt
}
return null;
}
public static void setFieldValue(Object instance, String fieldName, Object value) {
Field field = org.springframework.util.ReflectionUtils.findField(instance.getClass(), fieldName);
if (field == null) {
return;
}
field.setAccessible(true);
try {
setField(field, instance, value);
}
finally {
field.setAccessible(false);
}
}
}

@ -0,0 +1,78 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.common.util;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
/**
* Utils for URLDecoder/URLEncoder.
*
* @author Shedfree Wu
*/
public final class UrlUtils {
private static final Logger LOG = LoggerFactory.getLogger(UrlUtils.class);
private UrlUtils() {
}
public static String decode(String s) {
return decode(s, UTF_8);
}
public static String decode(String s, String enc) {
if (!StringUtils.hasText(s)) {
return s;
}
try {
return URLDecoder.decode(s, enc);
}
catch (UnsupportedEncodingException e) {
LOG.warn("Runtime system does not support {} coding. s:{}, msg:{}", enc, s, e.getMessage());
// return original string
return s;
}
}
public static String encode(String s) {
return encode(s, UTF_8);
}
public static String encode(String s, String enc) {
if (!StringUtils.hasText(s)) {
return s;
}
try {
return URLEncoder.encode(s, enc);
}
catch (UnsupportedEncodingException e) {
LOG.warn("Runtime system does not support {} coding. s:{}, msg:{}", enc, s, e.getMessage());
// return original string
return s;
}
}
}

@ -138,12 +138,16 @@ public final class ExpressionLabelUtils {
}
public static String getQueryValue(String queryString, String queryKey) {
return getQueryValue(queryString, queryKey, StringUtils.EMPTY);
}
public static String getQueryValue(String queryString, String queryKey, String defaultValue) {
if (StringUtils.isBlank(queryString)) {
return StringUtils.EMPTY;
return defaultValue;
}
String[] queries = StringUtils.split(queryString, "&");
if (queries == null || queries.length == 0) {
return StringUtils.EMPTY;
return defaultValue;
}
for (String query : queries) {
String[] queryKV = StringUtils.split(query, "=");
@ -151,7 +155,7 @@ public final class ExpressionLabelUtils {
return queryKV[1];
}
}
return StringUtils.EMPTY;
return defaultValue;
}
public static String getFirstValue(Map<String, Collection<String>> valueMaps, String key) {

@ -83,14 +83,18 @@ public final class ServletExpressionLabelUtils {
}
public static String getCookieValue(Cookie[] cookies, String key) {
return getCookieValue(cookies, key, StringUtils.EMPTY);
}
public static String getCookieValue(Cookie[] cookies, String key, String defaultValue) {
if (cookies == null || cookies.length == 0) {
return StringUtils.EMPTY;
return defaultValue;
}
for (Cookie cookie : cookies) {
if (StringUtils.equals(cookie.getName(), key)) {
return cookie.getValue();
}
}
return StringUtils.EMPTY;
return defaultValue;
}
}

@ -129,29 +129,41 @@ public final class SpringWebExpressionLabelUtils {
}
public static String getHeaderValue(ServerHttpRequest request, String key) {
return getHeaderValue(request, key, StringUtils.EMPTY);
}
public static String getHeaderValue(ServerHttpRequest request, String key, String defaultValue) {
String value = request.getHeaders().getFirst(key);
if (value == null) {
return StringUtils.EMPTY;
return defaultValue;
}
return value;
}
public static String getQueryValue(ServerHttpRequest request, String key) {
return getQueryValue(request, key, StringUtils.EMPTY);
}
public static String getQueryValue(ServerHttpRequest request, String key, String defaultValue) {
MultiValueMap<String, String> queries = request.getQueryParams();
if (CollectionUtils.isEmpty(queries)) {
return StringUtils.EMPTY;
return defaultValue;
}
String value = queries.getFirst(key);
if (value == null) {
return StringUtils.EMPTY;
return defaultValue;
}
return value;
}
public static String getCookieValue(ServerHttpRequest request, String key) {
return getCookieValue(request, key, StringUtils.EMPTY);
}
public static String getCookieValue(ServerHttpRequest request, String key, String defaultValue) {
HttpCookie cookie = request.getCookies().getFirst(key);
if (cookie == null) {
return StringUtils.EMPTY;
return defaultValue;
}
return cookie.getValue();
}

@ -63,7 +63,7 @@ public class MetadataContextHolderTest {
customMetadata.put("a", "1");
customMetadata.put("b", "22");
customMetadata.put("c", "3");
MetadataContextHolder.init(customMetadata, new HashMap<>());
MetadataContextHolder.init(customMetadata, new HashMap<>(), null);
metadataContext = MetadataContextHolder.get();
customMetadata = metadataContext.getTransitiveMetadata();
Assertions.assertThat(customMetadata.get("a")).isEqualTo("1");

@ -0,0 +1,68 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.common.util;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Utils for {@link UrlUtils}.
*
* @author Shedfree Wu
*/
public class UrlUtilsTest {
@Test
public void testEncodeDecode1() {
String expectEncodeValue = "a%2Fb";
String origin = "a/b";
String encode1 = UrlUtils.encode(origin);
assertThat(expectEncodeValue).isEqualTo(encode1);
// encode twice is different
String encode2 = UrlUtils.encode(encode1);
assertThat(encode1).isNotEqualTo(encode2);
// test decode
assertThat(origin).isEqualTo(UrlUtils.decode(encode1));
}
@Test
public void testEncodeDecode2() {
String origin = null;
String encode1 = UrlUtils.encode(origin);
assertThat(encode1).isNull();
origin = "";
encode1 = UrlUtils.encode(origin);
assertThat(encode1).isEqualTo(origin);
}
@Test
public void testError() {
String origin = "a/b";
String encode = UrlUtils.encode(origin, "error-enc");
assertThat(encode).isEqualTo(origin);
encode = "a%2Fb";
String decode = UrlUtils.decode(encode, "error-enc");
assertThat(decode).isEqualTo(encode);
}
}

@ -28,6 +28,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@ -80,6 +82,26 @@ public class QuickstartCalleeController {
return String.format("Quickstart [%s] Service [%s:%s] is called. datasource = [%s].", appName, ip, port, dataSourceProperties);
}
/**
* Mock post save value.
* @return true
*/
@PostMapping("/saveValue")
public Boolean saveValue(@RequestParam int value) {
LOG.info("Quickstart [{}] Service [{}:{}] is called. Mock save value = [{}].", appName, ip, port, value);
return true;
}
/**
* Get path echo of callee.
* @return information of callee
*/
@GetMapping("/path/echo/{param}")
public String pathEcho(@PathVariable String param) {
LOG.info("Quickstart [{}] Service [{}:{}] is called. param = [{}].", appName, ip, port, param);
return String.format("Quickstart [%s] Service [%s:%s] is called. datasource = [%s].", appName, ip, port, param);
}
/**
* Get metadata in HTTP header.
*

@ -76,8 +76,8 @@ public class PolarisLoadBalancer extends DynamicServerListLoadBalancer<Server> {
@Override
public List<Server> getReachableServers() {
// Get servers first from the thread context
if (!CollectionUtils.isEmpty(THREAD_CACHE_SERVERS.get())) {
// Get servers first from the thread context. When routers filter all instances, getReachableServersWithoutCache function cannot be executed.
if (THREAD_CACHE_SERVERS.get() != null) {
return THREAD_CACHE_SERVERS.get();
}
return getReachableServersWithoutCache();

@ -67,6 +67,7 @@ public class EnhancedFeignClient implements Client {
.url(url)
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setOriginRequest(request);
enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance());
DefaultServiceInstance serviceInstance = new DefaultServiceInstance(request.requestTemplate().feignTarget()

@ -36,6 +36,8 @@ public class EnhancedPluginContext {
private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedPluginContext.class);
private Object originRequest;
private EnhancedRequestContext request;
private EnhancedResponseContext response;
@ -51,6 +53,14 @@ public class EnhancedPluginContext {
*/
private ServiceInstance targetServiceInstance;
public Object getOriginRequest() {
return originRequest;
}
public void setOriginRequest(Object originRequest) {
this.originRequest = originRequest;
}
public EnhancedRequestContext getRequest() {
return request;
}

@ -44,5 +44,19 @@ public class PluginOrderConstant {
* {@link com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter}.
*/
public static final int CIRCUIT_BREAKER_REPORTER_PLUGIN_ORDER = Ordered.HIGHEST_PRECEDENCE + 2;
/**
* order for
* {@link com.tencent.cloud.metadata.core.EncodeTransferMedataFeignEnhancedPlugin}
* and
* {@link com.tencent.cloud.metadata.core.EncodeTransferMedataScgEnhancedPlugin}
* and
* {@link com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientEnhancedPlugin}
* and
* {@link com.tencent.cloud.metadata.core.EncodeTransferMedataZuulEnhancedPlugin}
* and
* {@link com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateEnhancedPlugin}.
*/
public static final int CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
}
}

@ -58,6 +58,7 @@ public class EnhancedRestTemplateInterceptor implements ClientHttpRequestInterce
.url(request.getURI())
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setOriginRequest(request);
enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance());
enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get()

@ -51,19 +51,21 @@ public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered {
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
public Mono<Void> filter(ServerWebExchange originExchange, GatewayFilterChain chain) {
EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext();
EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder()
.httpHeaders(exchange.getRequest().getHeaders())
.httpMethod(exchange.getRequest().getMethod())
.url(exchange.getRequest().getURI())
.httpHeaders(originExchange.getRequest().getHeaders())
.httpMethod(originExchange.getRequest().getMethod())
.url(originExchange.getRequest().getURI())
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setOriginRequest(originExchange);
// Run pre enhanced plugins.
pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext);
// Exchange may be changed in plugin
ServerWebExchange exchange = (ServerWebExchange) enhancedPluginContext.getOriginRequest();
long startTime = System.currentTimeMillis();
return chain.filter(exchange)
.doOnSubscribe(v -> {

@ -46,23 +46,25 @@ public class EnhancedWebClientExchangeFilterFunction implements ExchangeFilterFu
}
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
public Mono<ClientResponse> filter(ClientRequest originRequest, ExchangeFunction next) {
EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext();
EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder()
.httpHeaders(request.headers())
.httpMethod(request.method())
.url(request.url())
.httpHeaders(originRequest.headers())
.httpMethod(originRequest.method())
.url(originRequest.url())
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setOriginRequest(originRequest);
enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance());
enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get()
.getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), request.url());
.getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), originRequest.url());
// Run post enhanced plugins.
pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext);
// request may be changed by plugin
ClientRequest request = (ClientRequest) enhancedPluginContext.getOriginRequest();
long startTime = System.currentTimeMillis();
return next.exchange(request)
.doOnSuccess(response -> {

@ -99,6 +99,7 @@ public class EnhancedPreZuulFilter extends ZuulFilter {
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setOriginRequest(context);
enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance());
// Run pre enhanced plugins.

Loading…
Cancel
Save