feat: support lane router (#1269)

Co-authored-by: andrew shan <45474304+andrewshan@users.noreply.github.com>
pull/1270/head
Haotian Zhang 3 months ago committed by GitHub
parent 01f16131b8
commit daf6dab455
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -5,3 +5,4 @@
- [feat:upgrade jackson version.](https://github.com/Tencent/spring-cloud-tencent/pull/1261)
- [fix: fix RouterLabelRestTemplateInterceptor add response headers exception with httpclient5.](https://github.com/Tencent/spring-cloud-tencent/pull/1267)
- [feat: support lossless online and offline](https://github.com/Tencent/spring-cloud-tencent/pull/1268)
- [feat: support lane router](https://github.com/Tencent/spring-cloud-tencent/pull/1269)

@ -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>
@ -50,6 +56,24 @@
<artifactId>spring-cloud-starter-loadbalancer</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,29 +18,21 @@
package com.tencent.cloud.metadata.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.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.polaris.context.config.PolarisContextProperties;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
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 jakarta.servlet.DispatcherType.ASYNC;
import static jakarta.servlet.DispatcherType.ERROR;
@ -74,8 +66,8 @@ public class MetadataTransferAutoConfiguration {
}
@Bean
public DecodeTransferMetadataServletFilter metadataServletFilter() {
return new DecodeTransferMetadataServletFilter();
public DecodeTransferMetadataServletFilter metadataServletFilter(PolarisContextProperties polarisContextProperties) {
return new DecodeTransferMetadataServletFilter(polarisContextProperties);
}
}
@ -87,8 +79,8 @@ public class MetadataTransferAutoConfiguration {
protected static class MetadataReactiveFilterConfig {
@Bean
public DecodeTransferMetadataReactiveFilter metadataReactiveFilter() {
return new DecodeTransferMetadataReactiveFilter();
public DecodeTransferMetadataReactiveFilter metadataReactiveFilter(PolarisContextProperties polarisContextProperties) {
return new DecodeTransferMetadataReactiveFilter(polarisContextProperties);
}
}
@ -97,11 +89,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();
}
}
@ -110,11 +103,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();
}
}
@ -123,23 +117,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();
}
}
@ -148,20 +131,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,9 @@ 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 com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
@ -34,12 +35,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;
@ -51,8 +50,14 @@ import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUST
*/
public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered {
private PolarisContextProperties polarisContextProperties;
private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataReactiveFilter.class);
public DecodeTransferMetadataReactiveFilter(PolarisContextProperties polarisContextProperties) {
this.polarisContextProperties = polarisContextProperties;
}
@Override
public int getOrder() {
return OrderConstant.Server.Reactive.DECODE_TRANSFER_METADATA_FILTER_ORDER;
@ -62,19 +67,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, polarisContextProperties.getLocalIpAddress());
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, metadataProvider);
// Save to ServerWebExchange.
serverWebExchange.getAttributes().put(
MetadataConstant.HeaderName.METADATA_CONTEXT,
@ -82,20 +84,14 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
TransHeadersTransfer.transfer(serverHttpRequest);
return webFilterChain.filter(serverWebExchange)
.doOnError(throwable -> LOG.error("handle metadata[{}] error.",
MetadataContextHolder.get(), throwable))
.doFinally((type) -> MetadataContextHolder.remove());
}
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,14 +19,15 @@
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;
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 com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@ -36,10 +37,8 @@ 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;
@ -54,6 +53,12 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataServletFilter.class);
private PolarisContextProperties polarisContextProperties;
public DecodeTransferMetadataServletFilter(PolarisContextProperties polarisContextProperties) {
this.polarisContextProperties = polarisContextProperties;
}
@Override
protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest,
@NonNull HttpServletResponse httpServletResponse, FilterChain filterChain)
@ -64,11 +69,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, polarisContextProperties.getLocalIpAddress());
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, metadataProvider);
TransHeadersTransfer.transfer(httpServletRequest);
try {
@ -82,15 +86,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;
}
}

