feat: support encoding and decoding TSF headers without TSF Consul

Allow TSF-Tags transfer on Polaris via spring.cloud.tencent.metadata.tsf-header-compatible so putTag still works when Consul is off.

Co-authored-by: Cursor <cursoragent@cursor.com>
pull/1816/head
evelynwei 1 week ago
parent 6712d772ef
commit 1764a95f2e

@ -15,3 +15,4 @@
- [feat: adapt to polaris-java ReportClientRequestCustomizer plugin for config watch reporting](https://github.com/Tencent/spring-cloud-tencent/pull/1813)
- [feat: support audit log](https://github.com/Tencent/spring-cloud-tencent/pull/1812)
- [feat: support config effective value ](https://github.com/Tencent/spring-cloud-tencent/pull/1815)
- [feat: support encoding and decoding TSF headers without TSF Consul](https://github.com/Tencent/spring-cloud-tencent/pull/NNNN)

@ -21,6 +21,7 @@ import java.util.List;
import com.tencent.cloud.common.async.PolarisAsyncProperties;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.metadata.core.DecodeTransferMetadataReactiveFilter;
import com.tencent.cloud.metadata.core.DecodeTransferMetadataServletFilter;
import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignEnhancedPlugin;
@ -69,8 +70,9 @@ public class MetadataTransferAutoConfiguration {
}
@Bean
public DecodeTransferMetadataServletFilter metadataServletFilter(PolarisAsyncProperties polarisAsyncProperties) {
return new DecodeTransferMetadataServletFilter(polarisAsyncProperties);
public DecodeTransferMetadataServletFilter metadataServletFilter(PolarisAsyncProperties polarisAsyncProperties,
@Autowired(required = false) MetadataLocalProperties metadataLocalProperties) {
return new DecodeTransferMetadataServletFilter(polarisAsyncProperties, metadataLocalProperties);
}
}
@ -82,8 +84,9 @@ public class MetadataTransferAutoConfiguration {
protected static class MetadataReactiveFilterConfig {
@Bean
public DecodeTransferMetadataReactiveFilter metadataReactiveFilter(PolarisAsyncProperties polarisAsyncProperties) {
return new DecodeTransferMetadataReactiveFilter(polarisAsyncProperties);
public DecodeTransferMetadataReactiveFilter metadataReactiveFilter(PolarisAsyncProperties polarisAsyncProperties,
@Autowired(required = false) MetadataLocalProperties metadataLocalProperties) {
return new DecodeTransferMetadataReactiveFilter(polarisAsyncProperties, metadataLocalProperties);
}
}
@ -97,8 +100,9 @@ public class MetadataTransferAutoConfiguration {
@Bean
public EncodeTransferMedataScgEnhancedPlugin encodeTransferMedataScgEnhancedPlugin(
@Autowired(required = false) List<ContextToHeaderInterceptor> contextToHeaderInterceptorList) {
return new EncodeTransferMedataScgEnhancedPlugin(contextToHeaderInterceptorList);
@Autowired(required = false) List<ContextToHeaderInterceptor> contextToHeaderInterceptorList,
@Autowired(required = false) MetadataLocalProperties metadataLocalProperties) {
return new EncodeTransferMedataScgEnhancedPlugin(contextToHeaderInterceptorList, metadataLocalProperties);
}
}
@ -112,8 +116,9 @@ public class MetadataTransferAutoConfiguration {
@Bean
public EncodeTransferMedataFeignEnhancedPlugin encodeTransferMedataFeignEnhancedPlugin(
@Autowired(required = false) List<ContextToHeaderInterceptor> contextToHeaderInterceptorList) {
return new EncodeTransferMedataFeignEnhancedPlugin(contextToHeaderInterceptorList);
@Autowired(required = false) List<ContextToHeaderInterceptor> contextToHeaderInterceptorList,
@Autowired(required = false) MetadataLocalProperties metadataLocalProperties) {
return new EncodeTransferMedataFeignEnhancedPlugin(contextToHeaderInterceptorList, metadataLocalProperties);
}
}
@ -127,8 +132,9 @@ public class MetadataTransferAutoConfiguration {
@Bean
public EncodeTransferMedataRestTemplateEnhancedPlugin encodeTransferMedataRestTemplateEnhancedPlugin(
@Autowired(required = false) List<ContextToHeaderInterceptor> contextToHeaderInterceptorList) {
return new EncodeTransferMedataRestTemplateEnhancedPlugin(contextToHeaderInterceptorList);
@Autowired(required = false) List<ContextToHeaderInterceptor> contextToHeaderInterceptorList,
@Autowired(required = false) MetadataLocalProperties metadataLocalProperties) {
return new EncodeTransferMedataRestTemplateEnhancedPlugin(contextToHeaderInterceptorList, metadataLocalProperties);
}
}
@ -142,8 +148,9 @@ public class MetadataTransferAutoConfiguration {
@Bean
public EncodeTransferMedataWebClientEnhancedPlugin encodeTransferMedataWebClientEnhancedPlugin(
@Autowired(required = false) List<ContextToHeaderInterceptor> contextToHeaderInterceptorList) {
return new EncodeTransferMedataWebClientEnhancedPlugin(contextToHeaderInterceptorList);
@Autowired(required = false) List<ContextToHeaderInterceptor> contextToHeaderInterceptorList,
@Autowired(required = false) MetadataLocalProperties metadataLocalProperties) {
return new EncodeTransferMedataWebClientEnhancedPlugin(contextToHeaderInterceptorList, metadataLocalProperties);
}
}
}

@ -26,6 +26,8 @@ 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.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.tsf.TsfContextUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.TsfTagUtils;
import com.tencent.cloud.common.util.UrlUtils;
@ -59,8 +61,12 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
private final PolarisAsyncProperties polarisAsyncProperties;
public DecodeTransferMetadataReactiveFilter(PolarisAsyncProperties polarisAsyncProperties) {
private final MetadataLocalProperties metadataLocalProperties;
public DecodeTransferMetadataReactiveFilter(PolarisAsyncProperties polarisAsyncProperties,
MetadataLocalProperties metadataLocalProperties) {
this.polarisAsyncProperties = polarisAsyncProperties;
this.metadataLocalProperties = metadataLocalProperties;
}
@Override
@ -84,7 +90,8 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
mergedApplicationMetadata, addHeaders, callerIp,
serverHttpRequest.getHeaders().getFirst(MetadataConstant.HeaderName.TSF_TAGS),
serverHttpRequest.getHeaders().getFirst(MetadataConstant.HeaderName.TSF_SYSTEM_TAG),
serverHttpRequest.getHeaders().getFirst(MetadataConstant.HeaderName.TSF_METADATA));
serverHttpRequest.getHeaders().getFirst(MetadataConstant.HeaderName.TSF_METADATA),
TsfContextUtils.isTsfHeaderCompatible(isTsfHeaderCompatible()));
// transitive metadata
// from specific header
@ -147,4 +154,8 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
return JacksonUtils.deserialize2Map(customMetadataStr);
}
private boolean isTsfHeaderCompatible() {
return metadataLocalProperties != null && metadataLocalProperties.isTsfHeaderCompatible();
}
}

@ -26,6 +26,8 @@ import com.tencent.cloud.common.async.PolarisAsyncProperties;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.tsf.TsfContextUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.TsfTagUtils;
import com.tencent.cloud.common.util.UrlUtils;
@ -60,8 +62,12 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
private final PolarisAsyncProperties polarisAsyncProperties;
public DecodeTransferMetadataServletFilter(PolarisAsyncProperties polarisAsyncProperties) {
private final MetadataLocalProperties metadataLocalProperties;
public DecodeTransferMetadataServletFilter(PolarisAsyncProperties polarisAsyncProperties,
MetadataLocalProperties metadataLocalProperties) {
this.polarisAsyncProperties = polarisAsyncProperties;
this.metadataLocalProperties = metadataLocalProperties;
}
@Override
@ -79,7 +85,8 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
mergedApplicationMetadata, addHeaders, callerIp,
httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_TAGS),
httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_SYSTEM_TAG),
httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_METADATA));
httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_METADATA),
TsfContextUtils.isTsfHeaderCompatible(isTsfHeaderCompatible()));
// transitive metadata
// from specific header
@ -129,4 +136,8 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
// create custom metadata.
return JacksonUtils.deserialize2Map(customMetadataStr);
}
private boolean isTsfHeaderCompatible() {
return metadataLocalProperties != null && metadataLocalProperties.isTsfHeaderCompatible();
}
}

