diff --git a/CHANGELOG.md b/CHANGELOG.md index d5599378a..0a19a6354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,4 @@ - [feat:upgrade jacoco version.](https://github.com/Tencent/spring-cloud-tencent/pull/1307) - [fix: fix RouterLabelRestTemplateInterceptor add response headers exception with httpclient5.](https://github.com/Tencent/spring-cloud-tencent/pull/1376) - [feat: support lossless online and offline](https://github.com/Tencent/spring-cloud-tencent/pull/1377) +- [feat: support lane router](https://github.com/Tencent/spring-cloud-tencent/pull/1378) diff --git a/spring-cloud-starter-tencent-metadata-transfer/pom.xml b/spring-cloud-starter-tencent-metadata-transfer/pom.xml index 642bbec1a..ca8350c50 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/pom.xml +++ b/spring-cloud-starter-tencent-metadata-transfer/pom.xml @@ -19,6 +19,12 @@ com.tencent.cloud spring-cloud-tencent-commons + + + com.tencent.cloud + spring-cloud-tencent-rpc-enhancement + + @@ -50,6 +56,24 @@ spring-cloud-starter-loadbalancer test + + + com.tencent.polaris + polaris-test-mock-discovery + test + + + + com.tencent.polaris + polaris-test-common + test + + + + org.mockito + mockito-inline + test + diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java index 2b2bd4cb9..53f9efccc 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java @@ -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 javax.servlet.DispatcherType.ASYNC; import static javax.servlet.DispatcherType.ERROR; @@ -74,8 +66,8 @@ public FilterRegistrationBean metadataServl } @Bean - public DecodeTransferMetadataServletFilter metadataServletFilter() { - return new DecodeTransferMetadataServletFilter(); + public DecodeTransferMetadataServletFilter metadataServletFilter(PolarisContextProperties polarisContextProperties) { + return new DecodeTransferMetadataServletFilter(polarisContextProperties); } } @@ -87,8 +79,8 @@ public DecodeTransferMetadataServletFilter metadataServletFilter() { 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 DecodeTransferMetadataReactiveFilter metadataReactiveFilter() { */ @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 GlobalFilter encodeTransferMedataScgFilter() { */ @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 EncodeTransferMedataFeignInterceptor encodeTransferMedataFeignInterceptor */ @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 restTemplates = Collections.emptyList(); - - @Bean - public EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor() { - return new EncodeTransferMedataRestTemplateInterceptor(); - } - @Bean - public SmartInitializingSingleton addEncodeTransferMetadataInterceptorForRestTemplate(EncodeTransferMedataRestTemplateInterceptor interceptor) { - return () -> restTemplates.forEach(restTemplate -> { - List list = new ArrayList<>(restTemplate.getInterceptors()); - list.add(interceptor); - restTemplate.setInterceptors(list); - }); + public EncodeTransferMedataRestTemplateEnhancedPlugin encodeTransferMedataRestTemplateEnhancedPlugin() { + return new EncodeTransferMedataRestTemplateEnhancedPlugin(); } } @@ -148,20 +131,12 @@ public SmartInitializingSingleton addEncodeTransferMetadataInterceptorForRestTem */ @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 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(); } } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java index 9c967680f..eb7c8a91c 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java @@ -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.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 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 @@ */ 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 int getOrder() { public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) { // Get metadata string from http header. ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest(); - Map internalTransitiveMetadata = getIntervalMetadata(serverHttpRequest, CUSTOM_METADATA); Map customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(serverWebExchange); Map mergedTransitiveMetadata = new HashMap<>(); mergedTransitiveMetadata.putAll(internalTransitiveMetadata); mergedTransitiveMetadata.putAll(customTransitiveMetadata); - Map internalDisposableMetadata = getIntervalMetadata(serverHttpRequest, CUSTOM_DISPOSABLE_METADATA); Map 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, @@ -83,20 +85,14 @@ public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain web TransHeadersTransfer.transfer(serverHttpRequest); return webFilterChain.filter(serverWebExchange) + .doOnError(throwable -> LOG.error("handle metadata[{}] error.", + MetadataContextHolder.get(), throwable)) .doFinally((type) -> MetadataContextHolder.remove()); } private Map 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); diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java index 23f7c2a72..63abc08ec 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java @@ -19,8 +19,6 @@ package com.tencent.cloud.metadata.core; import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; import java.util.HashMap; import java.util.Map; @@ -32,15 +30,16 @@ 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.lang.NonNull; -import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; -import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA; import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA; @@ -55,6 +54,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) @@ -65,11 +70,10 @@ protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest, Map mergedTransitiveMetadata = new HashMap<>(); mergedTransitiveMetadata.putAll(internalTransitiveMetadata); mergedTransitiveMetadata.putAll(customTransitiveMetadata); - Map internalDisposableMetadata = getInternalMetadata(httpServletRequest, CUSTOM_DISPOSABLE_METADATA); Map mergedDisposableMetadata = new HashMap<>(internalDisposableMetadata); - - MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata); + ServletMetadataProvider metadataProvider = new ServletMetadataProvider(httpServletRequest, polarisContextProperties.getLocalIpAddress()); + MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, metadataProvider); TransHeadersTransfer.transfer(httpServletRequest); @@ -84,15 +88,7 @@ protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest, private Map 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. diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignEnhancedPlugin.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignEnhancedPlugin.java new file mode 100644 index 000000000..ff722edc6 --- /dev/null +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignEnhancedPlugin.java @@ -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 customMetadata = metadataContext.getCustomMetadata(); + Map disposableMetadata = metadataContext.getDisposableMetadata(); + Map transHeaders = metadataContext.getTransHeadersKV(); + + MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false); + Map 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 transHeaders) { + if (!CollectionUtils.isEmpty(transHeaders)) { + Map> 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 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 headerMap) { + if (!CollectionUtils.isEmpty(headerMap)) { + Map> 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> getModifiableHeaders(Request request) { + Map> headers; + headers = (Map>) 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; + } +} diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignInterceptor.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignInterceptor.java deleted file mode 100644 index edbf5f0bc..000000000 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignInterceptor.java +++ /dev/null @@ -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 customMetadata = metadataContext.getCustomMetadata(); - Map disposableMetadata = metadataContext.getDisposableMetadata(); - Map 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 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 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); - } - } - } -} diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptor.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateEnhancedPlugin.java similarity index 60% rename from spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptor.java rename to spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateEnhancedPlugin.java index cbf4901b3..50e42885b 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptor.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateEnhancedPlugin.java @@ -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 customMetadata = metadataContext.getCustomMetadata(); Map disposableMetadata = metadataContext.getDisposableMetadata(); Map transHeaders = metadataContext.getTransHeadersKV(); + MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false); + Map 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 ClientHttpResponse intercept(@NonNull HttpRequest httpRequest, @NonNull b // 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 transHeaders) { @@ -82,6 +84,12 @@ private void buildTransmittedHeader(HttpRequest request, Map tra } } + private void buildHeaderMap(HttpRequest request, Map 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 @@ private void buildTransmittedHeader(HttpRequest request, Map tra */ private void buildMetadataHeader(HttpRequest request, Map 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; + } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgEnhancedPlugin.java similarity index 58% rename from spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java rename to spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgEnhancedPlugin.java index eaf0ea466..8c996bbf1 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgEnhancedPlugin.java @@ -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 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,12 +70,22 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { Map customMetadata = metadataContext.getCustomMetadata(); Map disposableMetadata = metadataContext.getDisposableMetadata(); + MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false); + Map 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 headerMap) { + if (!CollectionUtils.isEmpty(headerMap)) { + headerMap.forEach((key, value) -> builder.header(key, UrlUtils.encode(value))); + } } /** @@ -82,13 +96,12 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { */ private void buildMetadataHeader(ServerHttpRequest.Builder builder, Map 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; + } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientEnhancedPlugin.java similarity index 57% rename from spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilter.java rename to spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientEnhancedPlugin.java index 3547add0e..2967a2b4c 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilter.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientEnhancedPlugin.java @@ -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 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 customMetadata = metadataContext.getCustomMetadata(); Map disposableMetadata = metadataContext.getDisposableMetadata(); Map transHeaders = metadataContext.getTransHeadersKV(); + MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false); + Map 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 transHeaders) { @@ -68,6 +81,11 @@ private void buildTransmittedHeader(ClientRequest.Builder requestBuilder, Map 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 @@ private void buildTransmittedHeader(ClientRequest.Builder requestBuilder, Map 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; + } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ReactiveMetadataProvider.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ReactiveMetadataProvider.java new file mode 100644 index 000000000..d39d74ed8 --- /dev/null +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ReactiveMetadataProvider.java @@ -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; + } + } +} diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ServletMetadataProvider.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ServletMetadataProvider.java new file mode 100644 index 000000000..8da5276b1 --- /dev/null +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ServletMetadataProvider.java @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.metadata.provider; + +import javax.servlet.http.HttpServletRequest; + +import com.tencent.cloud.common.util.UrlUtils; +import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils; +import com.tencent.polaris.metadata.core.MessageMetadataContainer; +import com.tencent.polaris.metadata.core.MetadataProvider; + + +/** + * MetadataProvider used for Servlet. + * + * @author Shedfree Wu + */ +public class ServletMetadataProvider implements MetadataProvider { + + private HttpServletRequest httpServletRequest; + + 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; + } + } +} diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java index 12073bc9e..4da4dc8d3 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java @@ -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 @@ public class MetadataTransferAutoConfigurationTest { private final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner(); + private final ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = new ReactiveWebApplicationContextRunner(); /** * No any web application. @@ -57,36 +52,26 @@ public void test1() { 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 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 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); }); } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilterTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilterTest.java index 18886a917..ff27c0c4d 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilterTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilterTest.java @@ -20,6 +20,7 @@ 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 @@ @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 diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilterTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilterTest.java index 900e82ed0..0107a8f98 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilterTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilterTest.java @@ -45,7 +45,9 @@ @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 diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignInterceptorTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignInterceptorTest.java index 93e553787..e34f4e9ae 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignInterceptorTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataFeignInterceptorTest.java @@ -40,14 +40,16 @@ 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 diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptorTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptorTest.java index 95f0f32a1..b0ce9217c 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptorTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataRestTemplateInterceptorTest.java @@ -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.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -39,14 +46,15 @@ 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 @@ protected static class TestApplication { @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 String test() { 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 getMetadata() { + return null; + } + } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilterTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilterTest.java index bdfcfb440..cbc1116bc 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilterTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilterTest.java @@ -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 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 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 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(); } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilterTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilterTest.java index ec44eec32..fe7c30d27 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilterTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/EncodeTransferMedataWebClientFilterTest.java @@ -37,18 +37,21 @@ 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 void testTransitiveMetadataFromApplicationConfig() { assertThat(metadata).isEqualTo("2"); } - @LocalServerPort - private int localServerPort; - - @SpringBootApplication @RestController protected static class TestApplication { diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/MetadataProviderTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/MetadataProviderTest.java new file mode 100644 index 000000000..dab78d99a --- /dev/null +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/MetadataProviderTest.java @@ -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"); + } +} diff --git a/spring-cloud-tencent-commons/pom.xml b/spring-cloud-tencent-commons/pom.xml index 6cc5556be..8632ccdd3 100644 --- a/spring-cloud-tencent-commons/pom.xml +++ b/spring-cloud-tencent-commons/pom.xml @@ -24,8 +24,17 @@ com.tencent.polaris polaris-model + + com.tencent.polaris + polaris-metadata + + + com.tencent.cloud + spring-cloud-starter-tencent-threadlocal-plugin + + org.springframework.boot spring-boot-autoconfigure diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContext.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContext.java index 8fcd89855..11b667c1a 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContext.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContext.java @@ -21,22 +21,25 @@ 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> fragmentContexts; + public MetadataContext() { + super(MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX); + } - private final Map loadbalancerMetadata; + private Map getMetadataAsMap(MetadataType metadataType, TransitiveType transitiveType, boolean downstream) { + MetadataContainer metadataContainer = getMetadataContainer(metadataType, downstream); + Map values = new HashMap<>(); + metadataContainer.iterateMetadataValues(new BiConsumer() { + @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 values) { + MetadataContainer metadataContainer = getMetadataContainer(metadataType, downstream); + for (Map.Entry entry : values.entrySet()) { + metadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), transitiveType); + } + } - public MetadataContext() { - this.fragmentContexts = new ConcurrentHashMap<>(); - this.loadbalancerMetadata = new ConcurrentHashMap<>(); + private Map getMapMetadataAsMap(MetadataType metadataType, String mapKey, TransitiveType transitiveType, boolean downstream) { + MetadataContainer metadataContainer = getMetadataContainer(metadataType, downstream); + Map values = new HashMap<>(); + MetadataValue metadataValue = metadataContainer.getMetadataValue(mapKey); + if (!(metadataValue instanceof MetadataMapValue)) { + return values; + } + MetadataMapValue metadataMapValue = (MetadataMapValue) metadataValue; + metadataMapValue.iterateMapValues(new BiConsumer() { + @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 values) { + MetadataContainer metadataContainer = getMetadataContainer(metadataType, downstream); + for (Map.Entry entry : values.entrySet()) { + metadataContainer.putMetadataMapValue(mapKey, entry.getKey(), entry.getValue(), transitiveType); + } } public Map getDisposableMetadata() { - return this.getFragmentContext(MetadataContext.FRAGMENT_DISPOSABLE); + return getFragmentContext(FRAGMENT_DISPOSABLE); } public Map getTransitiveMetadata() { - return this.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); + return getFragmentContext(FRAGMENT_TRANSITIVE); } public Map getCustomMetadata() { @@ -140,51 +196,76 @@ public Map getCustomMetadata() { } public Map getTransHeaders() { - return this.getFragmentContext(MetadataContext.FRAGMENT_RAW_TRANSHEADERS); + return this.getFragmentContext(FRAGMENT_RAW_TRANSHEADERS); } public Map getTransHeadersKV() { - return this.getFragmentContext(MetadataContext.FRAGMENT_RAW_TRANSHEADERS_KV); + return getFragmentContext(FRAGMENT_RAW_TRANSHEADERS_KV); } public Map getLoadbalancerMetadata() { - return this.loadbalancerMetadata; + MetadataContainer metadataContainer = getMetadataContainer(MetadataType.APPLICATION, false); + MetadataValue metadataValue = metadataContainer.getMetadataValue(FRAGMENT_LB_METADATA); + Map values = new HashMap<>(); + if (metadataValue instanceof MetadataMapValue) { + MetadataMapValue metadataMapValue = (MetadataMapValue) metadataValue; + metadataMapValue.iterateMapValues(new BiConsumer() { + @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 transitiveMetadata) { - this.putFragmentContext(FRAGMENT_TRANSITIVE, Collections.unmodifiableMap(transitiveMetadata)); + putFragmentContext(FRAGMENT_TRANSITIVE, Collections.unmodifiableMap(transitiveMetadata)); } public void setDisposableMetadata(Map disposableMetadata) { - this.putFragmentContext(FRAGMENT_DISPOSABLE, Collections.unmodifiableMap(disposableMetadata)); + putFragmentContext(FRAGMENT_DISPOSABLE, Collections.unmodifiableMap(disposableMetadata)); } public void setUpstreamDisposableMetadata(Map 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 getFragmentContext(String fragment) { - Map 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 fragmentContext = fragmentContexts.get(fragment); + Map fragmentContext = getFragmentContext(fragment); if (fragmentContext == null) { return null; } @@ -192,22 +273,31 @@ public String getContext(String fragment, String key) { } public void putContext(String fragment, String key, String value) { - Map fragmentContext = fragmentContexts.get(fragment); - if (fragmentContext == null) { - fragmentContext = new ConcurrentHashMap<>(); - fragmentContexts.put(fragment, fragmentContext); - } - fragmentContext.put(key, value); + Map values = new HashMap<>(); + values.put(key, value); + putFragmentContext(fragment, values); } public void putFragmentContext(String fragment, Map 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; + } } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java index 4251b8d3d..71e1b7754 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java @@ -24,6 +24,11 @@ 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 @@ */ public final class MetadataContextHolder { - private static final ThreadLocal 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 mergedStaticTransitiveMetadata = staticMetadataManager.getMergedStaticTransitiveMetadata(); + for (Map.Entry entry : mergedStaticTransitiveMetadata.entrySet()) { + metadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.PASS_THROUGH); + } + Map mergedStaticDisposableMetadata = staticMetadataManager.getMergedStaticDisposableMetadata(); + for (Map.Entry 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 static Optional getDisposableMetadata(String key, boolean upstrea /** * 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,44 +122,46 @@ public static Map getAllDisposableMetadata(boolean upstream) { /** * 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 dynamicTransitiveMetadata, Map dynamicDisposableMetadata) { - // Init ThreadLocal. - MetadataContextHolder.remove(); - MetadataContext metadataContext = MetadataContextHolder.get(); - - // Save transitive metadata to ThreadLocal. - if (!CollectionUtils.isEmpty(dynamicTransitiveMetadata)) { - Map staticTransitiveMetadata = metadataContext.getTransitiveMetadata(); - Map mergedTransitiveMetadata = new HashMap<>(); - mergedTransitiveMetadata.putAll(staticTransitiveMetadata); - mergedTransitiveMetadata.putAll(dynamicTransitiveMetadata); - metadataContext.setTransitiveMetadata(Collections.unmodifiableMap(mergedTransitiveMetadata)); - } - if (!CollectionUtils.isEmpty(dynamicDisposableMetadata)) { - Map mergedUpstreamDisposableMetadata = new HashMap<>(dynamicDisposableMetadata); - metadataContext.setUpstreamDisposableMetadata(Collections.unmodifiableMap(mergedUpstreamDisposableMetadata)); - } - - Map staticDisposableMetadata = metadataContext.getFragmentContext(FRAGMENT_DISPOSABLE); - metadataContext.setDisposableMetadata(Collections.unmodifiableMap(staticDisposableMetadata)); - MetadataContextHolder.set(metadataContext); + public static void init(Map dynamicTransitiveMetadata, Map 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 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 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(); } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ReflectionUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ReflectionUtils.java index 68ff6ac09..d822b1943 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ReflectionUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ReflectionUtils.java @@ -50,4 +50,38 @@ public static String capitalize(String name) { return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1); } + public static Object getFieldValue(Object instance, String fieldName) { + Field field = org.springframework.util.ReflectionUtils.findField(instance.getClass(), fieldName); + if (field == null) { + return null; + } + + field.setAccessible(true); + try { + return field.get(instance); + } + catch (IllegalAccessException e) { + // ignore + } + finally { + field.setAccessible(false); + } + 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); + } + } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java index b7edce5a4..eea811de7 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java @@ -137,12 +137,16 @@ public static boolean isCallerIPLabel(String expression) { } 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, "="); @@ -150,7 +154,7 @@ public static String getQueryValue(String queryString, String queryKey) { return queryKV[1]; } } - return StringUtils.EMPTY; + return defaultValue; } public static String getFirstValue(Map> valueMaps, String key) { diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java index a054c8f65..45ad670b0 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java @@ -83,14 +83,18 @@ else if (ExpressionLabelUtils.isUriLabel(labelKey)) { } 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; } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java index b8b1a59ad..476aeeecf 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java @@ -129,29 +129,41 @@ else if (ExpressionLabelUtils.isUriLabel(labelKey)) { } 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 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(); } diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/MetadataContextHolderTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/MetadataContextHolderTest.java index 21e62f3ed..b21344fa4 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/MetadataContextHolderTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/MetadataContextHolderTest.java @@ -36,7 +36,8 @@ @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 void test1() { 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"); diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index 968400e35..404f054d9 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -196,6 +196,12 @@ ${revision} + + com.tencent.cloud + spring-cloud-starter-tencent-threadlocal-plugin + ${revision} + + com.google.guava diff --git a/spring-cloud-tencent-plugin-starters/pom.xml b/spring-cloud-tencent-plugin-starters/pom.xml index 0258ceeea..5debf6360 100644 --- a/spring-cloud-tencent-plugin-starters/pom.xml +++ b/spring-cloud-tencent-plugin-starters/pom.xml @@ -19,6 +19,7 @@ spring-cloud-tencent-gateway-plugin spring-cloud-starter-tencent-discovery-adapter-plugin spring-cloud-tencent-lossless-plugin + spring-cloud-starter-tencent-threadlocal-plugin diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-threadlocal-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-threadlocal-plugin/pom.xml new file mode 100644 index 000000000..91975c141 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-threadlocal-plugin/pom.xml @@ -0,0 +1,35 @@ + + + + spring-cloud-tencent-plugin-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-starter-tencent-threadlocal-plugin + Spring Cloud Starter Tencent ThreadLocal plugin + + + + + com.tencent.polaris + polaris-threadlocal + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-threadlocal-plugin/src/main/java/com/tencent/cloud/plugin/threadlocal/TaskExecutorWrapper.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-threadlocal-plugin/src/main/java/com/tencent/cloud/plugin/threadlocal/TaskExecutorWrapper.java new file mode 100644 index 000000000..f2f9dc1c4 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-threadlocal-plugin/src/main/java/com/tencent/cloud/plugin/threadlocal/TaskExecutorWrapper.java @@ -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 implements TaskExecutor { + + private final TaskExecutor taskExecutor; + + private final Supplier contextGetter; + + private final Consumer contextSetter; + + public TaskExecutorWrapper(TaskExecutor taskExecutor, Supplier contextGetter, Consumer contextSetter) { + this.taskExecutor = taskExecutor; + this.contextGetter = contextGetter; + this.contextSetter = contextSetter; + } + + @Override + public void execute(Runnable command) { + taskExecutor.execute(new RunnableWrapper<>(command, contextGetter, contextSetter)); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-threadlocal-plugin/src/test/java/com/tencent/cloud/plugin/threadlocal/TaskExecutorWrapperTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-threadlocal-plugin/src/test/java/com/tencent/cloud/plugin/threadlocal/TaskExecutorWrapperTest.java new file mode 100644 index 000000000..15dd313c6 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-threadlocal-plugin/src/test/java/com/tencent/cloud/plugin/threadlocal/TaskExecutorWrapperTest.java @@ -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 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 result = new AtomicReference<>(false); + CountDownLatch latch = new CountDownLatch(1); + TaskExecutorWrapper 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()); + } + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClient.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClient.java index b7ee6d11b..0735a90df 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClient.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/EnhancedFeignClient.java @@ -21,7 +21,6 @@ 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.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 Response execute(Request request, Options options) throws IOException { .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); diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContext.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContext.java index 7eb14ae89..e9ef0cc67 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContext.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContext.java @@ -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; } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java index 6677dac0a..eff88f13a 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java @@ -44,5 +44,19 @@ public static class ClientPluginOrder { * {@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; } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateInterceptor.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateInterceptor.java index c50cbb907..6241ac598 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateInterceptor.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateInterceptor.java @@ -58,6 +58,7 @@ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttp .url(request.getURI()) .build(); enhancedPluginContext.setRequest(enhancedRequestContext); + enhancedPluginContext.setOriginRequest(request); enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance()); enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get() diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilter.java index a838b6466..7b65f2e81 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilter.java @@ -27,15 +27,15 @@ 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 EnhancedGatewayGlobalFilter(EnhancedPluginRunner pluginRunner) { } @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + public Mono 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 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() diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientExchangeFilterFunction.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientExchangeFilterFunction.java index 673cac3da..a215cfe8a 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientExchangeFilterFunction.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/webclient/EnhancedWebClientExchangeFilterFunction.java @@ -46,23 +46,25 @@ public EnhancedWebClientExchangeFilterFunction(EnhancedPluginRunner pluginRunner } @Override - public Mono filter(ClientRequest request, ExchangeFunction next) { + public Mono 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 -> { diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilterTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilterTest.java index d25ea12ec..397068162 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilterTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedGatewayGlobalFilterTest.java @@ -39,10 +39,9 @@ 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.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 void testRun() throws URISyntaxException { 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 serviceInstanceResponse = new Response() { - @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();