@ -0,0 +1,71 @@
/*
* 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;
private String callerIp;
public ReactiveMetadataProvider(ServerHttpRequest serverHttpRequest, String callerIp) {
this.serverHttpRequest = serverHttpRequest;
this.callerIp = callerIp;
}
@Override
public String getRawMetadataStringValue(String key) {
switch (key) {
case MessageMetadataContainer.LABEL_KEY_METHOD:
return serverHttpRequest.getMethod().name();
case MessageMetadataContainer.LABEL_KEY_PATH:
return UrlUtils.decode(serverHttpRequest.getPath().toString());
case MessageMetadataContainer.LABEL_KEY_CALLER_IP:
return callerIp;
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,71 @@
/*
* 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.ExpressionLabelUtils;
import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataProvider;
import jakarta.servlet.http.HttpServletRequest;
/**
* MetadataProvider used for Servlet.
*
* @author Shedfree Wu
*/
public class ServletMetadataProvider implements MetadataProvider {
private HttpServletRequest httpServletRequest;
private String callerIp;
public ServletMetadataProvider(HttpServletRequest httpServletRequest, String callerIp) {
this.httpServletRequest = httpServletRequest;
this.callerIp = callerIp;
}
@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());
case MessageMetadataContainer.LABEL_KEY_CALLER_IP:
return callerIp;
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,24 +18,18 @@
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 com.tencent.cloud.polaris.context.config.PolarisContextProperties;
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;
@ -48,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class MetadataTransferAutoConfigurationTest {
private final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner();
private final ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = new ReactiveWebApplicationContextRunner();
/**
* No any web application.
@ -57,36 +52,26 @@ 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);
assertThat(context).hasSingleBean(EncodeTransferMedataWebClientFilter.class);
});
}
/**
* Reactive web application.
*/
@Test
public void test2() {
this.applicationContextRunner
.withConfiguration(
AutoConfigurations.of(MetadataTransferAutoConfiguration.class, RestTemplateConfiguration.class))
this.reactiveWebApplicationContextRunner.withConfiguration(AutoConfigurations.of(MetadataTransferAutoConfiguration.class, PolarisContextProperties.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);
}
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferFeignInterceptorConfig.class);
assertThat(context).hasSingleBean(EncodeTransferMedataFeignEnhancedPlugin.class);
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig.class);
assertThat(context).hasSingleBean(EncodeTransferMedataRestTemplateEnhancedPlugin.class);
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferScgFilterConfig.class);
assertThat(context).hasSingleBean(EncodeTransferMedataWebClientEnhancedPlugin.class);
});
}

@ -20,6 +20,7 @@ package com.tencent.cloud.metadata.core;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -44,7 +45,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = MOCK,
classes = DecodeTransferMetadataServletFilterTest.TestApplication.class,
properties = {"spring.config.location = classpath:application-test.yml"})
properties = {"spring.config.location = classpath:application-test.yml", "spring.main.web-application-type = reactive"})
public class DecodeTransferMetadataReactiveFilterTest {
@Autowired
@ -54,7 +55,7 @@ public class DecodeTransferMetadataReactiveFilterTest {
@BeforeEach
public void setUp() {
this.metadataReactiveFilter = new DecodeTransferMetadataReactiveFilter();
this.metadataReactiveFilter = new DecodeTransferMetadataReactiveFilter(new PolarisContextProperties());
}
@Test

@ -44,7 +44,9 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
classes = DecodeTransferMetadataServletFilterTest.TestApplication.class,
properties = {"spring.config.location = classpath:application-test.yml"})
properties = {"spring.config.location = classpath:application-test.yml",
"spring.main.web-application-type = servlet",
"spring.cloud.gateway.enabled = false"})
public class DecodeTransferMetadataServletFilterTest {
@Autowired

@ -40,14 +40,16 @@ 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
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = DEFINED_PORT,
classes = EncodeTransferMedataFeignInterceptorTest.TestApplication.class,
properties = {"server.port=48081", "spring.config.location = classpath:application-test.yml"})
properties = {"server.port=48081", "spring.config.location = classpath:application-test.yml",
"spring.main.web-application-type = servlet",
"spring.cloud.gateway.enabled = false"})
public class EncodeTransferMedataFeignInterceptorTest {
@Autowired

@ -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.test.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,14 +46,15 @@ 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
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
classes = EncodeTransferMedataRestTemplateInterceptorTest.TestApplication.class,
properties = {"spring.config.location = classpath:application-test.yml"})
properties = {"spring.config.location = classpath:application-test.yml",
"spring.main.web-application-type = reactive"})
public class EncodeTransferMedataRestTemplateInterceptorTest {
@Autowired
@ -71,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")
@ -79,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;
}
}
}