@ -27,6 +27,7 @@ import java.util.Optional;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.tsf.TsfContextUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.ReflectionUtils;
@ -57,8 +58,16 @@ public class EncodeTransferMedataFeignEnhancedPlugin implements EnhancedPlugin {
private List<ContextToHeaderInterceptor> contextToHeaderInterceptorList;
private MetadataLocalProperties metadataLocalProperties;
public EncodeTransferMedataFeignEnhancedPlugin(List<ContextToHeaderInterceptor> contextToHeaderInterceptorList) {
this(contextToHeaderInterceptorList, null);
}
public EncodeTransferMedataFeignEnhancedPlugin(List<ContextToHeaderInterceptor> contextToHeaderInterceptorList,
MetadataLocalProperties metadataLocalProperties) {
this.contextToHeaderInterceptorList = Optional.ofNullable(contextToHeaderInterceptorList).orElse(Collections.EMPTY_LIST);
this.metadataLocalProperties = metadataLocalProperties;
}
@Override
@ -83,7 +92,7 @@ public class EncodeTransferMedataFeignEnhancedPlugin implements EnhancedPlugin {
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
if (TsfContextUtils.isTsfConsulEnabled()) {
if (TsfContextUtils.isTsfHeaderCompatible(isTsfHeaderCompatible())) {
Map<String, String> tsfMetadataMap = TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata);
this.buildHeaderMap(request, tsfMetadataMap);
}
@ -158,4 +167,8 @@ public class EncodeTransferMedataFeignEnhancedPlugin implements EnhancedPlugin {
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER;
}
private boolean isTsfHeaderCompatible() {
return metadataLocalProperties != null && metadataLocalProperties.isTsfHeaderCompatible();
}
}

@ -24,6 +24,7 @@ import java.util.Optional;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.tsf.TsfContextUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.TsfTagUtils;
@ -53,8 +54,16 @@ public class EncodeTransferMedataRestTemplateEnhancedPlugin implements EnhancedP
private List<ContextToHeaderInterceptor> contextToHeaderInterceptorList;
private MetadataLocalProperties metadataLocalProperties;
public EncodeTransferMedataRestTemplateEnhancedPlugin(List<ContextToHeaderInterceptor> contextToHeaderInterceptorList) {
this(contextToHeaderInterceptorList, null);
}
public EncodeTransferMedataRestTemplateEnhancedPlugin(List<ContextToHeaderInterceptor> contextToHeaderInterceptorList,
MetadataLocalProperties metadataLocalProperties) {
this.contextToHeaderInterceptorList = Optional.ofNullable(contextToHeaderInterceptorList).orElse(Collections.EMPTY_LIST);
this.metadataLocalProperties = metadataLocalProperties;
}
@Override
@ -81,7 +90,7 @@ public class EncodeTransferMedataRestTemplateEnhancedPlugin implements EnhancedP
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
if (TsfContextUtils.isTsfConsulEnabled()) {
if (TsfContextUtils.isTsfHeaderCompatible(isTsfHeaderCompatible())) {
Map<String, String> tsfMetadataMap = TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata);
this.buildHeaderMap(httpRequest, tsfMetadataMap);
}
@ -132,4 +141,8 @@ public class EncodeTransferMedataRestTemplateEnhancedPlugin implements EnhancedP
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER;
}
private boolean isTsfHeaderCompatible() {
return metadataLocalProperties != null && metadataLocalProperties.isTsfHeaderCompatible();
}
}

@ -25,6 +25,7 @@ import java.util.Optional;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.tsf.TsfContextUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.TsfTagUtils;
@ -55,8 +56,16 @@ public class EncodeTransferMedataScgEnhancedPlugin implements EnhancedPlugin {
private List<ContextToHeaderInterceptor> contextToHeaderInterceptorList;
private MetadataLocalProperties metadataLocalProperties;
public EncodeTransferMedataScgEnhancedPlugin(List<ContextToHeaderInterceptor> contextToHeaderInterceptorList) {
this(contextToHeaderInterceptorList, null);
}
public EncodeTransferMedataScgEnhancedPlugin(List<ContextToHeaderInterceptor> contextToHeaderInterceptorList,
MetadataLocalProperties metadataLocalProperties) {
this.contextToHeaderInterceptorList = Optional.ofNullable(contextToHeaderInterceptorList).orElse(Collections.EMPTY_LIST);
this.metadataLocalProperties = metadataLocalProperties;
}
@Override
@ -88,7 +97,7 @@ public class EncodeTransferMedataScgEnhancedPlugin implements EnhancedPlugin {
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
if (TsfContextUtils.isTsfConsulEnabled()) {
if (TsfContextUtils.isTsfHeaderCompatible(isTsfHeaderCompatible())) {
this.buildHeaderMap(builder, TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata));
}
// currently only support transitive header from calleeMessageMetadataContainer
@ -126,4 +135,8 @@ public class EncodeTransferMedataScgEnhancedPlugin implements EnhancedPlugin {
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER;
}
private boolean isTsfHeaderCompatible() {
return metadataLocalProperties != null && metadataLocalProperties.isTsfHeaderCompatible();
}
}

@ -24,6 +24,7 @@ import java.util.Optional;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.tsf.TsfContextUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.TsfTagUtils;
@ -53,8 +54,16 @@ public class EncodeTransferMedataWebClientEnhancedPlugin implements EnhancedPlug
private List<ContextToHeaderInterceptor> contextToHeaderInterceptorList;
private MetadataLocalProperties metadataLocalProperties;
public EncodeTransferMedataWebClientEnhancedPlugin(List<ContextToHeaderInterceptor> contextToHeaderInterceptorList) {
this(contextToHeaderInterceptorList, null);
}
public EncodeTransferMedataWebClientEnhancedPlugin(List<ContextToHeaderInterceptor> contextToHeaderInterceptorList,
MetadataLocalProperties metadataLocalProperties) {
this.contextToHeaderInterceptorList = Optional.ofNullable(contextToHeaderInterceptorList).orElse(Collections.EMPTY_LIST);
this.metadataLocalProperties = metadataLocalProperties;
}
@Override
@ -80,7 +89,7 @@ public class EncodeTransferMedataWebClientEnhancedPlugin implements EnhancedPlug
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
ClientRequest.Builder requestBuilder = ClientRequest.from(clientRequest);
if (TsfContextUtils.isTsfConsulEnabled()) {
if (TsfContextUtils.isTsfHeaderCompatible(isTsfHeaderCompatible())) {
this.buildHeaderMap(requestBuilder, TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata));
}
// currently only support transitive header from calleeMessageMetadataContainer
@ -124,4 +133,8 @@ public class EncodeTransferMedataWebClientEnhancedPlugin implements EnhancedPlug
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER;
}
private boolean isTsfHeaderCompatible() {
return metadataLocalProperties != null && metadataLocalProperties.isTsfHeaderCompatible();
}
}