@ -13,65 +13,134 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 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.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;
/**
* Test for {@link EncodeTransferMedataScgFilter}.
*
* @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
private GatewayFilterChain chain;
Registration registration;
@Mock
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);
}
@SpringBootApplication
protected static class TestApplication {
@AfterAll
static void afterAll() {
mockedApplicationContextAwareUtils.close();
}
@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,18 +37,21 @@ 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
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
classes = EncodeTransferMedataWebClientFilterTest.TestApplication.class,
properties = {"spring.config.location = classpath:application-test.yml"})
properties = {"spring.config.location = classpath:application-test.yml",
"spring.main.web-application-type = reactive"})
public class EncodeTransferMedataWebClientFilterTest {
@Autowired
private WebClient.Builder webClientBuilder;
@LocalServerPort
private int localServerPort;
@Test
public void testTransitiveMetadataFromApplicationConfig() {
@ -63,10 +66,6 @@ public class EncodeTransferMedataWebClientFilterTest {
assertThat(metadata).isEqualTo("2");
}
@LocalServerPort
private int localServerPort;
@SpringBootApplication
@RestController
protected static class TestApplication {

@ -0,0 +1,139 @@
/*
* 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.jupiter.api.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";
String callerIp = "localhost";
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, callerIp);
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(MessageMetadataContainer.LABEL_KEY_CALLER_IP)).isEqualTo(callerIp);
assertThat(reactiveMetadataProvider.getRawMetadataStringValue(notExistKey)).isNull();
request = MockServerHttpRequest.get("/echo/" + UrlUtils.decode("a@b")).build();
reactiveMetadataProvider = new ReactiveMetadataProvider(request, callerIp);
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";
String callerIp = "localhost";
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, callerIp);
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(MessageMetadataContainer.LABEL_KEY_CALLER_IP)).isEqualTo(callerIp);
assertThat(servletMetadataProvider.getRawMetadataStringValue(notExistKey)).isNull();
request.setRequestURI("/echo/" + UrlUtils.decode("a@b"));
assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo("/echo/a@b");
}
}

@ -24,8 +24,17 @@
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-model</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-metadata</artifactId>
</dependency>
<!-- Polaris dependencies end -->
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-threadlocal-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>

@ -21,22 +21,25 @@ package com.tencent.cloud.common.metadata;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Optional;
import java.util.function.BiConsumer;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.DiscoveryUtil;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.polaris.metadata.core.MetadataContainer;
import com.tencent.polaris.metadata.core.MetadataMapValue;
import com.tencent.polaris.metadata.core.MetadataObjectValue;
import com.tencent.polaris.metadata.core.MetadataStringValue;
import com.tencent.polaris.metadata.core.MetadataType;
import com.tencent.polaris.metadata.core.MetadataValue;
import com.tencent.polaris.metadata.core.TransitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* Metadata Context.
*
* @author Haotian Zhang
*/
public class MetadataContext {
public class MetadataContext extends com.tencent.polaris.metadata.core.manager.MetadataContext {
/**
* transitive context.
@ -63,6 +66,11 @@ public class MetadataContext {
*/
public static final String FRAGMENT_RAW_TRANSHEADERS_KV = "trans-headers-kv";
/**
* the key of the header(key-value) list needed to be store as loadbalance data.
*/
public static final String FRAGMENT_LB_METADATA = "load-balance-metadata";
private static final Logger LOG = LoggerFactory.getLogger(MetadataContext.class);
/**
* Namespace of local instance.
@ -108,22 +116,70 @@ public class MetadataContext {
LOCAL_SERVICE = serviceName;
}
private final Map<String, Map<String, String>> fragmentContexts;
public MetadataContext() {
super(MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX);
}
private final Map<String, Object> loadbalancerMetadata;
private Map<String, String> getMetadataAsMap(MetadataType metadataType, TransitiveType transitiveType, boolean downstream) {
MetadataContainer metadataContainer = getMetadataContainer(metadataType, downstream);
Map<String, String> values = new HashMap<>();
metadataContainer.iterateMetadataValues(new BiConsumer<String, MetadataValue>() {
@Override
public void accept(String s, MetadataValue metadataValue) {
if (metadataValue instanceof MetadataStringValue) {
MetadataStringValue metadataStringValue = (MetadataStringValue) metadataValue;
if (metadataStringValue.getTransitiveType() == transitiveType) {
values.put(s, metadataStringValue.getStringValue());
}
}
}
});
return values;
}
private void putMetadataAsMap(MetadataType metadataType, TransitiveType transitiveType, boolean downstream, Map<String, String> values) {
MetadataContainer metadataContainer = getMetadataContainer(metadataType, downstream);
for (Map.Entry<String, String> entry : values.entrySet()) {
metadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), transitiveType);
}
}
public MetadataContext() {
this.fragmentContexts = new ConcurrentHashMap<>();
this.loadbalancerMetadata = new ConcurrentHashMap<>();
private Map<String, String> getMapMetadataAsMap(MetadataType metadataType, String mapKey, TransitiveType transitiveType, boolean downstream) {
MetadataContainer metadataContainer = getMetadataContainer(metadataType, downstream);
Map<String, String> values = new HashMap<>();
MetadataValue metadataValue = metadataContainer.getMetadataValue(mapKey);
if (!(metadataValue instanceof MetadataMapValue)) {
return values;
}
MetadataMapValue metadataMapValue = (MetadataMapValue) metadataValue;
metadataMapValue.iterateMapValues(new BiConsumer<String, MetadataValue>() {
@Override
public void accept(String s, MetadataValue metadataValue) {
if (metadataValue instanceof MetadataStringValue) {
MetadataStringValue metadataStringValue = (MetadataStringValue) metadataValue;
if (metadataStringValue.getTransitiveType() == transitiveType) {
values.put(s, metadataStringValue.getStringValue());
}
}
}
});
return values;
}
private void putMapMetadataAsMap(MetadataType metadataType, String mapKey,
TransitiveType transitiveType, boolean downstream, Map<String, String> values) {
MetadataContainer metadataContainer = getMetadataContainer(metadataType, downstream);
for (Map.Entry<String, String> entry : values.entrySet()) {
metadataContainer.putMetadataMapValue(mapKey, entry.getKey(), entry.getValue(), transitiveType);
}
}
public Map<String, String> getDisposableMetadata() {
return this.getFragmentContext(MetadataContext.FRAGMENT_DISPOSABLE);
return getFragmentContext(FRAGMENT_DISPOSABLE);
}
public Map<String, String> getTransitiveMetadata() {
return this.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
return getFragmentContext(FRAGMENT_TRANSITIVE);
}
public Map<String, String> getCustomMetadata() {
@ -140,51 +196,76 @@ public class MetadataContext {
}
public Map<String, String> getTransHeaders() {
return this.getFragmentContext(MetadataContext.FRAGMENT_RAW_TRANSHEADERS);
return this.getFragmentContext(FRAGMENT_RAW_TRANSHEADERS);
}
public Map<String, String> getTransHeadersKV() {
return this.getFragmentContext(MetadataContext.FRAGMENT_RAW_TRANSHEADERS_KV);
return getFragmentContext(FRAGMENT_RAW_TRANSHEADERS_KV);
}
public Map<String, Object> getLoadbalancerMetadata() {
return this.loadbalancerMetadata;
MetadataContainer metadataContainer = getMetadataContainer(MetadataType.APPLICATION, false);
MetadataValue metadataValue = metadataContainer.getMetadataValue(FRAGMENT_LB_METADATA);
Map<String, Object> values = new HashMap<>();
if (metadataValue instanceof MetadataMapValue) {
MetadataMapValue metadataMapValue = (MetadataMapValue) metadataValue;
metadataMapValue.iterateMapValues(new BiConsumer<String, MetadataValue>() {
@Override
public void accept(String s, MetadataValue metadataValue) {
if (metadataValue instanceof MetadataObjectValue) {
Optional<?> objectValue = ((MetadataObjectValue<?>) metadataValue).getObjectValue();
objectValue.ifPresent(o -> values.put(s, o));
}
}
});
}
return values;
}
public void setLoadbalancer(String key, Object value) {
MetadataContainer metadataContainer = getMetadataContainer(MetadataType.APPLICATION, false);
metadataContainer.putMetadataMapObjectValue(FRAGMENT_LB_METADATA, key, value);
}
public void setTransitiveMetadata(Map<String, String> transitiveMetadata) {
this.putFragmentContext(FRAGMENT_TRANSITIVE, Collections.unmodifiableMap(transitiveMetadata));
putFragmentContext(FRAGMENT_TRANSITIVE, Collections.unmodifiableMap(transitiveMetadata));
}
public void setDisposableMetadata(Map<String, String> disposableMetadata) {
this.putFragmentContext(FRAGMENT_DISPOSABLE, Collections.unmodifiableMap(disposableMetadata));
putFragmentContext(FRAGMENT_DISPOSABLE, Collections.unmodifiableMap(disposableMetadata));
}
public void setUpstreamDisposableMetadata(Map<String, String> upstreamDisposableMetadata) {
this.putFragmentContext(FRAGMENT_UPSTREAM_DISPOSABLE, Collections.unmodifiableMap(upstreamDisposableMetadata));
putFragmentContext(FRAGMENT_UPSTREAM_DISPOSABLE, Collections.unmodifiableMap(upstreamDisposableMetadata));
}
public void setTransHeadersKV(String key, String value) {
this.putContext(FRAGMENT_RAW_TRANSHEADERS_KV, key, value);
putContext(FRAGMENT_RAW_TRANSHEADERS_KV, key, value);
}
public void setTransHeaders(String key, String value) {
this.putContext(FRAGMENT_RAW_TRANSHEADERS, key, value);
}
public void setLoadbalancer(String key, Object value) {
this.loadbalancerMetadata.put(key, value);
putContext(FRAGMENT_RAW_TRANSHEADERS, key, value);
}
public Map<String, String> getFragmentContext(String fragment) {
Map<String, String> fragmentContext = fragmentContexts.get(fragment);
if (fragmentContext == null) {
return Collections.emptyMap();
switch (fragment) {
case FRAGMENT_TRANSITIVE:
return getMetadataAsMap(MetadataType.CUSTOM, TransitiveType.PASS_THROUGH, false);
case FRAGMENT_DISPOSABLE:
return getMetadataAsMap(MetadataType.CUSTOM, TransitiveType.DISPOSABLE, false);
case FRAGMENT_UPSTREAM_DISPOSABLE:
return getMetadataAsMap(MetadataType.CUSTOM, TransitiveType.DISPOSABLE, true);
case FRAGMENT_RAW_TRANSHEADERS:
return getMapMetadataAsMap(MetadataType.CUSTOM, FRAGMENT_RAW_TRANSHEADERS, TransitiveType.NONE, false);
case FRAGMENT_RAW_TRANSHEADERS_KV:
return getMapMetadataAsMap(MetadataType.CUSTOM, FRAGMENT_RAW_TRANSHEADERS_KV, TransitiveType.PASS_THROUGH, false);
default:
return getMapMetadataAsMap(MetadataType.CUSTOM, fragment, TransitiveType.NONE, false);
}
return Collections.unmodifiableMap(fragmentContext);
}
public String getContext(String fragment, String key) {
Map<String, String> fragmentContext = fragmentContexts.get(fragment);
Map<String, String> fragmentContext = getFragmentContext(fragment);
if (fragmentContext == null) {
return null;
}
@ -192,22 +273,31 @@ public class MetadataContext {
}
public void putContext(String fragment, String key, String value) {
Map<String, String> fragmentContext = fragmentContexts.get(fragment);
if (fragmentContext == null) {
fragmentContext = new ConcurrentHashMap<>();
fragmentContexts.put(fragment, fragmentContext);
}
fragmentContext.put(key, value);
Map<String, String> values = new HashMap<>();
values.put(key, value);
putFragmentContext(fragment, values);
}
public void putFragmentContext(String fragment, Map<String, String> context) {
fragmentContexts.put(fragment, context);
}
@Override
public String toString() {
return "MetadataContext{" +
"fragmentContexts=" + JacksonUtils.serialize2Json(fragmentContexts) +
'}';
switch (fragment) {
case FRAGMENT_TRANSITIVE:
putMetadataAsMap(MetadataType.CUSTOM, TransitiveType.PASS_THROUGH, false, context);
break;
case FRAGMENT_DISPOSABLE:
putMetadataAsMap(MetadataType.CUSTOM, TransitiveType.DISPOSABLE, false, context);
break;
case FRAGMENT_UPSTREAM_DISPOSABLE:
putMetadataAsMap(MetadataType.CUSTOM, TransitiveType.DISPOSABLE, true, context);
break;
case FRAGMENT_RAW_TRANSHEADERS:
putMapMetadataAsMap(MetadataType.CUSTOM, FRAGMENT_RAW_TRANSHEADERS, TransitiveType.NONE, false, context);
break;
case FRAGMENT_RAW_TRANSHEADERS_KV:
putMapMetadataAsMap(MetadataType.CUSTOM, FRAGMENT_RAW_TRANSHEADERS_KV, TransitiveType.PASS_THROUGH, false, context);
break;
default:
putMapMetadataAsMap(MetadataType.CUSTOM, fragment, TransitiveType.NONE, false, context);
break;
}
}
}

@ -24,6 +24,11 @@ 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;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@ -38,48 +43,52 @@ import static com.tencent.cloud.common.metadata.MetadataContext.FRAGMENT_UPSTREA
*/
public final class MetadataContextHolder {
private static final ThreadLocal<MetadataContext> METADATA_CONTEXT = new InheritableThreadLocal<>();
private static MetadataLocalProperties metadataLocalProperties;
private static StaticMetadataManager staticMetadataManager;
static {
com.tencent.polaris.metadata.core.manager.MetadataContextHolder.setInitializer(MetadataContextHolder::createMetadataManager);
}
private MetadataContextHolder() {
}
/**
* Get metadata context. Create if not existing.
* @return METADATA_CONTEXT
*/
public static MetadataContext get() {
if (METADATA_CONTEXT.get() != null) {
return METADATA_CONTEXT.get();
}
return (MetadataContext) com.tencent.polaris.metadata.core.manager.MetadataContextHolder.getOrCreate();
}
private static MetadataContext createMetadataManager() {
MetadataContext metadataManager = new MetadataContext();
if (metadataLocalProperties == null) {
metadataLocalProperties = ApplicationContextAwareUtils.getApplicationContext().getBean(MetadataLocalProperties.class);
metadataLocalProperties = ApplicationContextAwareUtils.getApplicationContext()
.getBean(MetadataLocalProperties.class);
}
if (staticMetadataManager == null) {
staticMetadataManager = ApplicationContextAwareUtils.getApplicationContext().getBean(StaticMetadataManager.class);
staticMetadataManager = ApplicationContextAwareUtils.getApplicationContext()
.getBean(StaticMetadataManager.class);
}
MetadataContainer metadataContainer = metadataManager.getMetadataContainer(MetadataType.CUSTOM, false);
Map<String, String> mergedStaticTransitiveMetadata = staticMetadataManager.getMergedStaticTransitiveMetadata();
for (Map.Entry<String, String> entry : mergedStaticTransitiveMetadata.entrySet()) {
metadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.PASS_THROUGH);
}
Map<String, String> mergedStaticDisposableMetadata = staticMetadataManager.getMergedStaticDisposableMetadata();
for (Map.Entry<String, String> entry : mergedStaticDisposableMetadata.entrySet()) {
metadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.DISPOSABLE);
}
// init static transitive metadata
MetadataContext metadataContext = new MetadataContext();
metadataContext.setTransitiveMetadata(staticMetadataManager.getMergedStaticTransitiveMetadata());
metadataContext.setDisposableMetadata(staticMetadataManager.getMergedStaticDisposableMetadata());
if (StringUtils.hasText(staticMetadataManager.getTransHeader())) {
metadataContext.setTransHeaders(staticMetadataManager.getTransHeader(), "");
String transHeader = staticMetadataManager.getTransHeader();
metadataContainer.putMetadataMapValue(MetadataContext.FRAGMENT_RAW_TRANSHEADERS, transHeader, "", TransitiveType.NONE);
}
METADATA_CONTEXT.set(metadataContext);
return METADATA_CONTEXT.get();
return metadataManager;
}
/**
* Get disposable metadata value from thread local .
* @param key metadata key .
*
* @param key metadata key .
* @param upstream upstream disposable , otherwise will return local static disposable metadata .
* @return target disposable metadata value .
*/
@ -95,6 +104,7 @@ public final class MetadataContextHolder {
/**
* Get all disposable metadata value from thread local .
*
* @param upstream upstream disposable , otherwise will return local static disposable metadata .
* @return target disposable metadata value .
*/
@ -112,43 +122,46 @@ public final class MetadataContextHolder {
/**
* Set metadata context.
*
* @param metadataContext metadata context
*/
public static void set(MetadataContext metadataContext) {
METADATA_CONTEXT.set(metadataContext);
com.tencent.polaris.metadata.core.manager.MetadataContextHolder.set(metadataContext);
}
/**
* Save metadata map to thread local.
*
* @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) {
// Init ThreadLocal.
MetadataContextHolder.remove();
MetadataContext metadataContext = MetadataContextHolder.get();
// Save transitive metadata to ThreadLocal.
if (!CollectionUtils.isEmpty(dynamicTransitiveMetadata)) {
Map<String, String> staticTransitiveMetadata = metadataContext.getTransitiveMetadata();
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(staticTransitiveMetadata);
mergedTransitiveMetadata.putAll(dynamicTransitiveMetadata);
metadataContext.setTransitiveMetadata(Collections.unmodifiableMap(mergedTransitiveMetadata));
}
if (!CollectionUtils.isEmpty(dynamicDisposableMetadata)) {
Map<String, String> mergedUpstreamDisposableMetadata = new HashMap<>(dynamicDisposableMetadata);
metadataContext.setUpstreamDisposableMetadata(Collections.unmodifiableMap(mergedUpstreamDisposableMetadata));
}
Map<String, String> staticDisposableMetadata = metadataContext.getDisposableMetadata();
metadataContext.setDisposableMetadata(Collections.unmodifiableMap(staticDisposableMetadata));
MetadataContextHolder.set(metadataContext);
public static void init(Map<String, String> dynamicTransitiveMetadata, Map<String, String> dynamicDisposableMetadata,
MetadataProvider callerMetadataProvider) {
com.tencent.polaris.metadata.core.manager.MetadataContextHolder.refresh(metadataManager -> {
MetadataContainer metadataContainerUpstream = metadataManager.getMetadataContainer(MetadataType.CUSTOM, false);
if (!CollectionUtils.isEmpty(dynamicTransitiveMetadata)) {
for (Map.Entry<String, String> entry : dynamicTransitiveMetadata.entrySet()) {
metadataContainerUpstream.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.PASS_THROUGH);
}
}
MetadataContainer metadataContainerDownstream = metadataManager.getMetadataContainer(MetadataType.CUSTOM, true);
if (!CollectionUtils.isEmpty(dynamicDisposableMetadata)) {
for (Map.Entry<String, String> entry : dynamicDisposableMetadata.entrySet()) {
metadataContainerDownstream.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.DISPOSABLE);
}
}
if (callerMetadataProvider != null) {
MessageMetadataContainer callerMessageContainer = metadataManager.getMetadataContainer(MetadataType.MESSAGE, true);
callerMessageContainer.setMetadataProvider(callerMetadataProvider);
}
});
}
/**
* Remove metadata context.
*/
public static void remove() {
METADATA_CONTEXT.remove();
com.tencent.polaris.metadata.core.manager.MetadataContextHolder.remove();
}
}

@ -31,12 +31,15 @@ import static java.util.Locale.ENGLISH;
public final class ReflectionUtils extends org.springframework.util.ReflectionUtils {
private final static String SET_PREFIX = "set";
private ReflectionUtils() {
}
public static boolean writableBeanField(Field field) {
String fieldName = field.getName();
String setMethodName = SET_PREFIX + capitalize(fieldName);
return ClassUtils.hasMethod(field.getDeclaringClass(), setMethodName, field.getType());
}
@ -65,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);
}
}
}

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