@ -58,7 +58,7 @@ public class DecodeTransferMetadataReactiveFilterTest {
@BeforeEach
public void setUp() {
this.metadataReactiveFilter = new DecodeTransferMetadataReactiveFilter(polarisAsyncProperties);
this.metadataReactiveFilter = new DecodeTransferMetadataReactiveFilter(polarisAsyncProperties, metadataLocalProperties);
}
@Test

@ -0,0 +1,75 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. 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.IOException;
import java.util.concurrent.atomic.AtomicReference;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.UrlUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* Test TSF header decode when Consul is disabled and tsf-header-compatible is true.
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
classes = DecodeTransferTsfHeaderCompatibleTest.TestApplication.class,
properties = {"spring.config.location = classpath:application-test.yml",
"spring.main.web-application-type = servlet",
"spring.cloud.gateway.enabled = false",
"spring.cloud.tencent.metadata.tsf-header-compatible = true"})
public class DecodeTransferTsfHeaderCompatibleTest {
@Autowired
private DecodeTransferMetadataServletFilter metadataServletFilter;
@Test
public void testDecodeTsfTagsWithoutConsul() throws ServletException, IOException {
AtomicReference<String> featValue = new AtomicReference<>();
FilterChain filterChain = (servletRequest, servletResponse) ->
featValue.set(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "feat"));
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader(MetadataConstant.HeaderName.TSF_TAGS,
UrlUtils.encode("[{\"k\":\"feat\",\"v\":\"test\",\"f\":[\"0\"]}]"));
metadataServletFilter.doFilter(request, new MockHttpServletResponse(), filterChain);
assertThat(featValue.get()).isEqualTo("test");
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -0,0 +1,115 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. 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.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.tsf.TsfContextUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import feign.Request;
import feign.RequestTemplate;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test TSF header encode when Consul is disabled.
*/
public class EncodeTransferTsfHeaderCompatibleTest {
@BeforeEach
@AfterEach
public void reset() throws Exception {
MetadataContextHolder.remove();
Field isTsfConsulEnabledFirst = TsfContextUtils.class.getDeclaredField("isTsfConsulEnabledFirstConfiguration");
isTsfConsulEnabledFirst.setAccessible(true);
((AtomicBoolean) isTsfConsulEnabledFirst.get(null)).set(true);
Field tsfConsulEnabledField = TsfContextUtils.class.getDeclaredField("tsfConsulEnabled");
tsfConsulEnabledField.setAccessible(true);
tsfConsulEnabledField.set(null, false);
}
@Test
public void testEncodeTsfHeaderWhenCompatibleWithoutConsul() throws Throwable {
MetadataContextHolder.get().setTransitiveMetadata(Collections.singletonMap("feat", "test"));
MetadataLocalProperties properties = new MetadataLocalProperties();
properties.setTsfHeaderCompatible(true);
EncodeTransferMedataFeignEnhancedPlugin plugin =
new EncodeTransferMedataFeignEnhancedPlugin(new ArrayList<>(), properties);
Request request = createRequest();
EnhancedPluginContext context = new EnhancedPluginContext();
context.setOriginRequest(request);
plugin.run(context);
assertThat(request.headers()).containsKey(MetadataConstant.HeaderName.TSF_TAGS);
}
@Test
public void testSkipTsfHeaderWhenIncompatibleWithoutConsul() throws Throwable {
MetadataContextHolder.get().setTransitiveMetadata(Collections.singletonMap("feat", "test"));
MetadataLocalProperties properties = new MetadataLocalProperties();
properties.setTsfHeaderCompatible(false);
EncodeTransferMedataFeignEnhancedPlugin plugin =
new EncodeTransferMedataFeignEnhancedPlugin(new ArrayList<>(), properties);
Request request = createRequest();
EnhancedPluginContext context = new EnhancedPluginContext();
context.setOriginRequest(request);
plugin.run(context);
assertThat(request.headers()).doesNotContainKey(MetadataConstant.HeaderName.TSF_TAGS);
assertThat(request.headers()).containsKey(MetadataConstant.HeaderName.CUSTOM_METADATA);
}
@Test
public void testEncodeTsfHeaderWhenConsulEnabled() throws Throwable {
Field tsfConsulEnabledField = TsfContextUtils.class.getDeclaredField("tsfConsulEnabled");
tsfConsulEnabledField.setAccessible(true);
tsfConsulEnabledField.set(null, true);
MetadataContextHolder.get().setTransitiveMetadata(Collections.singletonMap("feat", "test"));
EncodeTransferMedataFeignEnhancedPlugin plugin =
new EncodeTransferMedataFeignEnhancedPlugin(new ArrayList<>(), new MetadataLocalProperties());
Request request = createRequest();
EnhancedPluginContext context = new EnhancedPluginContext();
context.setOriginRequest(request);
plugin.run(context);
assertThat(request.headers()).containsKey(MetadataConstant.HeaderName.TSF_TAGS);
}
private Request createRequest() {
Map<String, Collection<String>> headers = Collections.emptyMap();
return Request.create(Request.HttpMethod.GET, "http://localhost/test",
headers, new byte[0], StandardCharsets.UTF_8, new RequestTemplate());
}
}

@ -53,6 +53,12 @@ public class MetadataLocalProperties {
*/
private List<String> headers;
/**
* Whether to encode/decode TSF-Tags headers when TSF Consul is not enabled.
* Default false.
*/
private boolean tsfHeaderCompatible;
public Map<String, String> getContent() {
if (CollectionUtils.isEmpty(content)) {
content = new HashMap<>();
@ -97,6 +103,14 @@ public class MetadataLocalProperties {
this.headers = headers;
}
public boolean isTsfHeaderCompatible() {
return tsfHeaderCompatible;
}
public void setTsfHeaderCompatible(boolean tsfHeaderCompatible) {
this.tsfHeaderCompatible = tsfHeaderCompatible;
}
@Override
public String toString() {
return "MetadataLocalProperties{" +
@ -104,6 +118,7 @@ public class MetadataLocalProperties {
", transitive=" + transitive +
", disposable=" + disposable +
", headers=" + headers +
", tsfHeaderCompatible=" + tsfHeaderCompatible +
'}';
}
}

@ -90,8 +90,18 @@ public final class TsfContextUtils {
* This method should be called after {@link com.tencent.cloud.common.tsf.TsfContextUtils#isTsfConsulEnabled(Environment)}.
* @return whether Tsf Consul is enabled
*/
public static boolean isTsfConsulEnabled() {
return tsfConsulEnabled;
}
/**
* Whether TSF HTTP headers should be encoded or decoded.
* <p>True when TSF Consul is enabled, or when
* {@code spring.cloud.tencent.metadata.tsf-header-compatible} is true.
* @param configuredTsfHeaderCompatible configured tsf-header-compatible value
* @return whether TSF headers should be processed
*/
public static boolean isTsfHeaderCompatible(boolean configuredTsfHeaderCompatible) {
return configuredTsfHeaderCompatible || isTsfConsulEnabled();
}
}

@ -167,9 +167,10 @@ public final class TsfTagUtils {
public static void updateTsfMetadata(Map<String, String> mergedTransitiveMetadata,
Map<String, String> mergedDisposableMetadata, Map<String, String> mergedApplicationMetadata, Map<String, String> addHeaders,
AtomicReference<String> callerIp, String encodedUserTagList, String encodedSystemTagList, String encodedMetadata) {
AtomicReference<String> callerIp, String encodedUserTagList, String encodedSystemTagList, String encodedMetadata,
boolean tsfHeaderCompatible) {
if (!TsfContextUtils.isTsfConsulEnabled()) {
if (!TsfContextUtils.isTsfHeaderCompatible(tsfHeaderCompatible)) {
return;
}
List<Tag> tsfUserTagList = TsfTagUtils.deserializeTagList(encodedUserTagList);

@ -20,6 +20,12 @@
"type": "java.util.List",
"description": "Custom transitive http header key list."
},
{
"name": "spring.cloud.tencent.metadata.tsf-header-compatible",
"type": "java.lang.Boolean",
"defaultValue": false,
"description": "Encode and decode TSF-Tags headers when TSF Consul is not enabled. Default: false."
},
{
"name": "spring.cloud.tencent.async.enabled",
"type": "java.lang.Boolean",

@ -59,6 +59,11 @@ public class MetadataLocalPropertiesTest {
assertThat(metadataLocalProperties.getHeaders().contains("d")).isTrue();
}
@Test
public void testTsfHeaderCompatibleDefault() {
assertThat(metadataLocalProperties.isTsfHeaderCompatible()).isFalse();
}
@SpringBootApplication
protected static class TestApplication {

@ -0,0 +1,52 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.common.metadata.config;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test binding of tsf-header-compatible.
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = MetadataLocalPropertiesTsfHeaderCompatibleTest.TestApplication.class,
properties = {"spring.config.location = classpath:application-test.yml",
"spring.cloud.tencent.metadata.tsf-header-compatible=true"})
public class MetadataLocalPropertiesTsfHeaderCompatibleTest {
@Autowired
private MetadataLocalProperties metadataLocalProperties;
@Test
public void testTsfHeaderCompatibleBinding() {
assertThat(metadataLocalProperties.isTsfHeaderCompatible()).isTrue();
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -0,0 +1,65 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.common.tsf;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link TsfContextUtils}.
*/
public class TsfContextUtilsTest {
@BeforeEach
@AfterEach
public void reset() throws Exception {
Field isTsfConsulEnabledFirst = TsfContextUtils.class.getDeclaredField("isTsfConsulEnabledFirstConfiguration");
isTsfConsulEnabledFirst.setAccessible(true);
((AtomicBoolean) isTsfConsulEnabledFirst.get(null)).set(true);
Field tsfConsulEnabledField = TsfContextUtils.class.getDeclaredField("tsfConsulEnabled");
tsfConsulEnabledField.setAccessible(true);
tsfConsulEnabledField.set(null, false);
}
@Test
public void testIsTsfHeaderCompatibleWhenConfigured() {
assertThat(TsfContextUtils.isTsfHeaderCompatible(true)).isTrue();
assertThat(TsfContextUtils.isTsfConsulEnabled()).isFalse();
}
@Test
public void testIsTsfHeaderCompatibleWhenConsulEnabled() throws Exception {
Field tsfConsulEnabledField = TsfContextUtils.class.getDeclaredField("tsfConsulEnabled");
tsfConsulEnabledField.setAccessible(true);
tsfConsulEnabledField.set(null, true);
assertThat(TsfContextUtils.isTsfHeaderCompatible(false)).isTrue();
}
@Test
public void testIsTsfHeaderCompatibleWhenDisabled() {
assertThat(TsfContextUtils.isTsfHeaderCompatible(false)).isFalse();
}
}

@ -17,7 +17,10 @@
package com.tencent.cloud.common.util;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
@ -79,4 +82,52 @@ public class TsfTagUtilsTest {
assertThat(TsfTagUtils.deserializeMetadata(null)).isNull();
assertThat(TsfTagUtils.deserializeMetadata("")).isNull();
}
@Test
public void testUpdateTsfMetadataWhenCompatible() {
Map<String, String> transitive = new HashMap<>();
Map<String, String> disposable = new HashMap<>();
Map<String, String> application = new HashMap<>();
Map<String, String> addHeaders = new HashMap<>();
AtomicReference<String> callerIp = new AtomicReference<>("");
String encodedTags = UrlUtils.encode("[{\"k\":\"feat\",\"v\":\"test\",\"f\":[\"0\"]}]");
TsfTagUtils.updateTsfMetadata(transitive, disposable, application, addHeaders, callerIp,
encodedTags, null, null, true);
assertThat(transitive).containsEntry("feat", "test");
assertThat(disposable).isEmpty();
}
@Test
public void testUpdateTsfMetadataWhenIncompatible() {
Map<String, String> transitive = new HashMap<>();
Map<String, String> disposable = new HashMap<>();
Map<String, String> application = new HashMap<>();
Map<String, String> addHeaders = new HashMap<>();
AtomicReference<String> callerIp = new AtomicReference<>("");
String encodedTags = UrlUtils.encode("[{\"k\":\"feat\",\"v\":\"test\",\"f\":[\"0\"]}]");
TsfTagUtils.updateTsfMetadata(transitive, disposable, application, addHeaders, callerIp,
encodedTags, null, null, false);
assertThat(transitive).isEmpty();
assertThat(disposable).isEmpty();
}
@Test
public void testUpdateTsfMetadataDisposableWhenCompatible() {
Map<String, String> transitive = new HashMap<>();
Map<String, String> disposable = new HashMap<>();
Map<String, String> application = new HashMap<>();
Map<String, String> addHeaders = new HashMap<>();
AtomicReference<String> callerIp = new AtomicReference<>("");
String encodedTags = UrlUtils.encode("[{\"k\":\"operation\",\"v\":\"rest\",\"f\":[]}]");
TsfTagUtils.updateTsfMetadata(transitive, disposable, application, addHeaders, callerIp,
encodedTags, null, null, true);
assertThat(transitive).isEmpty();
assertThat(disposable).containsEntry("operation", "rest");
}
}

@ -0,0 +1,116 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. 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.tsf.demo.consumer.controller;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.tsf.demo.consumer.proxy.ProviderDemoService;
import com.tencent.polaris.api.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.tsf.core.TsfContext;
import org.springframework.tsf.core.entity.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* Put TSF tags then dump local context or call provider /metadata.
*/
@RestController
public class MetadataInspectController {
private static final Logger LOG = LoggerFactory.getLogger(MetadataInspectController.class);
@Autowired
private RestTemplate restTemplate;
@Autowired
private ProviderDemoService providerDemoService;
@GetMapping("/local-metadata")
public Map<String, Object> localMetadata(
@RequestParam(required = false) String hopKey,
@RequestParam(required = false) String hopValue,
@RequestParam(required = false) String passKey,
@RequestParam(required = false) String passValue) {
LOG.info("GET /local-metadata hop={}:{} pass={}:{}", hopKey, hopValue, passKey, passValue);
putTags(hopKey, hopValue, passKey, passValue);
Map<String, Object> body = dumpLocal();
LOG.info("GET /local-metadata dump after putTag (no outbound): {}", body);
return body;
}
@GetMapping("/echo-metadata-rest")
@SuppressWarnings("unchecked")
public Map<String, Object> echoMetadataRest(
@RequestParam(required = false) String hopKey,
@RequestParam(required = false) String hopValue,
@RequestParam(required = false) String passKey,
@RequestParam(required = false) String passValue) {
LOG.info("GET /echo-metadata-rest hop={}:{} pass={}:{}, RestTemplate -> provider-demo /metadata",
hopKey, hopValue, passKey, passValue);
putTags(hopKey, hopValue, passKey, passValue);
Map<String, Object> body = restTemplate.getForObject("http://provider-demo/metadata", Map.class);
LOG.info("GET /echo-metadata-rest provider /metadata response: {}", body);
return body;
}
@GetMapping("/echo-metadata-feign")
@SuppressWarnings("unchecked")
public Map<String, Object> echoMetadataFeign(
@RequestParam(required = false) String hopKey,
@RequestParam(required = false) String hopValue,
@RequestParam(required = false) String passKey,
@RequestParam(required = false) String passValue) {
LOG.info("GET /echo-metadata-feign hop={}:{} pass={}:{}, "
+ "Feign ProviderDemoService#metadata() -> provider-demo /metadata",
hopKey, hopValue, passKey, passValue);
putTags(hopKey, hopValue, passKey, passValue);
Map<String, Object> body = providerDemoService.metadata();
LOG.info("GET /echo-metadata-feign provider /metadata response: {}", body);
return body;
}
private void putTags(String hopKey, String hopValue, String passKey, String passValue) {
if (StringUtils.isNotBlank(hopKey)) {
TsfContext.putTag(hopKey, hopValue);
LOG.info("TsfContext.putTag one-hop (no flag) {}:{}", hopKey, hopValue);
}
if (StringUtils.isNotBlank(passKey)) {
TsfContext.putTags(Collections.singletonMap(passKey, passValue), Tag.ControlFlag.TRANSITIVE);
LOG.info("TsfContext.putTags TRANSITIVE {}:{}", passKey, passValue);
}
}
private Map<String, Object> dumpLocal() {
MetadataContext ctx = MetadataContextHolder.get();
Map<String, Object> body = new LinkedHashMap<>();
body.put("calleeTransitive", ctx.getTransitiveMetadata());
body.put("calleeDisposable", ctx.getDisposableMetadata());
body.put("callerDisposable", ctx.getFragmentContext(MetadataContext.FRAGMENT_UPSTREAM_DISPOSABLE));
return body;
}
}

@ -17,6 +17,8 @@
package com.tencent.cloud.tsf.demo.consumer.proxy;
import java.util.Map;
import com.tencent.cloud.tsf.demo.consumer.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
@ -39,4 +41,7 @@ public interface ProviderDemoService {
@RequestMapping(value = "/user", method = RequestMethod.POST)
String user(@RequestBody User user);
@RequestMapping(value = "/metadata", method = RequestMethod.GET)
Map<String, Object> metadata();
}

@ -8,6 +8,13 @@ feign:
tsf:
enabled: true
# 连接北极星且需要编解码 TSF-Tags 时打开(默认 false有 tsf_consul_ip 时无需打开)
#spring:
# cloud:
# tencent:
# metadata:
# tsf-header-compatible: true
#本地测试时打开
#tsf_namespace_id: default_namespace

@ -18,6 +18,10 @@ spring:
consul:
enabled: true
scheme: HTTP
tencent:
metadata:
# 连接北极星且需要编解码 TSF-Tags 时设为 true默认 false有 tsf_consul_ip 时无需打开)
# tsf-header-compatible: true
logging:
level:

@ -0,0 +1,71 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. 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.tsf.demo.provider;
import java.util.LinkedHashMap;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Dump incoming TSF/SCT headers and MetadataContext buckets for tag verification.
*/
@RestController
public class MetadataInspectController {
private static final Logger LOG = LoggerFactory.getLogger(MetadataInspectController.class);
@GetMapping("/metadata")
public Map<String, Object> metadata(HttpServletRequest request) {
MetadataContext ctx = MetadataContextHolder.get();
Map<String, Object> body = new LinkedHashMap<>();
Map<String, String> headers = new LinkedHashMap<>();
headers.put(MetadataConstant.HeaderName.TSF_TAGS,
request.getHeader(MetadataConstant.HeaderName.TSF_TAGS));
headers.put(MetadataConstant.HeaderName.TSF_SYSTEM_TAG,
request.getHeader(MetadataConstant.HeaderName.TSF_SYSTEM_TAG));
headers.put(MetadataConstant.HeaderName.TSF_METADATA,
request.getHeader(MetadataConstant.HeaderName.TSF_METADATA));
headers.put(MetadataConstant.HeaderName.CUSTOM_METADATA,
request.getHeader(MetadataConstant.HeaderName.CUSTOM_METADATA));
headers.put(MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA,
request.getHeader(MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA));
body.put("headers", headers);
body.put("calleeTransitive", ctx.getTransitiveMetadata());
body.put("calleeDisposable", ctx.getDisposableMetadata());
body.put("callerDisposable", ctx.getFragmentContext(MetadataContext.FRAGMENT_UPSTREAM_DISPOSABLE));
LOG.info("GET /metadata incoming headers TSF-Tags={} TSF-System-Tags={} TSF-Metadata={} "
+ "SCT-CUSTOM-METADATA={} SCT-CUSTOM-DISPOSABLE-METADATA={}",
headers.get(MetadataConstant.HeaderName.TSF_TAGS),
headers.get(MetadataConstant.HeaderName.TSF_SYSTEM_TAG),
headers.get(MetadataConstant.HeaderName.TSF_METADATA),
headers.get(MetadataConstant.HeaderName.CUSTOM_METADATA),
headers.get(MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA));
LOG.info("GET /metadata MetadataContext calleeTransitive={} calleeDisposable={} callerDisposable={}",
body.get("calleeTransitive"), body.get("calleeDisposable"), body.get("callerDisposable"));
return body;
}
}

@ -17,6 +17,9 @@ spring:
rpc-enhancement:
reporter:
enabled: true
metadata:
# 连接北极星且需要编解码 TSF-Tags 时设为 true默认 false有 tsf_consul_ip 时无需打开)
# tsf-header-compatible: true
logging:
file:

Loading…
Cancel
Save