@ -36,7 +36,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = MetadataContextHolderTest.TestApplication.class,
properties = {"spring.config.location = classpath:application-test.yml"})
properties = {"spring.config.location = classpath:application-test.yml",
"spring.main.web-application-type = reactive"})
public class MetadataContextHolderTest {
@Test
@ -62,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");

@ -192,6 +192,12 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-threadlocal-plugin</artifactId>
<version>${revision}</version>
</dependency>
<!-- third part framework dependencies -->
<dependency>
<groupId>com.google.guava</groupId>

@ -19,6 +19,7 @@
<module>spring-cloud-tencent-gateway-plugin</module>
<module>spring-cloud-starter-tencent-discovery-adapter-plugin</module>
<module>spring-cloud-tencent-lossless-plugin</module>
<module>spring-cloud-starter-tencent-threadlocal-plugin</module>
</modules>
</project>

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-starter-tencent-threadlocal-plugin</artifactId>
<name>Spring Cloud Starter Tencent ThreadLocal plugin</name>
<dependencies>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-threadlocal</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -0,0 +1,45 @@
/*
* 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.plugin.threadlocal;
import java.util.function.Consumer;
import java.util.function.Supplier;
import com.tencent.polaris.threadlocal.cross.RunnableWrapper;
import org.springframework.core.task.TaskExecutor;
public class TaskExecutorWrapper<T> implements TaskExecutor {
private final TaskExecutor taskExecutor;
private final Supplier<T> contextGetter;
private final Consumer<T> contextSetter;
public TaskExecutorWrapper(TaskExecutor taskExecutor, Supplier<T> contextGetter, Consumer<T> contextSetter) {
this.taskExecutor = taskExecutor;
this.contextGetter = contextGetter;
this.contextSetter = contextSetter;
}
@Override
public void execute(Runnable command) {
taskExecutor.execute(new RunnableWrapper<>(command, contextGetter, contextSetter));
}
}

@ -0,0 +1,62 @@
/*
* 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.plugin.threadlocal;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.fail;
/**
* Test for {@link TaskExecutorWrapper}.
*
* @author Haotian Zhang
*/
public class TaskExecutorWrapperTest {
private static final ThreadLocal<String> TEST_THREAD_LOCAL = new ThreadLocal<>();
private static final String TEST = "TEST";
@Test
public void testExecute() {
TEST_THREAD_LOCAL.set(TEST);
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.initialize();
AtomicReference<Boolean> result = new AtomicReference<>(false);
CountDownLatch latch = new CountDownLatch(1);
TaskExecutorWrapper<String> taskExecutorWrapper = new TaskExecutorWrapper<>(
executor, TEST_THREAD_LOCAL::get, TEST_THREAD_LOCAL::set);
taskExecutorWrapper.execute(() -> {
result.set(TEST.equals(TEST_THREAD_LOCAL.get()));
latch.countDown();
});
try {
latch.await();
assertThat(result.get()).isTrue();
}
catch (InterruptedException e) {
fail(e.getMessage());
}
}
}

@ -21,7 +21,6 @@ import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
@ -32,11 +31,10 @@ import feign.Request;
import feign.Request.Options;
import feign.Response;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import static com.tencent.cloud.rpc.enhancement.resttemplate.PolarisLoadBalancerRequestTransformer.LOAD_BALANCER_SERVICE_INSTANCE;
import static feign.Util.checkNotNull;
/**
@ -69,10 +67,20 @@ public class EnhancedFeignClient implements Client {
.url(url)
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setOriginRequest(request);
enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance());
enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get()
.getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), url);
String svcName = request.requestTemplate().feignTarget().name();
DefaultServiceInstance serviceInstance = new DefaultServiceInstance(
String.format("%s-%s-%d", svcName, url.getHost(), url.getPort()),
svcName, url.getHost(), url.getPort(), url.getScheme().equals("https"));
// -1 means access directly by url
if (serviceInstance.getPort() == -1) {
enhancedPluginContext.setTargetServiceInstance(null, url);
}
else {
enhancedPluginContext.setTargetServiceInstance(serviceInstance, url);
}
// Run pre enhanced plugins.
pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext);

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

@ -27,15 +27,15 @@ import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
/**
* EnhancedGatewayGlobalFilter.
@ -51,31 +51,40 @@ 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();
URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder()
.httpHeaders(exchange.getRequest().getHeaders())
.httpMethod(exchange.getRequest().getMethod())
.url(uri)
.httpHeaders(originExchange.getRequest().getHeaders())
.httpMethod(originExchange.getRequest().getMethod())
.url(originExchange.getRequest().getURI())
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance());
Response<ServiceInstance> serviceInstanceResponse = exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR);
if (serviceInstanceResponse != null && serviceInstanceResponse.hasServer()) {
ServiceInstance instance = serviceInstanceResponse.getServer();
enhancedPluginContext.setTargetServiceInstance(instance, exchange.getRequest().getURI());
}
else {
enhancedPluginContext.setTargetServiceInstance(null, uri);
}
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 -> {
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
enhancedPluginContext.getRequest().setUrl(uri);
if (uri != null) {
if (route != null && route.getUri().getScheme().contains("lb")) {
DefaultServiceInstance serviceInstance = new DefaultServiceInstance();
serviceInstance.setServiceId(route.getUri().getHost());
serviceInstance.setHost(uri.getHost());
serviceInstance.setPort(uri.getPort());
enhancedPluginContext.setTargetServiceInstance(serviceInstance, null);
}
else {
enhancedPluginContext.setTargetServiceInstance(null, uri);
}
}
})
.doOnSuccess(v -> {
enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime);
EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder()

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

@ -39,10 +39,9 @@ import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Response;
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.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@ -56,8 +55,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
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_LOADBALANCER_RESPONSE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
@ExtendWith(MockitoExtension.class)
public class EnhancedGatewayGlobalFilterTest {
@ -111,20 +110,10 @@ public class EnhancedGatewayGlobalFilterTest {
doReturn(HttpMethod.GET).when(request).getMethod();
doReturn(new HttpHeaders()).when(response).getHeaders();
doReturn(Mono.empty()).when(chain).filter(exchange);
ServiceInstance serviceInstance = mock(ServiceInstance.class);
Response<ServiceInstance> serviceInstanceResponse = new Response<ServiceInstance>() {
@Override
public boolean hasServer() {
return true;
}
@Override
public ServiceInstance getServer() {
return serviceInstance;
}
};
doReturn(serviceInstanceResponse).when(exchange).getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR);
Route route = mock(Route.class);
URI uri = new URI("http://TEST/");
doReturn(uri).when(route).getUri();
doReturn(route).when(exchange).getAttribute(GATEWAY_ROUTE_ATTR);
doReturn(new URI("http://0.0.0.0/")).when(exchange).getAttribute(GATEWAY_REQUEST_URL_ATTR);
doReturn(request).when(exchange).getRequest();
doReturn(response).when(exchange).getResponse();

Loading…
Cancel
Save