feat: support tsf gw. (#1697)

Co-authored-by: shedfreewu <49236872+shedfreewu@users.noreply.github.com>
pull/1698/head
Haotian Zhang 3 weeks ago committed by GitHub
parent 2e5bb9f65a
commit da9b66f8d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -5,3 +5,4 @@
- [fix: fix ConfigChangeListener and unit test](https://github.com/Tencent/spring-cloud-tencent/pull/1654)
- [feat: support spring-retry and feign config refresh and feign eager load support schema](https://github.com/Tencent/spring-cloud-tencent/pull/1649)
- [fix: fix ConfigChangeListener ut bug](https://github.com/Tencent/spring-cloud-tencent/pull/1659)
- [feat: support tsf gw.](https://github.com/Tencent/spring-cloud-tencent/pull/1696)

@ -17,20 +17,32 @@
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.EncodeTransferMedataFeignEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMedataScgEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientEnhancedPlugin;
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.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer;
import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import static javax.servlet.DispatcherType.ASYNC;
import static javax.servlet.DispatcherType.ERROR;
@ -118,9 +130,59 @@ public class MetadataTransferAutoConfiguration {
@ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true)
protected static class MetadataTransferRestTemplateConfig {
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor() {
return new EncodeTransferMedataRestTemplateInterceptor();
}
@Bean
public SmartInitializingSingleton addEncodeTransferMetadataInterceptorForRestTemplate(EncodeTransferMedataRestTemplateInterceptor interceptor) {
return () -> restTemplates.forEach(restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
list.add(interceptor);
restTemplate.setInterceptors(list);
});
}
@Bean
public EncodeTransferMedataRestTemplateEnhancedPlugin encodeTransferMedataRestTemplateEnhancedPlugin() {
return new EncodeTransferMedataRestTemplateEnhancedPlugin();
public RestTemplateCustomizer polarisRestTemplateCustomizer(
@Autowired(required = false) RetryLoadBalancerInterceptor retryLoadBalancerInterceptor,
@Autowired(required = false) LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
// LoadBalancerInterceptor must invoke before EncodeTransferMedataRestTemplateInterceptor
int addIndex = list.size();
if (CollectionUtils.containsInstance(list, retryLoadBalancerInterceptor) || CollectionUtils.containsInstance(list, loadBalancerInterceptor)) {
ClientHttpRequestInterceptor enhancedRestTemplateInterceptor = null;
for (int i = 0; i < list.size(); i++) {
if (list.get(i) instanceof EncodeTransferMedataRestTemplateInterceptor) {
enhancedRestTemplateInterceptor = list.get(i);
addIndex = i;
}
}
if (enhancedRestTemplateInterceptor != null) {
list.remove(addIndex);
list.add(enhancedRestTemplateInterceptor);
}
}
else {
if (retryLoadBalancerInterceptor != null || loadBalancerInterceptor != null) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i) instanceof EncodeTransferMedataRestTemplateInterceptor) {
addIndex = i;
}
}
list.add(addIndex,
retryLoadBalancerInterceptor != null
? retryLoadBalancerInterceptor
: loadBalancerInterceptor);
}
}
restTemplate.setInterceptors(list);
};
}
}

@ -19,12 +19,14 @@ package com.tencent.cloud.metadata.core;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
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 com.tencent.cloud.common.util.TsfTagUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.metadata.provider.ReactiveMetadataProvider;
import com.tencent.polaris.api.utils.StringUtils;
@ -64,30 +66,47 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
// Get metadata string from http header.
ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest();
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
Map<String, String> mergedDisposableMetadata = new HashMap<>();
Map<String, String> mergedApplicationMetadata = new HashMap<>();
// some tsf headers need to change to polaris header
Map<String, String> addHeaders = new HashMap<>();
AtomicReference<String> callerIp = new AtomicReference<>("");
TsfTagUtils.updateTsfMetadata(mergedTransitiveMetadata, mergedDisposableMetadata,
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));
// transitive metadata
// from specific header
Map<String, String> internalTransitiveMetadata = getInternalMetadata(serverHttpRequest, CUSTOM_METADATA);
// from header with specific prefix
Map<String, String> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(serverWebExchange);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
// disposable metadata
// from specific header
Map<String, String> internalDisposableMetadata = getInternalMetadata(serverHttpRequest, CUSTOM_DISPOSABLE_METADATA);
Map<String, String> mergedDisposableMetadata = new HashMap<>(internalDisposableMetadata);
mergedDisposableMetadata.putAll(internalDisposableMetadata);
// application metadata
Map<String, String> internalApplicationMetadata = getInternalMetadata(serverHttpRequest, APPLICATION_METADATA);
Map<String, String> mergedApplicationMetadata = new HashMap<>(internalApplicationMetadata);
mergedApplicationMetadata.putAll(internalApplicationMetadata);
String callerIp = "";
if (StringUtils.isNotBlank(mergedApplicationMetadata.get(LOCAL_IP))) {
callerIp = mergedApplicationMetadata.get(LOCAL_IP);
callerIp.set(mergedApplicationMetadata.get(LOCAL_IP));
}
// add headers
serverHttpRequest = serverHttpRequest.mutate().headers(httpHeaders -> {
for (Map.Entry<String, String> entry : addHeaders.entrySet()) {
httpHeaders.add(entry.getKey(), entry.getValue());
}
}).build();
// message metadata
ReactiveMetadataProvider callerMessageMetadataProvider = new ReactiveMetadataProvider(serverHttpRequest, callerIp);
ReactiveMetadataProvider callerMessageMetadataProvider = new ReactiveMetadataProvider(serverHttpRequest, callerIp.get());
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, mergedApplicationMetadata, callerMessageMetadataProvider);
@ -97,6 +116,11 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
MetadataContextHolder.get());
String targetNamespace = serverWebExchange.getRequest().getHeaders().getFirst(MetadataConstant.HeaderName.NAMESPACE);
// Compatible with TSF
if (StringUtils.isBlank(targetNamespace)) {
targetNamespace = serverWebExchange.getRequest().getHeaders().getFirst(MetadataConstant.HeaderName.TSF_NAMESPACE_ID);
}
if (StringUtils.isNotBlank(targetNamespace)) {
MetadataContextHolder.get().putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
MetadataConstant.POLARIS_TARGET_NAMESPACE, targetNamespace);

@ -20,15 +20,18 @@ package com.tencent.cloud.metadata.core;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.TsfTagUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.metadata.provider.ServletMetadataProvider;
import com.tencent.polaris.api.utils.StringUtils;
@ -59,30 +62,45 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest,
@NonNull HttpServletResponse httpServletResponse, FilterChain filterChain)
throws ServletException, IOException {
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
Map<String, String> mergedDisposableMetadata = new HashMap<>();
Map<String, String> mergedApplicationMetadata = new HashMap<>();
// some tsf headers need to change to polaris header
Map<String, String> addHeaders = new HashMap<>();
AtomicReference<String> callerIp = new AtomicReference<>("");
TsfTagUtils.updateTsfMetadata(mergedTransitiveMetadata, mergedDisposableMetadata,
mergedApplicationMetadata, addHeaders, callerIp,
httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_TAGS),
httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_SYSTEM_TAG),
httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_METADATA));
// transitive metadata
// from specific header
Map<String, String> internalTransitiveMetadata = getInternalMetadata(httpServletRequest, CUSTOM_METADATA);
// from header with specific prefix
Map<String, String> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(httpServletRequest);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
// disposable metadata
// from specific header
Map<String, String> internalDisposableMetadata = getInternalMetadata(httpServletRequest, CUSTOM_DISPOSABLE_METADATA);
Map<String, String> mergedDisposableMetadata = new HashMap<>(internalDisposableMetadata);
mergedDisposableMetadata.putAll(internalDisposableMetadata);
// application metadata
Map<String, String> internalApplicationMetadata = getInternalMetadata(httpServletRequest, APPLICATION_METADATA);
Map<String, String> mergedApplicationMetadata = new HashMap<>(internalApplicationMetadata);
mergedApplicationMetadata.putAll(internalApplicationMetadata);
String callerIp = "";
if (StringUtils.isNotBlank(mergedApplicationMetadata.get(LOCAL_IP))) {
callerIp = mergedApplicationMetadata.get(LOCAL_IP);
callerIp.set(mergedApplicationMetadata.get(LOCAL_IP));
}
// add headers
httpServletRequest = new HttpServletRequestHeaderWrapper(httpServletRequest, addHeaders);
// message metadata
ServletMetadataProvider callerMessageMetadataProvider = new ServletMetadataProvider(httpServletRequest, callerIp);
ServletMetadataProvider callerMessageMetadataProvider = new ServletMetadataProvider(httpServletRequest, callerIp.get());
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, mergedApplicationMetadata, callerMessageMetadataProvider);

@ -24,8 +24,10 @@ import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.tsf.TsfContextUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.ReflectionUtils;
import com.tencent.cloud.common.util.TsfTagUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
@ -69,18 +71,23 @@ public class EncodeTransferMedataFeignEnhancedPlugin implements EnhancedPlugin {
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(request, calleeTransitiveHeaders);
// build custom disposable metadata request header
this.buildMetadataHeader(request, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
if (TsfContextUtils.isOnlyTsfConsulEnabled()) {
Map<String, String> tsfMetadataMap = TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata);
this.buildHeaderMap(request, tsfMetadataMap);
}
else {
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(request, calleeTransitiveHeaders);
// process custom metadata
this.buildMetadataHeader(request, customMetadata, CUSTOM_METADATA);
// build custom disposable metadata request header
this.buildMetadataHeader(request, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
// add application metadata
this.buildMetadataHeader(request, applicationMetadata, APPLICATION_METADATA);
// process custom metadata
this.buildMetadataHeader(request, customMetadata, CUSTOM_METADATA);
// add application metadata
this.buildMetadataHeader(request, applicationMetadata, APPLICATION_METADATA);
}
// set headers that need to be transmitted from the upstream
this.buildTransmittedHeader(request, transHeaders);
}

@ -17,21 +17,27 @@
package com.tencent.cloud.metadata.core;
import java.io.IOException;
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.tsf.TsfContextUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.TsfTagUtils;
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 shade.polaris.com.google.common.collect.ImmutableMap;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
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.MetadataConstant.HeaderName.APPLICATION_METADATA;
@ -39,45 +45,54 @@ import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUST
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
/**
* Pre EnhancedPlugin for rest template to encode transfer metadata.
* Interceptor used for adding the metadata in http headers from context when web client
* is RestTemplate.
*
* @author Shedfree Wu
* It needs to execute after {@link LoadBalancerInterceptor}, because LaneRouter may add calleeTransitiveHeaders.
*
* @author Haotian Zhang
*/
public class EncodeTransferMedataRestTemplateEnhancedPlugin implements EnhancedPlugin {
public class EncodeTransferMedataRestTemplateInterceptor implements ClientHttpRequestInterceptor, Ordered {
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.PRE;
public int getOrder() {
return OrderConstant.Client.RestTemplate.ENCODE_TRANSFER_METADATA_INTERCEPTOR_ORDER;
}
@Override
public void run(EnhancedPluginContext context) throws Throwable {
if (!(context.getOriginRequest() instanceof HttpRequest)) {
return;
}
HttpRequest httpRequest = (HttpRequest) context.getOriginRequest();
public ClientHttpResponse intercept(@NonNull HttpRequest httpRequest, @NonNull byte[] bytes,
@NonNull ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
// get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
Map<String, String> applicationMetadata = metadataContext.getApplicationMetadata();
Map<String, String> transHeaders = metadataContext.getTransHeadersKV();
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(httpRequest, calleeTransitiveHeaders);
// build custom disposable metadata request header
this.buildMetadataHeader(httpRequest, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
if (TsfContextUtils.isOnlyTsfConsulEnabled()) {
Map<String, String> tsfMetadataMap = TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata);
this.buildHeaderMap(httpRequest, tsfMetadataMap);
}
else {
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(httpRequest, calleeTransitiveHeaders);
// build custom metadata request header
this.buildMetadataHeader(httpRequest, customMetadata, CUSTOM_METADATA);
// build custom disposable metadata request header
this.buildMetadataHeader(httpRequest, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
// build application metadata request header
this.buildMetadataHeader(httpRequest, applicationMetadata, APPLICATION_METADATA);
// build custom metadata request header
this.buildMetadataHeader(httpRequest, customMetadata, CUSTOM_METADATA);
// build application metadata request header
this.buildMetadataHeader(httpRequest, applicationMetadata, APPLICATION_METADATA);
}
// set headers that need to be transmitted from the upstream
this.buildTransmittedHeader(httpRequest, transHeaders);
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
private void buildTransmittedHeader(HttpRequest request, Map<String, String> transHeaders) {
@ -106,9 +121,4 @@ public class EncodeTransferMedataRestTemplateEnhancedPlugin implements EnhancedP
buildHeaderMap(request, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
}
}
@Override
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER;
}
}

@ -22,7 +22,9 @@ 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 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;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
@ -73,12 +75,17 @@ public class EncodeTransferMedataScgEnhancedPlugin implements EnhancedPlugin {
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(builder, calleeTransitiveHeaders);
if (TsfContextUtils.isOnlyTsfConsulEnabled()) {
this.buildHeaderMap(builder, TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata));
}
else {
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(builder, calleeTransitiveHeaders);
this.buildMetadataHeader(builder, customMetadata, CUSTOM_METADATA);
this.buildMetadataHeader(builder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
this.buildMetadataHeader(builder, applicationMetadata, APPLICATION_METADATA);
this.buildMetadataHeader(builder, customMetadata, CUSTOM_METADATA);
this.buildMetadataHeader(builder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
this.buildMetadataHeader(builder, applicationMetadata, APPLICATION_METADATA);
}
TransHeadersTransfer.transfer(exchange.getRequest());
context.setOriginRequest(exchange.mutate().request(builder.build()).build());

@ -21,7 +21,9 @@ import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
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;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
@ -65,15 +67,18 @@ public class EncodeTransferMedataWebClientEnhancedPlugin implements EnhancedPlug
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
ClientRequest.Builder requestBuilder = ClientRequest.from(clientRequest);
if (TsfContextUtils.isOnlyTsfConsulEnabled()) {
this.buildHeaderMap(requestBuilder, TsfTagUtils.getTsfMetadataMap(calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata));
}
else {
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(requestBuilder, calleeTransitiveHeaders);
// 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.buildMetadataHeader(requestBuilder, applicationMetadata, APPLICATION_METADATA);
this.buildMetadataHeader(requestBuilder, customMetadata, CUSTOM_METADATA);
this.buildMetadataHeader(requestBuilder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
this.buildMetadataHeader(requestBuilder, applicationMetadata, APPLICATION_METADATA);
}
this.buildTransmittedHeader(requestBuilder, transHeaders);
context.setOriginRequest(requestBuilder.build());
}

@ -0,0 +1,44 @@
/*
* 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.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class HttpServletRequestHeaderWrapper extends HttpServletRequestWrapper {
private Map<String, String> addHeaders;
public HttpServletRequestHeaderWrapper(HttpServletRequest request, Map<String, String> addHeaders) {
super(request);
this.addHeaders = addHeaders;
}
@Override
public String getHeader(String name) {
if (addHeaders.containsKey(name)) {
return addHeaders.get(name);
}
else {
return super.getHeader(name);
}
}
}

@ -18,7 +18,6 @@
package com.tencent.cloud.metadata.config;
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;
@ -52,7 +51,6 @@ public class MetadataTransferAutoConfigurationTest {
.run(context -> {
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferFeignInterceptorConfig.class);
assertThat(context).hasSingleBean(EncodeTransferMedataFeignEnhancedPlugin.class);
assertThat(context).hasSingleBean(EncodeTransferMedataRestTemplateEnhancedPlugin.class);
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig.class);
assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferScgFilterConfig.class);
});
@ -68,7 +66,6 @@ public class MetadataTransferAutoConfigurationTest {
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);
});

@ -43,7 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* Test for {@link EncodeTransferMedataRestTemplateEnhancedPlugin}.
* Test for {@link EncodeTransferMedataRestTemplateInterceptor}.
*
* @author Haotian Zhang
*/

@ -30,7 +30,6 @@ import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import com.tencent.polaris.client.util.Utils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -101,16 +100,16 @@ public class PolarisCircuitBreakerTest {
Method getConfigurationsMethod = ReflectionUtils.findMethod(PolarisCircuitBreakerFactory.class,
"getConfigurations");
Assertions.assertNotNull(getConfigurationsMethod);
assertThat(getConfigurationsMethod).isNotNull();
ReflectionUtils.makeAccessible(getConfigurationsMethod);
Map<?, ?> values = (Map<?, ?>) ReflectionUtils.invokeMethod(getConfigurationsMethod, polarisCircuitBreakerFactory);
Assertions.assertNotNull(values);
assertThat(values).isNotNull();
Assertions.assertEquals(1, values.size());
assertThat(values.size()).isEqualTo(1);
Utils.sleepUninterrupted(10 * 1000);
Assertions.assertEquals(0, values.size());
assertThat(values.size()).isEqualTo(0);
});
}

@ -19,13 +19,14 @@ package com.tencent.cloud.polaris.circuitbreaker.common;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for ${@link PolarisResultToErrorCode}.
*
@ -38,7 +39,7 @@ class PolarisResultToErrorCodeTest {
@Test
void testOnSuccess() {
Assertions.assertEquals(200, converter.onSuccess("any value"));
assertThat(converter.onSuccess("any value")).isEqualTo(200);
}
@Test
@ -51,7 +52,7 @@ class PolarisResultToErrorCodeTest {
int errorCode = converter.onError(exception);
// Then
Assertions.assertEquals(404, errorCode);
assertThat(errorCode).isEqualTo(404);
}
@Test
@ -60,7 +61,7 @@ class PolarisResultToErrorCodeTest {
int errorCode = converter.onError(new RuntimeException("test"));
// Then
Assertions.assertEquals(-1, errorCode);
assertThat(errorCode).isEqualTo(-1);
}
@Test
@ -72,7 +73,7 @@ class PolarisResultToErrorCodeTest {
int errorCode = converter.onError(exception);
// Then
Assertions.assertEquals(-1, errorCode);
assertThat(errorCode).isEqualTo(-1);
}
@Test
@ -85,10 +86,10 @@ class PolarisResultToErrorCodeTest {
// test exist class
boolean result1 = (boolean) checkClassExist.invoke(converter, "java.lang.String");
Assertions.assertTrue(result1);
assertThat(result1).isTrue();
// test not exist class
boolean result2 = (boolean) checkClassExist.invoke(converter, "com.nonexistent.Class");
Assertions.assertFalse(result2);
assertThat(result2).isFalse();
}
}

@ -28,7 +28,6 @@ import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import feign.InvocationHandlerFactory;
import feign.Target;
import feign.codec.Decoder;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -37,6 +36,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.openfeign.FallbackFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@ -84,20 +84,20 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest {
@Test
void testConstructorWithNullTarget() {
Assertions.assertThrows(NullPointerException.class, () ->
assertThatThrownBy(() ->
new PolarisFeignCircuitBreakerInvocationHandler(
null, dispatch, fallbackFactory, decoder
)
);
).isExactlyInstanceOf(NullPointerException.class);
}
@Test
void testConstructorWithNullDispatch() {
Assertions.assertThrows(NullPointerException.class, () ->
assertThatThrownBy(() ->
new PolarisFeignCircuitBreakerInvocationHandler(
target, null, fallbackFactory, decoder
)
);
).isExactlyInstanceOf(NullPointerException.class);
}
@Test
@ -108,9 +108,9 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest {
Map<Method, Method> result = PolarisFeignCircuitBreakerInvocationHandler.toFallbackMethod(testDispatch);
Assertions.assertNotNull(result);
Assertions.assertTrue(result.containsKey(method));
Assertions.assertEquals(method, result.get(method));
assertThat(result).isNotNull();
assertThat(result.containsKey(method)).isTrue();
assertThat(result.get(method)).isEqualTo(method);
}
@Test
@ -119,10 +119,10 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest {
Object mockProxy = mock(Object.class);
// Test equals with null
Assertions.assertFalse((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {null}));
assertThat((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {null})).isFalse();
// Test equals with non-proxy object
Assertions.assertFalse((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {new Object()}));
assertThat((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {new Object()})).isFalse();
}
@Test
@ -131,7 +131,7 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest {
Object mockProxy = mock(Object.class);
when(target.toString()).thenReturn("TestTarget");
Assertions.assertEquals("TestTarget", handler.invoke(mockProxy, toStringMethod, null));
assertThat(handler.invoke(mockProxy, toStringMethod, null)).isEqualTo("TestTarget");
}
@Test
@ -178,7 +178,7 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest {
Object result = handler.invoke(null, testMethod, new Object[] {});
// Verify
Assertions.assertEquals(expected, result);
assertThat(result).isEqualTo(expected);
}
@Test
@ -190,8 +190,8 @@ class PolarisFeignCircuitBreakerInvocationHandlerTest {
decoder
);
Assertions.assertEquals(handler, testHandler);
Assertions.assertEquals(handler.hashCode(), testHandler.hashCode());
assertThat(testHandler).isEqualTo(handler);
assertThat(testHandler.hashCode()).isEqualTo(handler.hashCode());
}
interface TestInterface {

@ -20,12 +20,12 @@ package com.tencent.cloud.polaris.circuitbreaker.instrument.feign;
import feign.Feign;
import feign.RequestLine;
import feign.Target;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.openfeign.FallbackFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -45,7 +45,7 @@ public class PolarisFeignCircuitBreakerTest {
@Test
public void testBuilderNotNull() {
Assertions.assertNotNull(builder);
assertThat(builder).isNotNull();
}
@Test
@ -67,8 +67,8 @@ public class PolarisFeignCircuitBreakerTest {
MyService result = builder.target(target, fallback);
// Verify that the result is not null and the fallback factory is used
Assertions.assertNotNull(result);
Assertions.assertEquals("Fallback Hello", result.sayHello());
assertThat(result).isNotNull();
assertThat(result.sayHello()).isEqualTo("Fallback Hello");
}
@Test
@ -93,8 +93,8 @@ public class PolarisFeignCircuitBreakerTest {
MyService result = builder.target(target, fallbackFactory);
// Verify that the result is not null and the fallback factory is used
Assertions.assertNotNull(result);
Assertions.assertEquals("Fallback Hello", result.sayHello());
assertThat(result).isNotNull();
assertThat(result.sayHello()).isEqualTo("Fallback Hello");
}
@Test
@ -112,8 +112,7 @@ public class PolarisFeignCircuitBreakerTest {
MyService result = builder.target(target);
// Verify that the result is not null
Assertions.assertNotNull(result);
// Additional verifications can be added here based on the implementation
assertThat(result).isNotNull();
}
@Test
@ -122,8 +121,7 @@ public class PolarisFeignCircuitBreakerTest {
Feign feign = builder.build(null);
// Verify that the Feign instance is not null
Assertions.assertNotNull(feign);
// Additional verifications can be added here based on the implementation
assertThat(feign).isNotNull();
}
public interface MyService {

@ -17,16 +17,19 @@
package com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
/**
* Tests for {@link PolarisCircuitBreakerHttpResponse}.
*
@ -35,28 +38,28 @@ import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class PolarisCircuitBreakerHttpResponseTest {
@Test
void testConstructorWithCodeOnly() {
void testConstructorWithCodeOnly() throws IOException {
PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200);
Assertions.assertEquals(200, response.getRawStatusCode());
Assertions.assertNotNull(response.getHeaders());
Assertions.assertTrue(response.getHeaders().isEmpty());
Assertions.assertNull(response.getBody());
assertThat(response.getStatusCode().value()).isEqualTo(200);
assertThat(response.getHeaders()).isNotNull();
assertThat(response.getHeaders()).isEmpty();
assertThat(response.getBody()).isNull();
}
@Test
void testConstructorWithCodeAndBody() {
void testConstructorWithCodeAndBody() throws IOException {
String body = "test body";
PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200, body);
Assertions.assertEquals(200, response.getRawStatusCode());
Assertions.assertNotNull(response.getHeaders());
Assertions.assertTrue(response.getHeaders().isEmpty());
Assertions.assertNotNull(response.getBody());
assertThat(response.getStatusCode().value()).isEqualTo(200);
assertThat(response.getHeaders()).isNotNull();
assertThat(response.getHeaders()).isEmpty();
assertThat(response.getBody()).isNotNull();
}
@Test
void testConstructorWithCodeHeadersAndBody() {
void testConstructorWithCodeHeadersAndBody() throws IOException {
String body = "test body";
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
@ -64,59 +67,59 @@ public class PolarisCircuitBreakerHttpResponseTest {
PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200, headers, body);
Assertions.assertEquals(200, response.getRawStatusCode());
Assertions.assertNotNull(response.getHeaders());
Assertions.assertEquals(2, response.getHeaders().size());
Assertions.assertTrue(response.getHeaders().containsKey("Content-Type"));
Assertions.assertTrue(response.getHeaders().containsKey("Authorization"));
Assertions.assertNotNull(response.getBody());
assertThat(response.getStatusCode().value()).isEqualTo(200);
assertThat(response.getHeaders()).isNotNull();
assertThat(response.getHeaders().size()).isEqualTo(2);
assertThat(response.getHeaders()).containsKey("Content-Type");
assertThat(response.getHeaders()).containsKey("Authorization");
assertThat(response.getBody()).isNotNull();
}
@Test
void testConstructorWithFallbackInfo() {
void testConstructorWithFallbackInfo() throws IOException {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
CircuitBreakerStatus.FallbackInfo fallbackInfo = new CircuitBreakerStatus.FallbackInfo(200, headers, "test body");
PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(fallbackInfo);
Assertions.assertEquals(200, response.getRawStatusCode());
Assertions.assertEquals(fallbackInfo, response.getFallbackInfo());
Assertions.assertNotNull(response.getHeaders());
Assertions.assertTrue(response.getHeaders().containsKey("Content-Type"));
Assertions.assertNotNull(response.getBody());
assertThat(response.getStatusCode().value()).isEqualTo(200);
assertThat(response.getFallbackInfo()).isEqualTo(fallbackInfo);
assertThat(response.getHeaders()).isNotNull();
assertThat(response.getHeaders()).containsKey("Content-Type");
assertThat(response.getBody()).isNotNull();
}
@Test
void testGetStatusTextWithValidHttpStatus() {
PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200);
Assertions.assertEquals("OK", response.getStatusText());
assertThat(response.getStatusText()).isEqualTo("OK");
}
@Test
void testGetStatusTextWithInvalidHttpStatus() {
PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(999);
Assertions.assertEquals("", response.getStatusText());
assertThat(response.getStatusText()).isEqualTo("");
}
@Test
void testClose() {
PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200, "test body");
InputStream body = response.getBody();
Assertions.assertNotNull(body);
assertThat(body).isNotNull();
response.close();
// Verify that reading from closed stream throws exception
Assertions.assertDoesNotThrow(() -> body.read());
assertThatNoException().isThrownBy(() -> body.read());
}
@Test
void testCloseWithNullBody() {
PolarisCircuitBreakerHttpResponse response = new PolarisCircuitBreakerHttpResponse(200);
Assertions.assertNull(response.getBody());
assertThat(response.getBody()).isNull();
// Should not throw exception when closing null body
Assertions.assertDoesNotThrow(() -> response.close());
assertThatNoException().isThrownBy(() -> response.close());
}
}

@ -33,7 +33,6 @@ import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import com.tencent.polaris.configuration.api.core.ConfigKVFile;
import com.tencent.polaris.configuration.client.internal.RevisableConfigFileGroup;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -354,21 +353,20 @@ public class PolarisConfigFileLocatorTest {
testProperties.setCheckAddress(checkAddress);
testProperties.setShutdownIfConnectToConfigServerFailed(shutdownIfConnectToConfigServerFailed);
Assertions.assertEquals(enabled, testProperties.isEnabled());
Assertions.assertEquals(address, testProperties.getAddress());
Assertions.assertEquals(port, testProperties.getPort());
Assertions.assertEquals(token, testProperties.getToken());
Assertions.assertEquals(autoRefresh, testProperties.isAutoRefresh());
Assertions.assertEquals(refreshType, testProperties.getRefreshType());
Assertions.assertEquals(groups, testProperties.getGroups());
Assertions.assertEquals(preference, testProperties.isPreference());
Assertions.assertEquals(dataSource, testProperties.getDataSource());
Assertions.assertEquals(localFileRootPath, testProperties.getLocalFileRootPath());
Assertions.assertEquals(internalEnabled, testProperties.isInternalEnabled());
Assertions.assertEquals(checkAddress, testProperties.isCheckAddress());
Assertions.assertEquals(shutdownIfConnectToConfigServerFailed, testProperties.isShutdownIfConnectToConfigServerFailed());
Assertions.assertNotNull(testProperties.toString());
assertThat(testProperties.isEnabled()).isEqualTo(enabled);
assertThat(testProperties.getAddress()).isEqualTo(address);
assertThat(testProperties.getPort()).isEqualTo(port);
assertThat(testProperties.getToken()).isEqualTo(token);
assertThat(testProperties.isAutoRefresh()).isEqualTo(autoRefresh);
assertThat(testProperties.getRefreshType()).isEqualTo(refreshType);
assertThat(testProperties.getGroups()).isEqualTo(groups);
assertThat(testProperties.isPreference()).isEqualTo(preference);
assertThat(testProperties.getDataSource()).isEqualTo(dataSource);
assertThat(testProperties.getLocalFileRootPath()).isEqualTo(localFileRootPath);
assertThat(testProperties.isInternalEnabled()).isEqualTo(internalEnabled);
assertThat(testProperties.isCheckAddress()).isEqualTo(checkAddress);
assertThat(testProperties.isShutdownIfConnectToConfigServerFailed()).isEqualTo(shutdownIfConnectToConfigServerFailed);
assertThat(testProperties.toString()).isNotNull();
}
private void clearCompositePropertySourceCache() {

@ -26,7 +26,6 @@ import com.tencent.polaris.api.config.consumer.ServiceRouterConfig;
import com.tencent.polaris.factory.ConfigAPIFactory;
import com.tencent.polaris.factory.config.ConfigurationImpl;
import com.tencent.polaris.plugins.router.nearby.NearbyRouterConfig;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -60,7 +59,7 @@ public class RouterBootstrapAutoConfigurationTest {
routerConfigModifier.modify((ConfigurationImpl) configuration);
NearbyRouterConfig nearbyRouterConfig = configuration.getConsumer().getServiceRouter().getPluginConfig(
ServiceRouterConfig.DEFAULT_ROUTER_NEARBY, NearbyRouterConfig.class);
Assertions.assertEquals("CAMPUS", nearbyRouterConfig.getMatchLevel().name());
assertThat(nearbyRouterConfig.getMatchLevel().name()).isEqualTo("CAMPUS");
});
}
}

@ -54,7 +54,18 @@ public final class MetadataConstant {
* Metadata HTTP header name.
*/
public static class HeaderName {
/**
* TSF Tags.
*/
public static final String TSF_TAGS = "TSF-Tags";
/**
* TSF System Tags.
*/
public static final String TSF_SYSTEM_TAG = "TSF-System-Tags";
/**
* TSF Metadata.
*/
public static final String TSF_METADATA = "TSF-Metadata";
/**
* Custom metadata.
*/
@ -78,6 +89,11 @@ public final class MetadataConstant {
* Namespace context.
*/
public static final String NAMESPACE = "SCT-NAMESPACE";
/**
* TSF Namespace Id context.
*/
public static final String TSF_NAMESPACE_ID = "TSF-NamespaceId";
}
public static class DefaultMetadata {

@ -51,7 +51,7 @@ public class OrderConstant {
/**
* Order of encode transfer metadata interceptor.
*/
public static final int ENCODE_TRANSFER_METADATA_INTERCEPTOR_ORDER = Ordered.LOWEST_PRECEDENCE - 1;
public static final int ENCODE_TRANSFER_METADATA_INTERCEPTOR_ORDER = Ordered.LOWEST_PRECEDENCE;
/**
* Order of encode router label interceptor.

@ -77,4 +77,12 @@ public final class TsfContextUtils {
}
return onlyTsfConsulEnabled;
}
/**
* This method should be called after {@link com.tencent.cloud.common.tsf.TsfContextUtils#isOnlyTsfConsulEnabled(Environment)}.
* @return whether only Tsf Consul is enabled
*/
public static boolean isOnlyTsfConsulEnabled() {
return onlyTsfConsulEnabled;
}
}

@ -17,11 +17,15 @@
package com.tencent.cloud.common.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.slf4j.Logger;
@ -93,6 +97,30 @@ public final class JacksonUtils {
}
}
public static <T> T deserialize(String jsonStr, TypeReference<T> typeReference) {
try {
return OM.readValue(jsonStr, typeReference);
}
catch (JsonProcessingException e) {
LOG.error("Json to object failed. {}", typeReference, e);
throw new RuntimeException("Json to object failed.", e);
}
}
public static <T> List<T> deserializeCollection(String jsonArrayStr, Class<T> clazz) {
JavaType javaType = getCollectionType(ArrayList.class, clazz);
try {
return (List<T>) OM.readValue(jsonArrayStr, javaType);
}
catch (Exception t) {
throw new RuntimeException(t);
}
}
public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
return OM.getTypeFactory().constructParametricType(collectionClass, elementClasses);
}
/**
* Json to Map.
* @param jsonStr Json String

@ -0,0 +1,231 @@
/*
* 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.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.tsf.TsfContextUtils;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.metadata.core.constant.MetadataConstants;
import com.tencent.polaris.metadata.core.constant.TsfMetadataConstants;
import com.tencent.polaris.plugins.connector.consul.service.common.TagConstant;
import com.tencent.polaris.plugins.router.lane.LaneRouter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.tsf.core.entity.Metadata;
import org.springframework.tsf.core.entity.Tag;
public final class TsfTagUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(TsfTagUtils.class);
private TsfTagUtils() {
}
public static List<Tag> deserializeTagList(String buffer) {
if (StringUtils.isEmpty(buffer)) {
return null;
}
return Arrays.asList(JacksonUtils.deserialize(UrlUtils.decode(buffer), Tag[].class));
}
public static Metadata deserializeMetadata(String buffer) {
if (StringUtils.isEmpty(buffer)) {
return null;
}
return JacksonUtils.deserialize(UrlUtils.decode(buffer), Metadata.class);
}
public static Map<String, String> getTsfMetadataMap(Map<String, String> calleeTransitiveHeaders, Map<String, String> disposableMetadata,
Map<String, String> customMetadata, Map<String, String> applicationMetadata) {
Map<String, String> tsfMetadataMap = new HashMap<>();
Set<String> tsfUserTagKeys = new HashSet<>();
// user tags
List<Tag> tsfUserTags = new ArrayList<>();
List<Tag> tsfSystemTags = new ArrayList<>();
Tag laneTag = null;
for (Map.Entry<String, String> entry : customMetadata.entrySet()) {
if (tsfUserTagKeys.contains(entry.getKey())) {
continue;
}
Tag tag = new Tag(entry.getKey(), entry.getValue(), Tag.ControlFlag.TRANSITIVE);
tsfUserTags.add(tag);
tsfUserTagKeys.add(entry.getKey());
if (LaneRouter.TRAFFIC_STAIN_LABEL.equals(entry.getKey()) && entry.getValue().contains("/")) {
String lane = entry.getValue().split("/")[1];
laneTag = new Tag("lane", lane, Tag.ControlFlag.TRANSITIVE);
}
}
for (Map.Entry<String, String> entry : disposableMetadata.entrySet()) {
if (tsfUserTagKeys.contains(entry.getKey())) {
continue;
}
Tag tag = new Tag(entry.getKey(), entry.getValue());
tsfUserTags.add(tag);
tsfUserTagKeys.add(entry.getKey());
}
for (Map.Entry<String, String> entry : calleeTransitiveHeaders.entrySet()) {
if (entry.getKey().startsWith(MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX)) {
String key = entry.getKey().substring(MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH);
String value = entry.getValue();
if (tsfUserTagKeys.contains(key)) {
continue;
}
Tag tag = new Tag(key, value, Tag.ControlFlag.TRANSITIVE);
tsfUserTags.add(tag);
tsfUserTagKeys.add(key);
if (LaneRouter.TRAFFIC_STAIN_LABEL.equals(key) && value.contains("/")) {
String lane = value.split("/")[1];
laneTag = new Tag("lane", lane, Tag.ControlFlag.TRANSITIVE);
}
}
}
if (laneTag != null) {
tsfSystemTags.add(laneTag);
}
if (CollectionUtils.isNotEmpty(tsfUserTags)) {
tsfMetadataMap.put(MetadataConstant.HeaderName.TSF_TAGS, JacksonUtils.serialize2Json(tsfUserTags));
}
if (CollectionUtils.isNotEmpty(tsfSystemTags)) {
tsfMetadataMap.put(MetadataConstant.HeaderName.TSF_SYSTEM_TAG, JacksonUtils.serialize2Json(tsfSystemTags));
}
Metadata metadata = new Metadata();
for (Map.Entry<String, String> entry : applicationMetadata.entrySet()) {
switch (entry.getKey()) {
case MetadataConstants.LOCAL_SERVICE:
metadata.setServiceName(entry.getValue());
break;
case MetadataConstants.LOCAL_IP:
metadata.setLocalIp(entry.getValue());
break;
case TsfMetadataConstants.TSF_GROUP_ID:
metadata.setGroupId(entry.getValue());
break;
case TsfMetadataConstants.TSF_APPLICATION_ID:
metadata.setApplicationId(entry.getValue());
break;
case TsfMetadataConstants.TSF_INSTNACE_ID:
metadata.setInstanceId(entry.getValue());
break;
case TsfMetadataConstants.TSF_PROG_VERSION:
metadata.setApplicationVersion(entry.getValue());
break;
case TsfMetadataConstants.TSF_NAMESPACE_ID:
metadata.setNamespaceId(entry.getValue());
break;
}
}
tsfMetadataMap.put(MetadataConstant.HeaderName.TSF_METADATA, JacksonUtils.serialize2Json(metadata));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("calleeTransitiveHeaders:{}, disposableMetadata: {}, customMetadata: {}, applicationMetadata: {}, tsfMetadataMap:{}",
calleeTransitiveHeaders, disposableMetadata, customMetadata, applicationMetadata, tsfMetadataMap);
}
return tsfMetadataMap;
}
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) {
if (!TsfContextUtils.isOnlyTsfConsulEnabled()) {
return;
}
List<Tag> tsfUserTagList = TsfTagUtils.deserializeTagList(encodedUserTagList);
int tagSize = Optional.ofNullable(tsfUserTagList).map(List::size).orElse(0);
Map<String, String> tsfTransitiveMetadata = new HashMap<>(tagSize);
Map<String, String> tsfDisposableMetadata = new HashMap<>(tagSize);
if (CollectionUtils.isNotEmpty(tsfUserTagList)) {
for (Tag tag : tsfUserTagList) {
if (Tag.ControlFlag.TRANSITIVE.equals(tag.getFlags())) {
tsfTransitiveMetadata.put(tag.getKey(), tag.getValue());
}
tsfDisposableMetadata.put(tag.getKey(), tag.getValue());
}
mergedTransitiveMetadata.putAll(tsfTransitiveMetadata);
mergedDisposableMetadata.putAll(tsfDisposableMetadata);
}
List<Tag> tsfSystemTagList = TsfTagUtils.deserializeTagList(encodedSystemTagList);
if (CollectionUtils.isNotEmpty(tsfSystemTagList)) {
for (Tag tag : tsfSystemTagList) {
if ("lane".equals(tag.getKey())) {
mergedTransitiveMetadata.put(LaneRouter.TRAFFIC_STAIN_LABEL, UrlUtils.encode("tsf/" + tag.getValue()));
addHeaders.put(MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX + LaneRouter.TRAFFIC_STAIN_LABEL,
UrlUtils.encode("tsf/" + tag.getValue()));
}
}
}
Metadata metadata = TsfTagUtils.deserializeMetadata(encodedMetadata);
if (metadata != null) {
if (StringUtils.isNotEmpty(metadata.getLocalIp())) {
callerIp.set(metadata.getLocalIp());
}
if (StringUtils.isNotEmpty(metadata.getApplicationId())) {
mergedApplicationMetadata.put(TsfMetadataConstants.TSF_APPLICATION_ID, metadata.getApplicationId());
}
if (StringUtils.isNotEmpty(metadata.getGroupId())) {
mergedApplicationMetadata.put(TsfMetadataConstants.TSF_GROUP_ID, metadata.getGroupId());
}
if (StringUtils.isNotEmpty(metadata.getApplicationVersion())) {
mergedApplicationMetadata.put(TsfMetadataConstants.TSF_PROG_VERSION, metadata.getApplicationVersion());
}
if (StringUtils.isNotEmpty(metadata.getNamespaceId())) {
mergedApplicationMetadata.put(TsfMetadataConstants.TSF_NAMESPACE_ID, metadata.getNamespaceId());
}
if (StringUtils.isNotEmpty(metadata.getServiceName())) {
mergedApplicationMetadata.put(MetadataConstants.LOCAL_SERVICE, metadata.getServiceName());
mergedApplicationMetadata.put(TagConstant.SYSTEM_FIELD.SOURCE_SERVICE_NAME, metadata.getServiceName());
}
if (StringUtils.isNotEmpty(metadata.getLocalIp())) {
mergedApplicationMetadata.put(MetadataConstants.LOCAL_IP, metadata.getLocalIp());
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("mergedTransitiveMetadata:{}, mergedDisposableMetadata: {}, mergedApplicationMetadata: {},"
+ " addHeaders:{}, encodedUserTagList:{}, encodedSystemTagList:{}, encodedMetadata:{}, callerIp:{}",
mergedTransitiveMetadata, mergedDisposableMetadata, mergedApplicationMetadata,
addHeaders, encodedUserTagList, encodedSystemTagList, encodedMetadata, callerIp.get());
}
}
}

@ -0,0 +1,152 @@
/*
* 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 org.springframework.tsf.core.entity;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.util.StringUtils;
public class Metadata implements Serializable {
@JsonProperty("ai")
private String applicationId = "";
@JsonProperty("av")
private String applicationVersion = "";
@JsonProperty("sn")
private String serviceName = "";
@JsonProperty("ii")
private String instanceId = "";
@JsonProperty("gi")
private String groupId = "";
@JsonProperty("li")
private String localIp = "";
@JsonProperty("lis")
private String localIps = "";
@JsonProperty("ni")
private String namespaceId = "";
@JsonProperty("pi")
private boolean preferIpv6;
public Metadata() {
}
public String getApplicationId() {
return applicationId;
}
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
// 其实是程序包的版本,但是这里程序包概念没啥用,直接用应用来表示
public String getApplicationVersion() {
return applicationVersion;
}
public void setApplicationVersion(String applicationVersion) {
this.applicationVersion = applicationVersion;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getInstanceId() {
return instanceId;
}
public void setInstanceId(String instanceId) {
this.instanceId = instanceId;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getLocalIp() {
if (preferIpv6 && !StringUtils.isEmpty(localIps)) {
for (String ip : localIps.split(",")) {
if (ip.contains(":")) {
return ip;
}
}
}
return localIp;
}
public void setLocalIp(String localIp) {
this.localIp = localIp;
}
public String getLocalIps() {
return localIps;
}
public void setLocalIps(String localIps) {
this.localIps = localIps;
}
public String getNamespaceId() {
return namespaceId;
}
public void setNamespaceId(String namespaceId) {
this.namespaceId = namespaceId;
}
public boolean isPreferIpv6() {
return preferIpv6;
}
public void setPreferIpv6(boolean preferIpv6) {
this.preferIpv6 = preferIpv6;
}
@Override
public String toString() {
return "Metadata{" +
"applicationId='" + applicationId + '\'' +
", applicationVersion='" + applicationVersion + '\'' +
", serviceName='" + serviceName + '\'' +
", instanceId='" + instanceId + '\'' +
", groupId='" + groupId + '\'' +
", localIp='" + localIp + '\'' +
", namespaceId='" + namespaceId + '\'' +
'}';
}
}

@ -22,17 +22,19 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Tag implements Serializable {
/**
* update version whenever change the content in tag.
*/
public static final int VERSION = 1;
@JsonProperty("k")
private String key;
@JsonProperty("v")
private String value;
@JsonProperty("f")
private Set<ControlFlag> flags = new HashSet<>();
public Tag(String key, String value, ControlFlag... flags) {
@ -98,31 +100,37 @@ public class Tag implements Serializable {
/**
* tag transitive by all services.
*/
@JsonProperty("0")
TRANSITIVE,
/**
* tag not used in auth.
*/
@JsonProperty("1")
NOT_IN_AUTH,
/**
* tag not used in route.
*/
@JsonProperty("2")
NOT_IN_ROUTE,
/**
* tag not used in trace.
*/
@JsonProperty("3")
NOT_IN_SLEUTH,
/**
* tag not used in lane.
*/
@JsonProperty("4")
NOT_IN_LANE,
/**
* tag not used in unit.
*/
@JsonProperty("5")
IN_UNIT
}
}

@ -0,0 +1,43 @@
/*
* 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.util;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.tsf.core.entity.Tag;
import static org.assertj.core.api.Assertions.assertThat;
/**
* test for {@link TsfTagUtils}.
*/
public class TsfTagUtilsTest {
@Test
public void deserializeTagList() {
String data = "%5B%7B%22k%22%3A%22tsf-gateway-ratelimit-context%22%2C%22v%22%3A%22grp-vyiwvq5t%22%2C%22f%22%3A%5B%5D%7D%2C%7B%22k%22%3A%22feat%22%2C%22v%22%3A%22test%22%2C%22f%22%3A%5B%220%22%5D%7D%5D";
List<Tag> tagList = TsfTagUtils.deserializeTagList(data);
for (Tag tag : tagList) {
assertThat(tag.getKey()).isNotNull();
assertThat(tag.getValue()).isNotNull();
assertThat(tag.getFlags()).isNotNull();
}
}
}

@ -74,7 +74,7 @@
<revision>2.1.0.0-2021.0.9-SNAPSHOT</revision>
<!-- Polaris SDK version -->
<polaris.version>2.0.2.0</polaris.version>
<polaris.version>2.0.3.0-SNAPSHOT</polaris.version>
<!-- Dependencies -->
<bcpkix-jdk18on.version>1.78.1</bcpkix-jdk18on.version>

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>tsf-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>msgw-scg</artifactId>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,34 @@
/*
* 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.msgw.scg;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.tsf.annotation.EnableTsf;
/**
* @author seanlxliu
*/
@SpringBootApplication
@EnableTsf
public class ScgApplication {
public static void main(String[] args) {
SpringApplication.run(ScgApplication.class, args);
}
}

@ -0,0 +1,28 @@
server:
port: 8080
error:
include-exception: true
spring:
application:
name: msgw-scg
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: false
httpclient:
# The connect timeout in millis, the default is 45s.
connectTimeout: 200
responseTimeout: 10s
consul:
enabled: true
scheme: HTTP
logging:
level:
root: INFO
file:
name: /tsf-demo-logs/${spring.application.name}/root.log
pattern:
level: "%-5level [${spring.application.name},%mdc{trace_id},%mdc{span_id},]"

@ -17,5 +17,6 @@
<module>provider-demo</module>
<module>consumer-demo-retry</module>
<module>consumer-demo</module>
<module>msgw-scg</module>
</modules>
</project>

@ -17,22 +17,34 @@
package com.tencent.cloud.plugin.gateway;
import com.tencent.cloud.common.tsf.ConditionalOnOnlyTsfConsulEnabled;
import com.tencent.cloud.plugin.gateway.context.ContextGatewayFilterFactory;
import com.tencent.cloud.plugin.gateway.context.ContextGatewayProperties;
import com.tencent.cloud.plugin.gateway.context.ContextGatewayPropertiesManager;
import com.tencent.cloud.plugin.gateway.context.ContextPropertiesRouteDefinitionLocator;
import com.tencent.cloud.plugin.gateway.context.ContextRoutePredicateFactory;
import com.tencent.cloud.plugin.gateway.context.GatewayConfigChangeListener;
import com.tencent.cloud.plugin.gateway.context.GatewayConsulRepo;
import com.tencent.cloud.polaris.config.ConditionalOnPolarisConfigEnabled;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
import com.tencent.tsf.gateway.core.plugin.GatewayPluginFactory;
import com.tencent.tsf.gateway.core.plugin.JwtGatewayPlugin;
import com.tencent.tsf.gateway.core.plugin.OAuthGatewayPlugin;
import com.tencent.tsf.gateway.core.plugin.ReqTransformerGatewayPlugin;
import com.tencent.tsf.gateway.core.plugin.TagGatewayPlugin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
@ -40,6 +52,11 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import static com.tencent.tsf.gateway.core.constant.PluginType.JWT;
import static com.tencent.tsf.gateway.core.constant.PluginType.OAUTH;
import static com.tencent.tsf.gateway.core.constant.PluginType.REQ_TRANSFORMER;
import static com.tencent.tsf.gateway.core.constant.PluginType.TAG;
/**
* Auto configuration for spring cloud gateway plugins.
* @author lepdou 2022-07-06
@ -52,6 +69,7 @@ public class GatewayPluginAutoConfiguration {
@Configuration
@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.context.enabled", matchIfMissing = true)
@ConditionalOnPolarisConfigEnabled
@AutoConfigureBefore(GatewayAutoConfiguration.class)
@ConditionalOnClass(GlobalFilter.class)
@Import(ContextGatewayProperties.class)
public static class ContextPluginConfiguration {
@ -62,6 +80,9 @@ public class GatewayPluginAutoConfiguration {
@Value("${spring.cloud.polaris.discovery.eager-load.gateway.enabled:#{'false'}}")
private boolean gatewayEagerLoadEnabled;
@Value("${tsf_group_id:}")
private String tsfGroupId;
@Bean
public ContextGatewayFilterFactory contextGatewayFilterFactory(ContextGatewayPropertiesManager contextGatewayPropertiesManager) {
return new ContextGatewayFilterFactory(contextGatewayPropertiesManager);
@ -73,8 +94,8 @@ public class GatewayPluginAutoConfiguration {
}
@Bean
public ContextRoutePredicateFactory contextServiceRoutePredicateFactory() {
return new ContextRoutePredicateFactory();
public ContextRoutePredicateFactory contextServiceRoutePredicateFactory(ContextGatewayPropertiesManager manager) {
return new ContextRoutePredicateFactory(manager);
}
@Bean
@ -82,7 +103,8 @@ public class GatewayPluginAutoConfiguration {
@Autowired(required = false) PolarisDiscoveryClient polarisDiscoveryClient,
@Autowired(required = false) PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
ContextGatewayPropertiesManager contextGatewayPropertiesManager = new ContextGatewayPropertiesManager();
contextGatewayPropertiesManager.setGroupRouteMap(properties.getGroups());
contextGatewayPropertiesManager.refreshGroupRoute(properties.getGroups());
contextGatewayPropertiesManager.setPathRewrites(properties.getPathRewrites());
if (commonEagerLoadEnabled && gatewayEagerLoadEnabled) {
contextGatewayPropertiesManager.eagerLoad(polarisDiscoveryClient, polarisReactiveDiscoveryClient);
}
@ -96,7 +118,7 @@ public class GatewayPluginAutoConfiguration {
@Bean
public GatewayConfigChangeListener gatewayConfigChangeListener(ContextGatewayPropertiesManager manager,
ApplicationEventPublisher publisher, Environment environment) {
ApplicationEventPublisher publisher, Environment environment) {
return new GatewayConfigChangeListener(manager, publisher, environment);
}
@ -105,5 +127,55 @@ public class GatewayPluginAutoConfiguration {
ApplicationContext applicationContext) {
return new PolarisReactiveLoadBalancerClientFilterBeanPostProcessor(applicationContext);
}
@Bean
@ConditionalOnOnlyTsfConsulEnabled
public GatewayConsulRepo gatewayConsulRepo(ContextGatewayProperties contextGatewayProperties,
PolarisSDKContextManager polarisSDKContextManager,
ContextGatewayPropertiesManager contextGatewayPropertiesManager,
ApplicationEventPublisher publisher) {
return new GatewayConsulRepo(contextGatewayProperties, polarisSDKContextManager,
contextGatewayPropertiesManager, publisher, tsfGroupId);
}
@Bean(destroyMethod = "close")
@ConditionalOnMissingBean
public GatewayPluginFactory gatewayPluginFactory(
ReqTransformerGatewayPlugin reqTransformerGatewayPlugin,
OAuthGatewayPlugin oAuthGatewayPlugin,
JwtGatewayPlugin jwtGatewayPlugin,
TagGatewayPlugin tagGatewayPlugin) {
GatewayPluginFactory gatewayPluginFactory = new GatewayPluginFactory();
gatewayPluginFactory.putGatewayPlugin(OAUTH.getType(), oAuthGatewayPlugin);
gatewayPluginFactory.putGatewayPlugin(JWT.getType(), jwtGatewayPlugin);
gatewayPluginFactory.putGatewayPlugin(TAG.getType(), tagGatewayPlugin);
gatewayPluginFactory.putGatewayPlugin(REQ_TRANSFORMER.getType(), reqTransformerGatewayPlugin);
return gatewayPluginFactory;
}
@Bean
@ConditionalOnMissingBean
public TagGatewayPlugin tagGatewayPlugin() {
return new TagGatewayPlugin();
}
@Bean
@ConditionalOnMissingBean
public OAuthGatewayPlugin oAuthGatewayPlugin(LoadBalancerClientFactory clientFactory, PolarisSDKContextManager polarisSDKContextManager) {
return new OAuthGatewayPlugin(clientFactory, polarisSDKContextManager);
}
@Bean
@ConditionalOnMissingBean
public JwtGatewayPlugin jwtGatewayPlugin() {
return new JwtGatewayPlugin();
}
@Bean
@ConditionalOnMissingBean
public ReqTransformerGatewayPlugin reqTransformerGatewayPlugin() {
return new ReqTransformerGatewayPlugin();
}
}
}

@ -33,7 +33,7 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance
private final ReactiveLoadBalancerClientFilter clientFilter;
public PolarisReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
GatewayLoadBalancerProperties properties, ReactiveLoadBalancerClientFilter clientFilter) {
GatewayLoadBalancerProperties properties, ReactiveLoadBalancerClientFilter clientFilter) {
super(clientFactory, properties);
this.clientFilter = clientFilter;
}

@ -31,7 +31,7 @@ public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessor implements
*/
public static final int ORDER = 0;
private ApplicationContext applicationContext;
private final ApplicationContext applicationContext;
public PolarisReactiveLoadBalancerClientFilterBeanPostProcessor(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;

@ -0,0 +1,119 @@
/*
* 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.plugin.gateway.context;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.constant.AuthMode;
import com.tencent.tsf.gateway.core.constant.CommonStatus;
import com.tencent.tsf.gateway.core.constant.HeaderName;
import com.tencent.tsf.gateway.core.constant.TsfAlgType;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
import com.tencent.tsf.gateway.core.util.TsfSignUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.server.ServerWebExchange;
public final class AuthCheckUtil {
private static final Logger logger = LoggerFactory.getLogger(AuthCheckUtil.class);
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private AuthCheckUtil() {
}
public static void signCheck(ServerWebExchange exchange, GroupContext groupContext) {
AuthMode authMode = Optional.ofNullable(groupContext.getAuth()).map(GroupContext.ContextAuth::getType)
.orElse(null);
if (!AuthMode.SECRET.equals(authMode)) {
return;
}
String secretId = exchange.getRequest().getHeaders().getFirst(HeaderName.APP_KEY);
if (StringUtils.isEmpty(secretId)) {
logger.error("[signCheck] secretId is empty. path: {}", exchange.getRequest().getURI().getPath());
throw new TsfGatewayException(TsfGatewayError.GATEWAY_BAD_REQUEST, "RequestHeader x-mg-secretid is required");
}
String nonce = exchange.getRequest().getHeaders().getFirst(HeaderName.NONCE);
if (StringUtils.isEmpty(nonce)) {
logger.error("[signCheck] nonce is empty. path: {}", exchange.getRequest().getURI().getPath());
throw new TsfGatewayException(TsfGatewayError.GATEWAY_BAD_REQUEST, "RequestHeader x-mg-nonce is required");
}
String alg = exchange.getRequest().getHeaders().getFirst(HeaderName.ALG);
if (StringUtils.isEmpty(alg)) {
logger.error("[signCheck] alg is empty. path: {}", exchange.getRequest().getURI().getPath());
throw new TsfGatewayException(TsfGatewayError.GATEWAY_BAD_REQUEST, "RequestHeader x-mg-alg is required");
}
String clientSign = exchange.getRequest().getHeaders().getFirst(HeaderName.SIGN);
if (StringUtils.isEmpty(clientSign)) {
logger.error("[signCheck] sign is empty. path: {}", exchange.getRequest().getURI().getPath());
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "RequestHeader x-mg-sign is wrong");
}
TsfAlgType algType = TsfAlgType.getSecurityCode(alg);
List<GroupContext.ContextSecret> secretList = groupContext.getAuth().getSecrets();
GroupContext.ContextSecret secret = null;
if (secretList != null) {
for (GroupContext.ContextSecret s : secretList) {
if (s.getId().equals(secretId)) {
secret = s;
break;
}
}
}
if (secret == null) {
logger.error("[signCheck] secret is not found. secretId:{}, path: {}", secretId, exchange.getRequest()
.getURI().getPath());
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "GroupSecret Not Found");
}
if (CommonStatus.DISABLED.getStatus().equals(secret.getStatus())) {
logger.error("[signCheck] secret is invalid. secretId:{}, path: {}", secretId, exchange.getRequest()
.getURI().getPath());
throw new TsfGatewayException(TsfGatewayError.GATEWAY_BAD_REQUEST, "GroupSecret Invalid");
}
String expiredTime = secret.getExpiredTime();
LocalDateTime expiredDateTime = LocalDateTime.parse(expiredTime, FORMATTER);
LocalDateTime nowDateTime = LocalDateTime.now();
boolean expired = nowDateTime.isAfter(expiredDateTime);
if (expired) {
logger.error("[signCheck] secret is expired. secretId:{}, path: {}", secretId, exchange.getRequest()
.getURI().getPath());
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "GroupSecret Expired");
}
String serverSign = TsfSignUtil.generate(nonce, secretId, secret.getKey(), algType);
if (!StringUtils.equals(clientSign, serverSign)) {
logger.error("[signCheck] sign check failed. secretId:{}, nonce:{}, secretKey:{}, clientSign:{}, path: {}",
secretId, nonce, secret.getKey(), clientSign, exchange.getRequest().getURI().getPath());
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "GroupSecret Check Failed");
}
}
}

@ -17,13 +17,34 @@
package com.tencent.cloud.plugin.gateway.context;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
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 com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.TsfGatewayRequest;
import com.tencent.tsf.gateway.core.constant.GatewayConstant;
import com.tencent.tsf.gateway.core.constant.HeaderName;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
import com.tencent.tsf.gateway.core.model.PluginDetail;
import com.tencent.tsf.gateway.core.model.PluginInfo;
import com.tencent.tsf.gateway.core.model.PluginPayload;
import com.tencent.tsf.gateway.core.plugin.GatewayPluginFactory;
import com.tencent.tsf.gateway.core.plugin.IGatewayPlugin;
import com.tencent.tsf.gateway.core.util.CookieUtil;
import com.tencent.tsf.gateway.core.util.IdGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
@ -34,20 +55,30 @@ import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.core.Ordered;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.tsf.core.TsfContext;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import static com.tencent.tsf.gateway.core.constant.GatewayConstant.TSF_GATEWAY_RATELIMIT_CONTEXT_TAG;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_PATH_CONTAINER_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
import static org.springframework.http.server.PathContainer.parsePath;
public class ContextGatewayFilter implements GatewayFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(ContextGatewayFilter.class);
private ContextGatewayPropertiesManager manager;
private final ContextGatewayPropertiesManager manager;
private ContextGatewayFilterFactory.Config config;
private final ContextGatewayFilterFactory.Config config;
public ContextGatewayFilter(ContextGatewayPropertiesManager manager, ContextGatewayFilterFactory.Config config) {
this.manager = manager;
@ -56,57 +87,196 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ResponseStatusException pathRewriteException = (ResponseStatusException) exchange.getAttributes()
.get(GatewayConstant.PATH_REWRITE_EXCEPTION);
if (pathRewriteException != null) {
throw pathRewriteException;
}
// plugins will add metadata
if (exchange.getAttributes().containsKey(MetadataConstant.HeaderName.METADATA_CONTEXT)) {
MetadataContextHolder.set((MetadataContext) exchange.getAttributes().get(
MetadataConstant.HeaderName.METADATA_CONTEXT));
}
// for tsf gateway rate-limit
TsfContext.putTag(TSF_GATEWAY_RATELIMIT_CONTEXT_TAG, config.getGroup());
GroupContext groupContext = manager.getGroups().get(config.getGroup());
// auth
AuthCheckUtil.signCheck(exchange, groupContext);
// path can be changed in ContextRoutePredicateFactory
PathContainer path = (PathContainer) exchange.getAttributes()
.computeIfAbsent(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR,
s -> parsePath(exchange.getRequest().getURI().getRawPath()));
ServerWebExchange apiRebuildExchange;
if (ApiType.MS.equals(groupContext.getPredicate().getApiType())) {
return msFilter(exchange, chain, groupContext);
apiRebuildExchange = msFilter(exchange, chain, groupContext, path);
}
else {
return externalFilter(exchange, chain, groupContext);
apiRebuildExchange = externalFilter(exchange, chain, path);
}
ServerWebExchange pluginRebuildExchange = doPlugins(apiRebuildExchange,
(GroupContext.ContextRoute) exchange.getAttributes().get(GatewayConstant.CONTEXT_ROUTE));
return chain.filter(pluginRebuildExchange);
}
private Mono<Void> externalFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext) {
private ServerWebExchange doPlugins(ServerWebExchange exchange, GroupContext.ContextRoute contextRoute) {
List<PluginPayload> pluginPayloadList = pluginCheck(exchange, contextRoute);
if (CollectionUtils.isEmpty(pluginPayloadList)) {
return exchange;
}
ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
HttpHeaders headers = exchange.getRequest().getHeaders();
StringBuilder cookieStringBuilder = new StringBuilder();
List<String> cookie = headers.get("Cookie");
if (!org.springframework.util.CollectionUtils.isEmpty(cookie)) {
cookieStringBuilder.append(cookie.get(0));
}
Map<String, String[]> queryParamFromPlugin = new HashMap<>();
Map<String, String> headersMapFromPlugin = new HashMap<>();
Map<String, String> responseHeadersMapFromPlugin = new HashMap<>();
//添加queryParam与header
for (PluginPayload pluginPayload : pluginPayloadList) {
Map<String, String[]> parameterMap = pluginPayload.getParameterMap();
Map<String, String> headersMap = pluginPayload.getRequestHeaders();
Map<String, String> responseHeaderMap = pluginPayload.getResponseHeaders();
Map<String, String> requestCookieMap = pluginPayload.getRequestCookies();
//合并请求参数
if (!org.springframework.util.CollectionUtils.isEmpty(parameterMap)) {
queryParamFromPlugin.putAll(parameterMap);
}
//合并请求头参数
if (!org.springframework.util.CollectionUtils.isEmpty(headersMap)) {
headersMapFromPlugin.putAll(headersMap);
}
//合并cookie参数
CookieUtil.buildCookie(cookieStringBuilder, requestCookieMap);
//合并响应头参数
if (!org.springframework.util.CollectionUtils.isEmpty(responseHeaderMap)) {
responseHeadersMapFromPlugin.putAll(responseHeaderMap);
}
}
//添加cookie至header
headersMapFromPlugin.put("Cookie", cookieStringBuilder.toString());
//重构请求参数
URI newUri = addQueryParam(exchange.getRequest().getURI(), queryParamFromPlugin);
builder.uri(newUri);
builder.headers(httpHeader -> httpHeader.setAll(headersMapFromPlugin));
//添加响应头至请求上下文
if (CollectionUtils.isNotEmpty(responseHeadersMapFromPlugin)) {
responseHeadersMapFromPlugin.forEach((k, v) -> exchange.getResponse().getHeaders().add(k, v));
}
return exchange.mutate().request(builder.build()).build();
}
private List<PluginPayload> pluginCheck(ServerWebExchange exchange, GroupContext.ContextRoute contextRoute) {
List<PluginPayload> pluginPayloadList = new ArrayList<>();
List<PluginDetail> pluginDetails = manager.getPluginDetails(config.getGroup(), contextRoute.getApiId());
if (pluginDetails == null || pluginDetails.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug("[doPlugins] No plugin found for group: {}, path:{}, path id: {}", config.getGroup(), contextRoute.getPath(), contextRoute.getApiId());
}
return pluginPayloadList;
}
TsfGatewayRequest tsfGatewayRequest = buildTsfGatewayRequest(exchange);
//遍历绑定的插件
for (PluginDetail pluginDetail : pluginDetails) {
PluginInfo pluginInfo = manager.getPluginInfo(pluginDetail.getId());
//pluginInfo.check();
//执行插件逻辑
if (logger.isDebugEnabled()) {
logger.debug("pluginInfo is : {}", pluginInfo);
}
IGatewayPlugin gatewayPluginExecutor;
try {
gatewayPluginExecutor = GatewayPluginFactory.getGatewayPluginExecutor(pluginInfo.getType());
}
catch (Throwable t) {
//日志打印出原始异常
logger.error("build pluginExecute error", t);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "build pluginExecute error");
}
if (gatewayPluginExecutor == null) {
logger.error("can not find pluginExecutor");
return Collections.emptyList();
}
PluginPayload pluginPayload = gatewayPluginExecutor.invoke(pluginInfo, tsfGatewayRequest);
if (logger.isDebugEnabled()) {
logger.debug("plugin invoke result: {}", pluginPayload);
}
pluginPayloadList.add(pluginPayload);
}
return pluginPayloadList;
}
private ServerWebExchange externalFilter(ServerWebExchange exchange, GatewayFilterChain chain, PathContainer path) {
ServerHttpRequest request = exchange.getRequest();
String[] apis = rebuildExternalApi(request, request.getPath().value());
String[] apis = rebuildExternalApi(request, path.value());
GroupContext.ContextRoute contextRoute = manager.getGroupPathRoute(config.getGroup(), apis[0]);
if (contextRoute == null) {
String msg = String.format("[externalFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath());
String msg = String.format("[externalFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], path.value());
logger.warn(msg);
throw NotFoundException.create(true, msg);
}
updateRouteMetadata(exchange, contextRoute);
exchange.getAttributes().put(GatewayConstant.CONTEXT_ROUTE, contextRoute);
URI requestUri = URI.create(contextRoute.getHost() + apis[1]);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri);
// to correct path
ServerHttpRequest newRequest = request.mutate().path(apis[1]).build();
return chain.filter(exchange.mutate().request(newRequest).build());
return exchange.mutate().request(newRequest).build();
}
private Mono<Void> msFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext) {
private ServerWebExchange msFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext, PathContainer path) {
ServerHttpRequest request = exchange.getRequest();
String[] apis = rebuildMsApi(request, groupContext, request.getPath().value());
String[] apis = rebuildMsApi(request, groupContext, path.value());
logger.debug("[msFilter] path:{}, apis: {}", path, apis);
// check api
GroupContext.ContextRoute contextRoute = manager.getGroupPathRoute(config.getGroup(), apis[0]);
if (contextRoute == null) {
String msg = String.format("[msFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath());
String msg = String.format("[msFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], path.value());
logger.warn(msg);
throw NotFoundException.create(true, msg);
}
updateRouteMetadata(exchange, contextRoute);
exchange.getAttributes().put(GatewayConstant.CONTEXT_ROUTE, contextRoute);
MetadataContext metadataContext = exchange.getAttribute(
MetadataConstant.HeaderName.METADATA_CONTEXT);
if (metadataContext != null) {
// priority: namespace id(tsf) > namespace name(polaris)
String routeNamespace = StringUtils.isBlank(contextRoute.getNamespaceId()) ?
contextRoute.getNamespace() : contextRoute.getNamespaceId();
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
MetadataConstant.POLARIS_TARGET_NAMESPACE, contextRoute.getNamespace());
MetadataConstant.POLARIS_TARGET_NAMESPACE, routeNamespace);
}
URI requestUri = URI.create("lb://" + contextRoute.getService() + apis[1]);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri);
// to correct path
ServerHttpRequest newRequest = request.mutate().path(apis[1]).build();
return chain.filter(exchange.mutate().request(newRequest).build());
return exchange.mutate().request(newRequest).build();
}
/**
@ -141,28 +311,37 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
Position namespacePosition = groupContext.getPredicate().getNamespace().getPosition();
switch (namespacePosition) {
case QUERY:
matchPath.append("/").append(request.getQueryParams().getFirst(groupContext.getPredicate().getNamespace().getKey()));
break;
case HEADER:
matchPath.append("/").append(request.getHeaders().getFirst(groupContext.getPredicate().getNamespace().getKey()));
break;
case PATH:
default:
case QUERY:
matchPath.append("/")
.append(request.getQueryParams().getFirst(groupContext.getPredicate().getNamespace().getKey()));
break;
case HEADER:
matchPath.append("/")
.append(request.getHeaders().getFirst(groupContext.getPredicate().getNamespace().getKey()));
break;
case PATH:
default:
if (index < pathSegments.length - 1) {
matchPath.append("/").append(pathSegments[index++]);
break;
}
break;
}
Position servicePosition = groupContext.getPredicate().getService().getPosition();
switch (servicePosition) {
case QUERY:
matchPath.append("/").append(request.getQueryParams().getFirst(groupContext.getPredicate().getService().getKey()));
break;
case HEADER:
matchPath.append("/").append(request.getHeaders().getFirst(groupContext.getPredicate().getService().getKey()));
break;
case PATH:
default:
case QUERY:
matchPath.append("/")
.append(request.getQueryParams().getFirst(groupContext.getPredicate().getService().getKey()));
break;
case HEADER:
matchPath.append("/")
.append(request.getHeaders().getFirst(groupContext.getPredicate().getService().getKey()));
break;
case PATH:
default:
if (index < pathSegments.length - 1) {
matchPath.append("/").append(pathSegments[index++]);
}
break;
}
StringBuilder realPath = new StringBuilder();
for (int i = index; i < pathSegments.length; i++) {
@ -202,4 +381,117 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
// after RouteToRequestUrlFilter, DecodeTransferMetadataReactiveFilter
return RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER + 12;
}
private TsfGatewayRequest buildTsfGatewayRequest(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
TsfGatewayRequest gatewayRequest = new TsfGatewayRequest();
gatewayRequest.setUri(uri);
gatewayRequest.setMethod(request.getMethodValue());
gatewayRequest.setHeaders(buildTsfGatewayRequestHeader(exchange));
gatewayRequest.setParameterMap(buildTsfGatewayRequestParameter(request.getQueryParams()));
gatewayRequest.setRequestHeaders(copyRequestHeader(request));
gatewayRequest.setCookieMap(getCookieMap(request));
return gatewayRequest;
}
private Map<String, String> buildTsfGatewayRequestHeader(ServerWebExchange exchange) {
ServerHttpRequest servletRequest = exchange.getRequest();
HttpHeaders headers = servletRequest.getHeaders();
String traceId = exchange.getAttribute(HeaderName.TRACE_ID);
if (StringUtils.isEmpty(traceId)) {
traceId = IdGenerator.generate();
}
// 设置TraceId
exchange.getAttributes().put(HeaderName.TRACE_ID, traceId);
Map<String, String> headerMap = new LinkedHashMap<>();
headerMap.put(HeaderName.NODE, headers.getFirst(HeaderName.NODE));
headerMap.put(HeaderName.APP_KEY, headers.getFirst(HeaderName.APP_KEY));
headerMap.put(HeaderName.ALG, headers.getFirst(HeaderName.ALG));
headerMap.put(HeaderName.SIGN, headers.getFirst(HeaderName.SIGN));
headerMap.put(HeaderName.TRACE_ID, traceId);
headerMap.put(HeaderName.NONCE, headers.getFirst(HeaderName.NONCE));
String namespaceIdKey = HeaderName.NAMESPACE_ID;
headerMap.put(namespaceIdKey, headers.getFirst(namespaceIdKey));
return headerMap;
}
private Map<String, String[]> buildTsfGatewayRequestParameter(MultiValueMap<String, String> queryParams) {
Map<String, String[]> map = new HashMap<>();
if (queryParams != null) {
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
List<String> value = entry.getValue();
String[] newValue = new String[value.size()];
map.put(entry.getKey(), value.toArray(newValue));
}
}
return map;
}
private Map<String, String> copyRequestHeader(ServerHttpRequest request) {
Map<String, String> headerMap = new LinkedHashMap<>();
HttpHeaders headers = request.getHeaders();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
if (!entry.getValue().isEmpty()) {
headerMap.put(entry.getKey(), entry.getValue().get(0));
}
}
return headerMap;
}
private Map<String, String> getCookieMap(ServerHttpRequest request) {
Map<String, String> map = new LinkedHashMap<>();
MultiValueMap<String, HttpCookie> cookie = request.getCookies();
for (Map.Entry<String, List<HttpCookie>> entry : cookie.entrySet()) {
List<HttpCookie> httpCookies = entry.getValue();
if (!httpCookies.isEmpty()) {
map.put(entry.getKey(), httpCookies.get(0).getValue());
}
}
return map;
}
private URI addQueryParam(URI uri, Map<String, String[]> queryParam) {
StringBuilder query = new StringBuilder();
String originalQuery = uri.getRawQuery();
if (org.springframework.util.StringUtils.hasText(originalQuery)) {
query.append(originalQuery);
}
for (Map.Entry<String, String[]> entry : queryParam.entrySet()) {
String[] values = entry.getValue();
for (String value : values) {
if (query.length() == 0 || query.charAt(query.length() - 1) != '&') {
query.append('&');
}
try {
String encodedValue = URLEncoder.encode(value, "UTF-8");
query.append(entry.getKey());
query.append('=');
query.append(encodedValue);
}
catch (UnsupportedEncodingException e) {
logger.warn("value {} cannot be encoded to UTF-8", value, e);
}
}
}
try {
URI newUri = UriComponentsBuilder.fromUri(uri)
.replaceQuery(query.toString())
.build(true)
.toUri();
return newUri;
}
catch (RuntimeException ex) {
throw new IllegalStateException("Invalid URI query: \"" + query + "\"");
}
}
}

@ -17,9 +17,12 @@
package com.tencent.cloud.plugin.gateway.context;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tencent.tsf.gateway.core.model.PluginInstanceInfo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -41,6 +44,10 @@ public class ContextGatewayProperties {
private Map<String, GroupContext> groups = new HashMap<>();
private List<PathRewrite> pathRewrites = new ArrayList<>();
private List<PluginInstanceInfo> plugins;
public Map<String, RouteDefinition> getRoutes() {
return routes;
}
@ -60,6 +67,22 @@ public class ContextGatewayProperties {
this.groups = groups;
}
public List<PathRewrite> getPathRewrites() {
return pathRewrites;
}
public void setPathRewrites(List<PathRewrite> pathRewrites) {
this.pathRewrites = pathRewrites;
}
public List<PluginInstanceInfo> getPlugins() {
return plugins;
}
public void setPlugins(List<PluginInstanceInfo> plugins) {
this.plugins = plugins;
}
@Override
public String toString() {
return new ToStringCreator(this).append("routes", routes)

@ -17,9 +17,14 @@
package com.tencent.cloud.plugin.gateway.context;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
@ -27,8 +32,18 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.constant.PluginScopeType;
import com.tencent.tsf.gateway.core.constant.PluginType;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
import com.tencent.tsf.gateway.core.model.PluginArgInfo;
import com.tencent.tsf.gateway.core.model.PluginDetail;
import com.tencent.tsf.gateway.core.model.PluginInfo;
import com.tencent.tsf.gateway.core.model.PluginInstanceInfo;
import com.tencent.tsf.gateway.core.util.PluginUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import shade.polaris.org.apache.commons.beanutils.BeanUtils;
import org.springframework.util.AntPathMatcher;
@ -43,10 +58,26 @@ public class ContextGatewayPropertiesManager {
* context -> {wildcard path key -> route}.
*/
private volatile ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> groupWildcardPathRouteMap = new ConcurrentHashMap<>();
/**
* group -> plugin info.
*/
private volatile ConcurrentHashMap<String, PluginInstanceInfo> groupPluginInfoMap = new ConcurrentHashMap<>();
/**
* api id -> plugin info.
*/
private volatile ConcurrentHashMap<String, PluginInstanceInfo> apiPluginInfoMap = new ConcurrentHashMap<>();
/**
* plugin id -> plugin info.
*/
private volatile ConcurrentHashMap<String, PluginInfo> gatewayPluginInfoMap = new ConcurrentHashMap<>();
private Map<String, GroupContext> groups = new HashMap<>();
private AntPathMatcher antPathMatcher = new AntPathMatcher();
private List<PathRewrite> pathRewrites = new ArrayList<>();
private List<PluginInstanceInfo> plugins = new ArrayList<>();
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
public Map<String, Map<String, GroupContext.ContextRoute>> getGroupPathRouteMap() {
return groupPathRouteMap;
@ -56,22 +87,81 @@ public class ContextGatewayPropertiesManager {
return groupWildcardPathRouteMap;
}
public void setGroupRouteMap(Map<String, GroupContext> groups) {
public List<PathRewrite> getPathRewrites() {
return pathRewrites;
}
public void setPathRewrites(List<PathRewrite> pathRewrites) {
this.pathRewrites = pathRewrites;
}
public List<PluginInstanceInfo> getPlugins() {
return plugins;
}
public void setPlugins(List<PluginInstanceInfo> plugins) {
this.plugins = plugins;
}
public void refreshPlugins(List<PluginInstanceInfo> plugins) {
if (plugins != null) {
this.plugins = plugins;
ConcurrentHashMap<String, PluginInstanceInfo> groupPluginInfoHashMap = new ConcurrentHashMap<>();
ConcurrentHashMap<String, PluginInstanceInfo> apiPluginInfoHashMap = new ConcurrentHashMap<>();
ConcurrentHashMap<String, PluginInfo> gatewayPluginInfoHashMap = new ConcurrentHashMap<>();
for (PluginInstanceInfo pluginInstanceInfo : plugins) {
if (PluginScopeType.GROUP.getScopeType().equalsIgnoreCase(pluginInstanceInfo.getScopeType())) {
groupPluginInfoHashMap.put(pluginInstanceInfo.getScopeValue(), pluginInstanceInfo);
}
else if (PluginScopeType.API.getScopeType().equalsIgnoreCase(pluginInstanceInfo.getScopeType())) {
apiPluginInfoHashMap.put(pluginInstanceInfo.getScopeValue(), pluginInstanceInfo);
}
List<PluginDetail> gatewayPluginDetails = pluginInstanceInfo.getPluginDetails();
for (PluginDetail gatewayPluginDetail : gatewayPluginDetails) {
if (!gatewayPluginInfoHashMap.containsKey(gatewayPluginDetail.getId())) {
PluginInfo gatewayPluginInfo = buildGatewayPluginInfo(gatewayPluginDetail);
if (gatewayPluginInfo != null) {
//初始化的时候就应该检查避免运行时检查在QPS高的时候可以明显提升网关的性能
gatewayPluginInfo.check();
gatewayPluginInfoHashMap.put(gatewayPluginDetail.getId(), gatewayPluginInfo);
}
}
}
}
this.groupPluginInfoMap = groupPluginInfoHashMap;
this.apiPluginInfoMap = apiPluginInfoHashMap;
this.gatewayPluginInfoMap = gatewayPluginInfoHashMap;
}
}
public void refreshGroupRoute(Map<String, GroupContext> groups) {
ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> newGroupPathRouteMap = new ConcurrentHashMap<>();
ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> newGroupWildcardPathRouteMap = new ConcurrentHashMap<>();
if (groups != null) {
for (Map.Entry<String, GroupContext> entry : groups.entrySet()) {
GroupContext groupContext = entry.getValue();
Map<String, GroupContext.ContextRoute> newGroupPathRoute = new HashMap<>();
Map<String, GroupContext.ContextRoute> newGroupWildcardPathRoute = new HashMap<>();
for (GroupContext.ContextRoute route : entry.getValue().getRoutes()) {
for (GroupContext.ContextRoute route : groupContext.getRoutes()) {
String path = route.getPath();
// convert path parameter to group wildcard path
if (path.contains("{") && path.contains("}") || path.contains("*") || path.contains("?")) {
newGroupWildcardPathRoute.put(buildPathKey(entry.getValue(), route), route);
newGroupWildcardPathRoute.put(buildPathKey(groupContext, route), route);
}
else {
newGroupPathRoute.put(buildPathKey(entry.getValue(), route), route);
newGroupPathRoute.put(buildPathKey(groupContext, route), route);
if (StringUtils.isNotEmpty(route.getPathMapping())) {
newGroupPathRoute.put(buildPathMappingKey(groupContext, route), route);
}
}
}
newGroupWildcardPathRouteMap.put(entry.getKey(), newGroupWildcardPathRoute);
@ -141,14 +231,88 @@ public class ContextGatewayPropertiesManager {
}
}
private String buildPathKey(GroupContext groupContext, GroupContext.ContextRoute route) {
private String buildPathKey(GroupContext groupContext, GroupContext.ContextRoute route) {
switch (groupContext.getPredicate().getApiType()) {
case MS:
return String.format("%s|/%s/%s%s", route.getMethod(), route.getNamespace(), route.getService(), route.getPath());
case EXTERNAL:
default:
return String.format("%s|%s", route.getMethod(), route.getPath());
case MS:
return String.format("%s|/%s/%s%s", route.getMethod(), route.getNamespace(), route.getService(), route.getPath());
case EXTERNAL:
default:
return String.format("%s|%s", route.getMethod(), route.getPath());
}
}
private String buildPathMappingKey(GroupContext groupContext, GroupContext.ContextRoute route) {
return String.format("%s|%s", route.getMethod(), route.getPathMapping());
}
protected PluginInfo buildGatewayPluginInfo(PluginDetail gatewayPluginDetail) {
//不同插件类型,作不同方式处理
PluginInfo pluginInfo = deserializePlugin(gatewayPluginDetail, gatewayPluginDetail.getPluginArgInfos());
return pluginInfo;
}
private <T extends PluginInfo> T deserializePlugin(PluginInfo basePlugin, List<PluginArgInfo> pluginArgs) {
if (basePlugin == null || pluginArgs == null) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR);
}
//查询网关插件参数信息存储到map
Map<String, Object> pluginArgMap = new HashMap<>(pluginArgs.size());
pluginArgs.forEach((pluginArg -> pluginArgMap.put(pluginArg.getKey(), pluginArg.getValue())));
//不同插件类型,作不同方式处理
T pluginDetail;
PluginType pluginType = PluginType.getPluginType(basePlugin.getType());
if (pluginType == null) {
return null;
}
try {
//构建插件实例
Class<? extends PluginInfo> gatewayPluginClazz = pluginType.getPluginClazz();
pluginDetail = (T) gatewayPluginClazz.newInstance();
//复制插件通用属性
org.springframework.beans.BeanUtils.copyProperties(basePlugin, pluginDetail);
//复制插件私有参数,map转为bean
BeanUtils.populate(pluginDetail, pluginArgMap);
}
catch (Throwable t) {
//日志打印出原始异常
logger.error("build plugin error: plugin type is: {}", basePlugin.getType(), t);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "构建" + basePlugin.getType() + "插件异常");
}
return pluginDetail;
}
public List<PluginDetail> getPluginDetails(String group, String apiId) {
List<PluginDetail> pluginDetails = null;
PluginInstanceInfo groupPluginInfo = groupPluginInfoMap.get(group);
PluginInstanceInfo apiPluginInfo = apiPluginInfoMap.get(Optional.ofNullable(apiId).orElse(""));
Stream<PluginDetail> pluginDetailStream = null;
if (groupPluginInfo != null && apiPluginInfo != null) {
pluginDetailStream = Stream.of(groupPluginInfo.getPluginDetails(), apiPluginInfo.getPluginDetails())
.flatMap(Collection::stream);
}
else if (groupPluginInfo != null) {
pluginDetailStream = groupPluginInfo.getPluginDetails().stream();
}
else if (apiPluginInfo != null) {
pluginDetailStream = apiPluginInfo.getPluginDetails().stream();
}
// 去重、排序
if (pluginDetailStream != null) {
pluginDetails = PluginUtil.sortPluginDetail(pluginDetailStream);
}
return pluginDetails;
}
public PluginInfo getPluginInfo(String pluginId) {
return gatewayPluginInfoMap.get(Optional.ofNullable(pluginId).orElse(""));
}
}

@ -23,12 +23,18 @@ import java.util.function.Predicate;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.http.server.PathContainer;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_PATH_CONTAINER_ATTR;
public class ContextRoutePredicateFactory extends AbstractRoutePredicateFactory<ContextRoutePredicateFactory.Config> {
public ContextRoutePredicateFactory() {
private final ContextGatewayPropertiesManager manager;
public ContextRoutePredicateFactory(ContextGatewayPropertiesManager manager) {
super(Config.class);
this.manager = manager;
}
@Override
@ -36,7 +42,14 @@ public class ContextRoutePredicateFactory extends AbstractRoutePredicateFactory<
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
// TODO: do path-rewriting , put to GATEWAY_PREDICATE_PATH_CONTAINER_ATTR
if (exchange.getAttributes().containsKey(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR)) {
return true;
}
// do path-rewriting
String path = exchange.getRequest().getURI().getRawPath();
String rewritePath = PathRewriteUtil.getNewPath(manager.getPathRewrites(), path, exchange);
exchange.getAttributes()
.put(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR, PathContainer.parsePath(rewritePath));
return true;
}

@ -29,14 +29,14 @@ import org.springframework.core.env.Environment;
public class GatewayConfigChangeListener {
private ApplicationEventPublisher publisher;
private final ApplicationEventPublisher publisher;
private ContextGatewayPropertiesManager manager;
private final ContextGatewayPropertiesManager manager;
private Environment environment;
private final Environment environment;
public GatewayConfigChangeListener(ContextGatewayPropertiesManager manager,
ApplicationEventPublisher publisher, Environment environment) {
ApplicationEventPublisher publisher, Environment environment) {
this.manager = manager;
this.publisher = publisher;
this.environment = environment;
@ -47,7 +47,10 @@ public class GatewayConfigChangeListener {
Binder binder = Binder.get(environment);
BindResult<ContextGatewayProperties> result = binder.bind(ContextGatewayProperties.PREFIX, ContextGatewayProperties.class);
if (result.isBound()) {
manager.setGroupRouteMap(result.get().getGroups());
manager.refreshGroupRoute(result.get().getGroups());
manager.refreshPlugins(result.get().getPlugins());
manager.setPathRewrites(result.get().getPathRewrites());
this.publisher.publishEvent(new RefreshRoutesEvent(event));
}
}

@ -0,0 +1,474 @@
/*
* 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.plugin.gateway.context;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.ConsulRawClient;
import com.ecwid.consul.v1.QueryParams;
import com.ecwid.consul.v1.Response;
import com.ecwid.consul.v1.kv.model.GetValue;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.client.util.NamedThreadFactory;
import com.tencent.polaris.factory.config.global.ServerConnectorConfigImpl;
import com.tencent.polaris.plugins.configuration.connector.consul.ConsulConfigConstants;
import com.tencent.polaris.plugins.configuration.connector.consul.ConsulConfigContext;
import com.tencent.tsf.gateway.core.constant.AuthMode;
import com.tencent.tsf.gateway.core.constant.GatewayConstant;
import com.tencent.tsf.gateway.core.model.GatewayAllResult;
import com.tencent.tsf.gateway.core.model.Group;
import com.tencent.tsf.gateway.core.model.GroupApi;
import com.tencent.tsf.gateway.core.model.GroupApiResult;
import com.tencent.tsf.gateway.core.model.GroupResult;
import com.tencent.tsf.gateway.core.model.GroupSecret;
import com.tencent.tsf.gateway.core.model.PathRewriteResult;
import com.tencent.tsf.gateway.core.model.PathWildcardResult;
import com.tencent.tsf.gateway.core.model.PathWildcardRule;
import com.tencent.tsf.gateway.core.model.PluginInstanceInfoResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.ApplicationEventPublisher;
import static com.tencent.polaris.api.config.plugin.DefaultPlugins.SERVER_CONNECTOR_CONSUL;
public class GatewayConsulRepo {
private static final Logger logger = LoggerFactory.getLogger(GatewayConsulRepo.class);
private final ContextGatewayProperties contextGatewayProperties;
private final ContextGatewayPropertiesManager contextGatewayPropertiesManager;
private final ApplicationEventPublisher publisher;
private final AtomicLong gatewayGroupIndex = new AtomicLong(-1);
private final AtomicLong commonPluginIndex = new AtomicLong(-1);
private ConsulClient consulClient;
private ConsulConfigContext consulConfigContext;
private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2, new NamedThreadFactory("consul-gateway-watch", true));
public GatewayConsulRepo(ContextGatewayProperties contextGatewayProperties,
PolarisSDKContextManager polarisSDKContextManager,
ContextGatewayPropertiesManager contextGatewayPropertiesManager,
ApplicationEventPublisher publisher, String tsfGroupId) {
this.contextGatewayProperties = contextGatewayProperties;
this.contextGatewayPropertiesManager = contextGatewayPropertiesManager;
this.publisher = publisher;
List<ServerConnectorConfigImpl> serverConnectorConfigs = polarisSDKContextManager.getSDKContext().getConfig()
.getGlobal().getServerConnectors();
if (serverConnectorConfigs == null || serverConnectorConfigs.size() != 1 || !SERVER_CONNECTOR_CONSUL.equals(serverConnectorConfigs.get(0)
.getProtocol())) {
logger.warn("GatewayConsulRepo not enable, serverConnectorConfigs:{}", serverConnectorConfigs);
return;
}
// init consul client
ServerConnectorConfigImpl connectorConfig = serverConnectorConfigs.get(0);
if (CollectionUtils.isEmpty(connectorConfig.getAddresses())) {
logger.warn("GatewayConsulRepo not enable, connectorConfig:{}", connectorConfig);
return;
}
String address = connectorConfig.getAddresses().get(0);
int lastIndex = address.lastIndexOf(":");
String agentHost = address.substring(0, lastIndex);
int agentPort = Integer.parseInt(address.substring(lastIndex + 1));
logger.info("Connect to consul config server : [{}].", address);
consulClient = new ConsulClient(new ConsulRawClient(agentHost, agentPort));
initConsulConfigContext(connectorConfig);
Response<List<GetValue>> listResponse = consulClient.getKVValues("tsf_gateway/" + tsfGroupId, consulConfigContext.getAclToken());
gatewayGroupIndex.set(listResponse.getConsulIndex());
if (listResponse.getValue() != null) {
refreshGatewayGroupConfig(parseGroupResponse(listResponse));
}
scheduledExecutorService.scheduleAtFixedRate(() -> {
try {
Response<List<GetValue>> watchResponse = consulClient.getKVValues("tsf_gateway/" + tsfGroupId,
consulConfigContext.getAclToken(), new QueryParams(consulConfigContext.getWaitTime(), gatewayGroupIndex.get()));
// 404
if (watchResponse.getValue() == null) {
return;
}
// 200
Long newIndex = watchResponse.getConsulIndex();
if (logger.isDebugEnabled()) {
logger.debug("[watch group] index: {}, newIndex: {}", gatewayGroupIndex.get(), newIndex);
}
if (newIndex != null && !Objects.equals(gatewayGroupIndex.get(), newIndex)) {
gatewayGroupIndex.set(newIndex);
refreshGatewayGroupConfig(parseGroupResponse(watchResponse));
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
}
catch (Exception e) {
logger.warn("Gateway config watch error.", e);
try {
Thread.sleep(consulConfigContext.getConsulErrorSleep());
}
catch (Exception ex) {
logger.error("error in sleep, msg: " + e.getMessage());
}
}
}, consulConfigContext.getDelay(), consulConfigContext.getDelay(), TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(() -> {
try {
Response<List<GetValue>> watchResponse = consulClient.getKVValues("tsf_gateway/common/plugin",
consulConfigContext.getAclToken(), new QueryParams(consulConfigContext.getWaitTime(), commonPluginIndex.get()));
// 404
if (watchResponse.getValue() == null) {
return;
}
// 200
Long newIndex = watchResponse.getConsulIndex();
if (logger.isDebugEnabled()) {
logger.debug("[watch plugin] index: {}, newIndex: {}", commonPluginIndex.get(), newIndex);
}
if (newIndex != null && !Objects.equals(commonPluginIndex.get(), newIndex)) {
commonPluginIndex.set(listResponse.getConsulIndex());
parsePluginResponse(watchResponse);
}
}
catch (Exception e) {
logger.error("Gateway plugin watch error.", e);
try {
Thread.sleep(consulConfigContext.getConsulErrorSleep());
}
catch (Exception ex) {
logger.error("error in sleep, msg: " + e.getMessage());
}
}
}, consulConfigContext.getDelay(), consulConfigContext.getDelay(), TimeUnit.MILLISECONDS);
}
private void initConsulConfigContext(ServerConnectorConfigImpl connectorConfig) {
// init consul config context.
consulConfigContext = new ConsulConfigContext();
// token
String tokenStr = connectorConfig.getToken();
if (StringUtils.isNotBlank(tokenStr)) {
consulConfigContext.setAclToken(tokenStr);
}
Map<String, String> metadata = connectorConfig.getMetadata();
if (CollectionUtils.isNotEmpty(metadata)) {
String waitTimeStr = metadata.get(ConsulConfigConstants.WAIT_TIME_KEY);
if (StringUtils.isNotBlank(waitTimeStr)) {
try {
int waitTime = Integer.parseInt(waitTimeStr);
consulConfigContext.setWaitTime(waitTime);
}
catch (Exception e) {
logger.warn("wait time string {} is not integer.", waitTimeStr, e);
}
}
String delayStr = metadata.get(ConsulConfigConstants.DELAY_KEY);
if (StringUtils.isNotBlank(delayStr)) {
try {
int delay = Integer.parseInt(delayStr);
consulConfigContext.setDelay(delay);
}
catch (Exception e) {
logger.warn("delay string {} is not integer.", delayStr, e);
}
}
String consulErrorSleepStr = metadata.get(ConsulConfigConstants.CONSUL_ERROR_SLEEP_KEY);
if (StringUtils.isNotBlank(consulErrorSleepStr)) {
try {
long consulErrorSleep = Long.parseLong(consulErrorSleepStr);
consulConfigContext.setConsulErrorSleep(consulErrorSleep);
}
catch (Exception e) {
logger.warn("delay string {} is not integer.", consulErrorSleepStr, e);
}
}
}
}
private void parsePluginResponse(Response<List<GetValue>> listResponse) {
PluginInstanceInfoResult pluginInstanceInfoResult = new PluginInstanceInfoResult();
pluginInstanceInfoResult.setResult(new ArrayList<>());
for (GetValue getValue : listResponse.getValue()) {
if (logger.isDebugEnabled()) {
logger.debug("[parseResponse] Received plugin data: {}", getValue.getDecodedValue());
}
PluginInstanceInfoResult temp = JacksonUtils.deserialize(getValue.getDecodedValue(), PluginInstanceInfoResult.class);
pluginInstanceInfoResult.getResult().addAll(temp.getResult());
}
saveAsFile(JacksonUtils.serialize2Json(pluginInstanceInfoResult), GatewayConstant.PLUGIN_FILE_NAME);
contextGatewayProperties.setPlugins(pluginInstanceInfoResult.getResult());
contextGatewayPropertiesManager.refreshPlugins(contextGatewayProperties.getPlugins());
}
private GatewayAllResult parseGroupResponse(Response<List<GetValue>> listResponse) {
GroupResult groupResult = null;
GroupApiResult groupApiResult = new GroupApiResult();
groupApiResult.setResult(new ArrayList<>());
PathRewriteResult pathRewriteResult = new PathRewriteResult();
PathWildcardResult pathWildcardResult = null;
for (GetValue getValue : listResponse.getValue()) {
String key = getValue.getKey();
String[] keySplit = key.split("/");
// format example: tsf_gateway/group-xxx/group/data
if (keySplit.length < 4) {
continue;
}
switch (keySplit[2]) {
case GatewayConstant.GROUP_FILE_NAME:
if (logger.isDebugEnabled()) {
logger.debug("[parseResponse] Received group data: {}", getValue.getDecodedValue());
}
groupResult = JacksonUtils.deserialize(getValue.getDecodedValue(), GroupResult.class);
break;
case GatewayConstant.API_FILE_NAME:
if (logger.isDebugEnabled()) {
logger.debug("[parseResponse] Received api data: {}", getValue.getDecodedValue());
}
GroupApiResult temp = JacksonUtils.deserialize(getValue.getDecodedValue(), GroupApiResult.class);
groupApiResult.getResult().addAll(temp.getResult());
break;
case GatewayConstant.PATH_REWRITE_FILE_NAME:
if (logger.isDebugEnabled()) {
logger.debug("[parseResponse] Received path rewrite data: {}", getValue.getDecodedValue());
}
pathRewriteResult = JacksonUtils.deserialize(getValue.getDecodedValue(), PathRewriteResult.class);
break;
case GatewayConstant.PATH_WILDCARD_FILE_NAME:
if (logger.isDebugEnabled()) {
logger.debug("[parseResponse] Received path wildcard data: {}", getValue.getDecodedValue());
}
pathWildcardResult = JacksonUtils.deserialize(getValue.getDecodedValue(), PathWildcardResult.class);
break;
}
}
saveAsFile(JacksonUtils.serialize2Json(groupResult), GatewayConstant.GROUP_FILE_NAME);
saveAsFile(JacksonUtils.serialize2Json(groupApiResult), GatewayConstant.API_FILE_NAME);
saveAsFile(JacksonUtils.serialize2Json(pathRewriteResult), GatewayConstant.PATH_REWRITE_FILE_NAME);
saveAsFile(JacksonUtils.serialize2Json(pathWildcardResult), GatewayConstant.PATH_WILDCARD_FILE_NAME);
return new GatewayAllResult(groupResult, groupApiResult, pathRewriteResult, pathWildcardResult);
}
private void refreshGatewayGroupConfig(GatewayAllResult gatewayAllResult) {
GroupResult groupResult = gatewayAllResult.getGroupResult();
GroupApiResult groupApiResult = gatewayAllResult.getGroupApiResult();
PathRewriteResult pathRewriteResult = gatewayAllResult.getPathRewriteResult();
PathWildcardResult pathWildcardResult = gatewayAllResult.getPathWildcardResult();
Map<String, RouteDefinition> routes = new HashMap<>();
Map<String, GroupContext> groups = new HashMap<>();
if (groupResult != null && groupResult.getResult() != null) {
for (Group group : groupResult.getResult()) {
routes.put(group.getGroupId(), getRouteDefinition(group));
GroupContext.ContextPredicate contextPredicate = new GroupContext.ContextPredicate();
contextPredicate.setApiType(ApiType.valueOf(group.getGroupType().toUpperCase(Locale.ROOT)));
contextPredicate.setContext(group.getGroupContext());
contextPredicate.setNamespace(new GroupContext.ContextNamespace(
Position.valueOf(group.getNamespaceNameKeyPosition()
.toUpperCase(Locale.ROOT)), group.getNamespaceNameKey()));
contextPredicate.setService(new GroupContext.ContextService(
Position.valueOf(group.getServiceNameKeyPosition()
.toUpperCase(Locale.ROOT)), group.getServiceNameKey()));
List<GroupContext.ContextSecret> secrets = new ArrayList<>();
if (CollectionUtils.isNotEmpty(group.getSecretList())) {
for (GroupSecret groupSecret : group.getSecretList()) {
GroupContext.ContextSecret contextSecret = new GroupContext.ContextSecret();
contextSecret.setName(groupSecret.getSecretName());
contextSecret.setId(groupSecret.getSecretId());
contextSecret.setKey(groupSecret.getSecretKey());
contextSecret.setStatus(groupSecret.getStatus());
contextSecret.setExpiredTime(groupSecret.getExpiredTime());
secrets.add(contextSecret);
}
}
GroupContext.ContextAuth auth = new GroupContext.ContextAuth();
auth.setType(AuthMode.getMode(group.getAuthMode()));
auth.setSecrets(secrets);
GroupContext groupContext = new GroupContext();
groupContext.setRoutes(new ArrayList<>());
groupContext.setPredicate(contextPredicate);
groupContext.setAuth(auth);
groups.put(group.getGroupId(), groupContext);
}
}
for (GroupApi groupApi : groupApiResult.getResult()) {
GroupContext groupContext = groups.get(groupApi.getGroupId());
if (groupContext == null) {
if (logger.isDebugEnabled()) {
logger.debug("group api {} not found in group {}", groupApi.getApiId(), groupApi.getGroupId());
}
continue;
}
GroupContext.ContextRoute contextRoute = new GroupContext.ContextRoute();
contextRoute.setApiId(groupApi.getApiId());
contextRoute.setHost(groupApi.getHost());
contextRoute.setPath(groupApi.getPath());
contextRoute.setPathMapping(groupApi.getPathMapping());
contextRoute.setMethod(groupApi.getMethod());
contextRoute.setService(groupApi.getServiceName());
contextRoute.setNamespaceId(groupApi.getNamespaceId());
contextRoute.setNamespace(groupApi.getNamespaceName());
if (groupApi.getTimeout() != null) {
Map<String, String> metadata = new HashMap<>();
metadata.put("response-timeout", String.valueOf(groupApi.getTimeout()));
contextRoute.setMetadata(metadata);
}
groupContext.getRoutes().add(contextRoute);
}
if (pathWildcardResult != null && pathWildcardResult.getResult() != null) {
for (PathWildcardRule wildcardRule : pathWildcardResult.getResult()) {
GroupContext.ContextRoute contextRoute = new GroupContext.ContextRoute();
contextRoute.setPath(wildcardRule.getWildCardPath());
contextRoute.setMethod(wildcardRule.getMethod());
contextRoute.setService(wildcardRule.getServiceName());
contextRoute.setNamespaceId(wildcardRule.getNamespaceId());
contextRoute.setNamespace(wildcardRule.getNamespaceName());
if (wildcardRule.getTimeout() != null) {
Map<String, String> metadata = new HashMap<>();
metadata.put("response-timeout", String.valueOf(wildcardRule.getTimeout()));
contextRoute.setMetadata(metadata);
}
GroupContext groupContext = groups.get(wildcardRule.getGroupId());
groupContext.getRoutes().add(contextRoute);
}
}
contextGatewayProperties.setGroups(groups);
contextGatewayProperties.setRoutes(routes);
contextGatewayProperties.setPathRewrites(Optional.ofNullable(pathRewriteResult.getResult())
.orElse(new ArrayList<>()));
logger.debug("Gateway config loaded. :{}", JacksonUtils.serialize2Json(contextGatewayProperties));
contextGatewayPropertiesManager.setPathRewrites(contextGatewayProperties.getPathRewrites());
contextGatewayPropertiesManager.refreshGroupRoute(contextGatewayProperties.getGroups());
}
private void saveAsFile(String data, String type) {
try {
// 写入文件
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(getRepoStoreFile(type)));
writer.write(data);
writer.close();
}
catch (Throwable t) {
logger.warn("[tsf-gateway] save as file occur exception.", t);
}
}
private File getRepoStoreFile(String type) {
String filePath = GatewayConstant.GATEWAY_REPO_PREFIX + type + GatewayConstant.FILE_SUFFIX;
File file = new File(filePath);
try {
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
}
catch (IOException e) {
logger.warn("[tsf-gateway] load group info from local file occur error. filePath: " + filePath, e);
}
return file;
}
private RouteDefinition getRouteDefinition(Group group) {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setUri(URI.create("lb://" + group.getGroupId()));
PredicateDefinition contextPredicateDefinition = new PredicateDefinition();
contextPredicateDefinition.setName("Context");
contextPredicateDefinition.setArgs(Collections.singletonMap("group", group.getGroupId()));
PredicateDefinition pathPredicateDefinition = new PredicateDefinition();
pathPredicateDefinition.setName("Path");
pathPredicateDefinition.setArgs(Collections.singletonMap("pattern", group.getGroupContext() + "/**"));
routeDefinition.setPredicates(Arrays.asList(contextPredicateDefinition, pathPredicateDefinition));
FilterDefinition contextFilterDefinition = new FilterDefinition();
contextFilterDefinition.setName("Context");
contextFilterDefinition.setArgs(Collections.singletonMap("group", group.getGroupId()));
routeDefinition.setFilters(Collections.singletonList(contextFilterDefinition));
routeDefinition.setOrder(-1);
return routeDefinition;
}
}

@ -20,6 +20,8 @@ package com.tencent.cloud.plugin.gateway.context;
import java.util.List;
import java.util.Map;
import com.tencent.tsf.gateway.core.constant.AuthMode;
public class GroupContext {
private String comment;
@ -28,6 +30,8 @@ public class GroupContext {
private List<ContextRoute> routes;
private ContextAuth auth;
public String getComment() {
return comment;
}
@ -52,6 +56,14 @@ public class GroupContext {
this.routes = routes;
}
public ContextAuth getAuth() {
return auth;
}
public void setAuth(ContextAuth auth) {
this.auth = auth;
}
public static class ContextPredicate {
private ApiType apiType;
@ -100,6 +112,15 @@ public class GroupContext {
private String key;
public ContextNamespace() {
}
public ContextNamespace(Position position, String key) {
this.position = position;
this.key = key;
}
public Position getPosition() {
return position;
}
@ -122,6 +143,15 @@ public class GroupContext {
private String key;
public ContextService() {
}
public ContextService(Position position, String key) {
this.position = position;
this.key = key;
}
public Position getPosition() {
return position;
}
@ -146,12 +176,16 @@ public class GroupContext {
private String method;
private String apiId;
private String service;
private String host;
private String namespace;
private String namespaceId;
private Map<String, String> metadata;
public String getPath() {
@ -178,6 +212,14 @@ public class GroupContext {
this.method = method;
}
public String getApiId() {
return apiId;
}
public void setApiId(String apiId) {
this.apiId = apiId;
}
public String getService() {
return service;
}
@ -202,6 +244,14 @@ public class GroupContext {
this.namespace = namespace;
}
public String getNamespaceId() {
return namespaceId;
}
public void setNamespaceId(String namespaceId) {
this.namespaceId = namespaceId;
}
public Map<String, String> getMetadata() {
return metadata;
}
@ -210,4 +260,79 @@ public class GroupContext {
this.metadata = metadata;
}
}
public static class ContextAuth {
private AuthMode type;
private List<ContextSecret> secrets;
public AuthMode getType() {
return type;
}
public void setType(AuthMode type) {
this.type = type;
}
public List<ContextSecret> getSecrets() {
return secrets;
}
public void setSecrets(List<ContextSecret> secrets) {
this.secrets = secrets;
}
}
public static class ContextSecret {
private String name;
private String id;
private String key;
private String status;
private String expiredTime;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getExpiredTime() {
return expiredTime;
}
public void setExpiredTime(String expiredTime) {
this.expiredTime = expiredTime;
}
}
}

@ -0,0 +1,133 @@
/*
* 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.plugin.gateway.context;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* @author seanlxliu
* @date 2020/5/14
*/
public class PathRewrite {
/**
* ID.
*/
private String pathRewriteId;
/**
* ID.
*/
private String gatewayGroupId;
/**
* .
*/
private String regex;
/**
* .
*/
private String replacement;
/**
* Y: N: .
*/
private String blocked;
/**
* .
*/
private Integer order;
/**
* IDs.
*/
@JsonIgnore
private List<String> pathRewriteIds;
public String getPathRewriteId() {
return pathRewriteId;
}
public void setPathRewriteId(String pathRewriteId) {
this.pathRewriteId = pathRewriteId;
}
public String getGatewayGroupId() {
return gatewayGroupId;
}
public void setGatewayGroupId(String gatewayGroupId) {
this.gatewayGroupId = gatewayGroupId;
}
public String getRegex() {
return regex;
}
public void setRegex(String regex) {
this.regex = regex;
}
public String getReplacement() {
return replacement;
}
public void setReplacement(String replacement) {
this.replacement = replacement;
}
public String getBlocked() {
return blocked;
}
public void setBlocked(String blocked) {
this.blocked = blocked;
}
public Integer getOrder() {
return order;
}
public void setOrder(Integer order) {
this.order = order;
}
public List<String> getPathRewriteIds() {
return pathRewriteIds;
}
public void setPathRewriteIds(List<String> pathRewriteIds) {
this.pathRewriteIds = pathRewriteIds;
}
@Override
public String toString() {
return "PathRewrite{" +
"pathRewriteId='" + pathRewriteId + '\'' +
", gatewayGroupId='" + gatewayGroupId + '\'' +
", regex='" + regex + '\'' +
", replacement='" + replacement + '\'' +
", blocked='" + blocked + '\'' +
", order=" + order +
'}';
}
}

@ -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.plugin.gateway.context;
import java.util.List;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.constant.GatewayConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
public final class PathRewriteUtil {
private static final Logger logger = LoggerFactory.getLogger(PathRewriteUtil.class);
private static final String BLOCKED = "Y";
private PathRewriteUtil() {
}
public static String getNewPath(List<PathRewrite> pathRewrites, String path, ServerWebExchange exchange) {
String newPath = path;
for (PathRewrite pathRewrite : pathRewrites) {
String regex = pathRewrite.getRegex();
String replacement = pathRewrite.getReplacement();
String blockedRegex = replacement.replaceAll("\\$(\\d+|\\{[^/]+?\\})", ".*");
if (!StringUtils.isBlank(blockedRegex) && BLOCKED.equalsIgnoreCase(pathRewrite.getBlocked())
&& path.matches(blockedRegex)) {
logger.warn("[TSF-MSGW] uri is blocked by rule. uri: [{}] rule: [{}] -> [{}]",
path, regex, replacement);
ResponseStatusException exception = new ResponseStatusException(HttpStatus.BAD_REQUEST, "uri is blocked by rule. uri: [" + path + "] rule: [" + regex + "] -> [" + replacement + "]");
exchange.getAttributes().put(GatewayConstant.PATH_REWRITE_EXCEPTION, exception);
break;
}
if (!StringUtils.isBlank(regex) && !StringUtils.isBlank(replacement) && path.matches(regex)) {
newPath = path.replaceAll(regex, replacement);
logger.info("[TSF-MSGW] rewrite path [{}] to [{}], rule: [{}] -> [{}]", path, newPath, regex, replacement);
break;
}
}
return newPath;
}
}

@ -17,6 +17,8 @@
package com.tencent.cloud.plugin.gateway.context;
import com.fasterxml.jackson.annotation.JsonCreator;
public enum Position {
/**
* Path position.
@ -30,4 +32,24 @@ public enum Position {
* Header position.
*/
HEADER,
/**
* HTTP COOKIE.
*/
COOKIE,
/**
* TSF TAG Request Transformer Plugin .
*/
TSF_TAG;
@JsonCreator
public static Position fromString(String key) {
for (Position position : Position.values()) {
if (position.name().equalsIgnoreCase(key)) {
return position;
}
}
return null;
}
}

@ -0,0 +1,128 @@
/*
* 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.tsf.gateway.core;
import java.net.URI;
import java.util.Locale;
import java.util.Map;
import com.tencent.polaris.api.utils.StringUtils;
/**
* @author kysonli
* 2019/4/15 15:01
*/
public class TsfGatewayRequest {
private URI uri;
private String method;
private Map<String, String> headers;
/**
* .
*/
private Map<String, String> requestHeaders;
private Map<String, String[]> parameterMap;
private Map<String, String> cookieMap;
public URI getUri() {
return uri;
}
public void setUri(URI uri) {
this.uri = uri;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public String getHeader(String headerKey) {
return headers.get(headerKey);
}
public Map<String, String[]> getParameterMap() {
return parameterMap;
}
public void setParameterMap(Map<String, String[]> parameterMap) {
this.parameterMap = parameterMap;
}
public Map<String, String> getRequestHeaders() {
return requestHeaders;
}
public void setRequestHeaders(Map<String, String> requestHeaders) {
this.requestHeaders = requestHeaders;
}
public String getRequestHeader(String headerKey) {
return StringUtils.isNotEmpty(requestHeaders.get(headerKey)) ? requestHeaders.get(headerKey) :
requestHeaders.get(headerKey.toLowerCase(Locale.ROOT));
}
public void putRequestHeader(String headerKey, String headerValue) {
requestHeaders.put(headerKey.toLowerCase(Locale.ROOT), headerValue);
}
public Map<String, String> getCookieMap() {
return cookieMap;
}
public void setCookieMap(Map<String, String> cookieMap) {
this.cookieMap = cookieMap;
}
public String getCookie(String key) {
return cookieMap.get(key);
}
public void putCookie(String key, String value) {
cookieMap.put(key, value);
}
@Override
public String toString() {
return "TsfGatewayRequest{" +
"uri=" + uri +
", method='" + method + '\'' +
", headers=" + headers +
", requestHeaders=" + requestHeaders +
", parameterMap=" + parameterMap +
", cookieMap=" + cookieMap +
'}';
}
}

@ -0,0 +1,38 @@
/*
* 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.tsf.gateway.core.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
/**
* Compatible with old versions TSF SDK.
*
* @author Shedfree Wu
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface TsfGatewayFilter {
}

@ -0,0 +1,49 @@
/*
* 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.tsf.gateway.core.constant;
/**
* @author kysonli
* 2019/4/11 23:37
*/
public enum AuthMode {
/**
* NONE.
*/
NONE("none"),
/**
* SECRET.
*/
SECRET("secret");
private final String authMode;
AuthMode(String authMode) {
this.authMode = authMode;
}
public static AuthMode getMode(String authMode) {
for (AuthMode groupAuthType : AuthMode.values()) {
if (groupAuthType.authMode.equalsIgnoreCase(authMode)) {
return groupAuthType;
}
}
return null;
}
}

@ -0,0 +1,61 @@
/*
* 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.tsf.gateway.core.constant;
/**
* @author kysonli
* 2019/4/11 17:05
*/
public enum CommonStatus {
/**
* ENABLED.
*/
ENABLED("enabled"),
/**
* DISABLED.
*/
DISABLED("disabled");
private final String status;
CommonStatus(String status) {
this.status = status;
}
public static CommonStatus getStatus(String status) {
for (CommonStatus commonStatus : CommonStatus.values()) {
if (commonStatus.status.equals(status)) {
return commonStatus;
}
}
return null;
}
public static CommonStatus getStatus(String status, String errorMsg) {
for (CommonStatus commonStatus : CommonStatus.values()) {
if (commonStatus.status.equals(status)) {
return commonStatus;
}
}
return null;
}
public String getStatus() {
return status;
}
}

@ -0,0 +1,82 @@
/*
* 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.tsf.gateway.core.constant;
/**
* @author kysonli
* 2019/4/10 15:34
*/
public final class GatewayConstant {
/**
* CONTEXT_ROUTE.
*/
public static final String CONTEXT_ROUTE = "contextRoute";
/**
* 访API.
*/
public static final String GROUP_FILE_NAME = "group";
/**
* 访API.
*/
public static final String API_FILE_NAME = "api";
/**
* .
*/
public static final String PLUGIN_FILE_NAME = "plugin";
/**
* .
*/
public static final String PATH_REWRITE_FILE_NAME = "rewrite";
/**
* PATH_REWRITE_EXCEPTION.
*/
public static final String PATH_REWRITE_EXCEPTION = "PathRewriteException";
/**
* .
*/
public static final String PATH_WILDCARD_FILE_NAME = "wildcard";
/**
* .
*/
public static final String FILE_SUFFIX = ".json";
/**
* .
*/
public static final String GATEWAY_COMMON_DEPLOY_GROUP_ID = "common";
/**
* Spring Cloud Gateway.
*/
public static final String GATEWAY_WILDCARD_SERVICE_NAME = "wildcard_tsf_gateway";
/**
* Tsf Gateway .
*/
public static final String TSF_GATEWAY_RATELIMIT_CONTEXT_TAG = "tsf-gateway-ratelimit-context";
/**
* .
*/
private static final String GATEWAY_REPO_ROOT = System.getProperty("user.home");
/**
* Gateway .
*/
public static final String GATEWAY_REPO_PREFIX = GATEWAY_REPO_ROOT + "/tsf/gateway/";
private GatewayConstant() {
}
}

@ -0,0 +1,57 @@
/*
* 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.tsf.gateway.core.constant;
public final class HeaderName {
/**
* UNIT.
*/
public static final String UNIT = "TSF-Unit";
/**
* NAMESPACE_ID.
*/
public static final String NAMESPACE_ID = "TSF-NamespaceId";
/**
* APP_KEY.
*/
public static final String APP_KEY = "x-mg-secretid";
/**
* ALG.
*/
public static final String ALG = "x-mg-alg";
/**
* SIGN.
*/
public static final String SIGN = "x-mg-sign";
/**
* NONCE.
*/
public static final String NONCE = "x-mg-nonce";
/**
* NODE.
*/
public static final String NODE = "x-mg-node";
/**
* TRACE_ID.
*/
public static final String TRACE_ID = "x-mg-traceid";
private HeaderName() {
}
}

@ -0,0 +1,56 @@
/*
* 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.tsf.gateway.core.constant;
/**
* @author kysonli
* 2019/4/11 11:54
*/
public enum HttpMethod {
/**
* POST.
*/
POST("POST"),
/**
* GET.
*/
GET("GET"),
/**
* PUT.
*/
PUT("PUT"),
/**
* DELETE.
*/
DELETE("DELETE");
private final String httpMethod;
HttpMethod(String httpMethod) {
this.httpMethod = httpMethod;
}
public static HttpMethod getHttpMethod(String method) {
for (HttpMethod httpMethod : HttpMethod.values()) {
if (httpMethod.httpMethod.equalsIgnoreCase(method)) {
return httpMethod;
}
}
return null;
}
}

@ -0,0 +1,68 @@
/*
* 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.tsf.gateway.core.constant;
import java.util.Optional;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
/**
* @author: vmershen
* @since: 1.1.0
**/
public final class PluginConstants {
/**
* JSON.
*/
public static final Integer TAG_PLUGIN_INFO_LIST_LIMIT = 3000;
private PluginConstants() {
}
/**
* TAGtraceIdEnabled.
*/
public enum TraceIdEnabledType {
/**
* Y: enable.
*/
Y,
/**
* N: disable.
*/
N;
public static TraceIdEnabledType getTraceIdEnabledType(String enabledType) {
for (TraceIdEnabledType taskFlowEdgeType : TraceIdEnabledType.values()) {
if (taskFlowEdgeType.name().equalsIgnoreCase(enabledType)) {
return taskFlowEdgeType;
}
}
return null;
}
public static void checkValidity(String enabledType) {
TraceIdEnabledType traceIdEnabledType = getTraceIdEnabledType(enabledType);
Optional.ofNullable(traceIdEnabledType).orElseThrow(()
-> new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_INVALID, "Tag插件TraceIdEnabled类型"));
}
}
}

@ -0,0 +1,54 @@
/*
* 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.tsf.gateway.core.constant;
/**
* .
* @author seanlxliu
* @since 2019/9/10
*/
public enum PluginScopeType {
/**
* .
*/
GROUP("group"),
/**
* API.
*/
API("api");
private final String scopeType;
PluginScopeType(String scopeType) {
this.scopeType = scopeType;
}
public static PluginScopeType getScopeType(String scopeType) {
for (PluginScopeType pluginScopeType : PluginScopeType.values()) {
if (pluginScopeType.scopeType.equals(scopeType)) {
return pluginScopeType;
}
}
return null;
}
public String getScopeType() {
return scopeType;
}
}

@ -0,0 +1,99 @@
/*
* 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.tsf.gateway.core.constant;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;
import com.tencent.tsf.gateway.core.model.JwtPlugin;
import com.tencent.tsf.gateway.core.model.OAuthPlugin;
import com.tencent.tsf.gateway.core.model.PluginInfo;
import com.tencent.tsf.gateway.core.model.RequestTransformerPlugin;
import com.tencent.tsf.gateway.core.model.TagPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public enum PluginType {
/**
* ReqTransformer .
*/
REQ_TRANSFORMER("ReqTransformer", RequestTransformerPlugin.class),
/**
* OAuth .
*/
OAUTH("OAuth", OAuthPlugin.class),
/**
* Jwt .
*/
JWT("Jwt", JwtPlugin.class),
/**
* Tag .
*/
TAG("Tag", TagPlugin.class);
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final Map<String, String> pluginMap = new HashMap<>();
static {
pluginMap.put(OAUTH.name(), OAUTH.type);
pluginMap.put(JWT.name(), JWT.type);
pluginMap.put(TAG.name(), TAG.type);
}
private String type;
private Class<? extends PluginInfo> pluginClazz;
PluginType(String type, Class<? extends PluginInfo> pluginClazz) {
this.type = type;
this.pluginClazz = pluginClazz;
}
public static PluginType getPluginType(String name) {
for (PluginType pluginType : PluginType.values()) {
if (pluginType.type.equalsIgnoreCase(name)) {
return pluginType;
}
}
logger.warn("Unknown plugin type exception, please upgrade your gateway sdk");
return null;
}
public static Map<String, String> toMap() {
return pluginMap;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Class<? extends PluginInfo> getPluginClazz() {
return pluginClazz;
}
public void setPluginClazz(Class<? extends PluginInfo> pluginClazz) {
this.pluginClazz = pluginClazz;
}
}

@ -0,0 +1,69 @@
/*
* 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.tsf.gateway.core.constant;
/**
* @author kysonli
* 2019/4/11 11:42
*/
public enum TsfAlgType {
/**
* 0:HMAC_MD5.
*/
HMAC_MD5("0"),
/**
* 1:HMAC_SHA_1.
*/
HMAC_SHA_1("1"),
/**
* 2:HMAC_SHA_256.
*/
HMAC_SHA_256("2"),
/**
* 3:HMAC_SHA_512.
*/
HMAC_SHA_512("3"),
/**
* 4:HMAC_SM3.
*/
HMAC_SM3("4");
private String alg;
TsfAlgType(String code) {
this.alg = code;
}
public static TsfAlgType getSecurityCode(String code) {
for (TsfAlgType algType : TsfAlgType.values()) {
if (algType.alg.equals(code)) {
return algType;
}
}
return null;
}
public String getAlg() {
return alg;
}
public void setAlg(String alg) {
this.alg = alg;
}
}

@ -0,0 +1,142 @@
/*
* 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.tsf.gateway.core.exception;
/**
* @author kysonli
* 2019/4/9 13:30
*/
public enum TsfGatewayError {
/**
* GATEWAY_INTERNAL_ERROR.
*/
GATEWAY_INIT_ERROR(5000, "InitializeError: %s", 500),
/**
* GATEWAY_SYNC_ERROR.
*/
GATEWAY_SYNC_ERROR(5001, "SyncDataError: %s", 500),
/**
* GATEWAY_FILE_ERROR.
*/
GATEWAY_FILE_ERROR(5002, "LocalFile IO Error", 500),
/**
* GATEWAY_SERIALIZE_ERROR.
*/
GATEWAY_SERIALIZE_ERROR(5003, "SerializeError: %s", 500),
/**
* GATEWAY_AUTH_ERROR.
*/
GATEWAY_AUTH_ERROR(5004, "AuthCheckException: %s", 500),
/**
* GATEWAY_INTERNAL_ERROR.
*/
GATEWAY_INTERNAL_ERROR(5005, "Gateway Internal Error: %s", 500),
/**
* GATEWAY_PARAMETER_REQUIRED.
*/
GATEWAY_PARAMETER_REQUIRED(5006, "ParameterRequired: %s", 500),
/**
* GATEWAY_PARAMETER_INVALID.
*/
GATEWAY_PARAMETER_INVALID(5007, "ParameterInvalid: %s", 500),
/**
* GATEWAY_BAD_REQUEST.
*/
GATEWAY_BAD_REQUEST(4000, "BadRequest: %s", 400),
/**
* GATEWAY_AUTH_FAILED.
*/
GATEWAY_AUTH_FAILED(4001, "AuthCheckFailed: %s", 401),
/**
* GATEWAY_REQUEST_FORBIDDEN.
*/
GATEWAY_REQUEST_FORBIDDEN(4003, "RequestForbidden: %s", 403),
/**
* GATEWAY_REQUEST_NOT_FOUND.
*/
GATEWAY_REQUEST_NOT_FOUND(4004, "RequestNotFound: %s", 404),
/**
* GATEWAY_REQUEST_METHOD_NOT_ALLOWED.
*/
GATEWAY_REMOTE_REQUEST_INVALID(4016, "RemoteRequestInvalid: %s", 416),
/**
* GATEWAY_REMOTE_SERVER_AUTH_FAILED.
*/
GATEWAY_REMOTE_SERVER_AUTH_FAILED(4017, "RemoteServerAuthFailed: %s", 401),
/**
* GATEWAY_PLUGIN_JWT.
*/
GATEWAY_PLUGIN_JWT(4020, "JwtError: %s", 500),
/**
* GATEWAY_REMOTE_SERVER_BUSY.
*/
GATEWAY_REMOTE_SERVER_BUSY(4021, "Server is busy: %s", 401),
/**
* GATEWAY_REMOTE_SERVER_CODE_INVALID.
*/
GATEWAY_REMOTE_SERVER_CODE_INVALID(4022, "Login Code is error: %s", 401),
/**
* GATEWAY_REMOTE_SERVER_LIMIT.
*/
GATEWAY_REMOTE_SERVER_LIMIT(4023, "Server Limit : %s", 401),
/**
* GATEWAY_AUTH_EXPIRED.
*/
GATEWAY_AUTH_EXPIRED(4024, "AuthCheckExpired : %s", 401),
/**
* GATEWAY_TOO_MANY_REQUEST.
*/
GATEWAY_TOO_MANY_REQUEST(4025, "AuthCheckExpired : %s", 429);
private Integer code;
private String errMsg;
private Integer httpStatus;
TsfGatewayError(Integer code, String errMsg, Integer httpStatus) {
this.code = code;
this.errMsg = errMsg;
this.httpStatus = httpStatus;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public Integer getHttpStatus() {
return httpStatus;
}
public void setHttpStatus(Integer httpStatus) {
this.httpStatus = httpStatus;
}
}

@ -0,0 +1,44 @@
/*
* 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.tsf.gateway.core.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
/**
* @author kysonli
* 2019/4/9 12:29
*/
public class TsfGatewayException extends ResponseStatusException {
private static final long serialVersionUID = 9210499285923741857L;
private final TsfGatewayError gatewayError;
public TsfGatewayException(TsfGatewayError gatewayError, Object... args) {
this(gatewayError, null, args);
}
public TsfGatewayException(TsfGatewayError gatewayError, Throwable throwable, Object... args) {
super(HttpStatus.resolve(gatewayError.getHttpStatus()), String.format("%s", String.format(gatewayError.getErrMsg(), args)), throwable);
this.gatewayError = gatewayError;
}
public TsfGatewayError getGatewayError() {
return gatewayError;
}
}

@ -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.tsf.gateway.core.http;
/**
* @ClassName Config
* @Description TODO
* @Author vmershen
* @Date 2019/7/8 11:48
* @Version 1.0
*/
public final class HttpConfigConstant {
/**
* HTTP_CONNECT_TIMEOUT.
*/
public static final int HTTP_CONNECT_TIMEOUT = 2000;
/**
* HTTP_SOCKET_TIMEOUT.
*/
public static final int HTTP_SOCKET_TIMEOUT = 10000;
/**
* HTTP_MAX_POOL_SIZE.
*/
public static final int HTTP_MAX_POOL_SIZE = 200;
/**
* HTTP_MONITOR_INTERVAL.
*/
public static final int HTTP_MONITOR_INTERVAL = 5000;
/**
* HTTP_IDLE_TIMEOUT.
*/
public static final int HTTP_IDLE_TIMEOUT = 30000;
private HttpConfigConstant() {
}
}

@ -0,0 +1,345 @@
/*
* 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.tsf.gateway.core.http;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.invoke.MethodHandles;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.client.util.NamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import shade.polaris.org.apache.org.apache.http.HttpEntityEnclosingRequest;
import shade.polaris.org.apache.org.apache.http.HttpHost;
import shade.polaris.org.apache.org.apache.http.HttpRequest;
import shade.polaris.org.apache.org.apache.http.NoHttpResponseException;
import shade.polaris.org.apache.org.apache.http.client.HttpRequestRetryHandler;
import shade.polaris.org.apache.org.apache.http.client.config.RequestConfig;
import shade.polaris.org.apache.org.apache.http.client.methods.CloseableHttpResponse;
import shade.polaris.org.apache.org.apache.http.client.methods.HttpGet;
import shade.polaris.org.apache.org.apache.http.client.methods.HttpPost;
import shade.polaris.org.apache.org.apache.http.client.methods.HttpRequestBase;
import shade.polaris.org.apache.org.apache.http.client.protocol.HttpClientContext;
import shade.polaris.org.apache.org.apache.http.client.utils.URLEncodedUtils;
import shade.polaris.org.apache.org.apache.http.config.Registry;
import shade.polaris.org.apache.org.apache.http.config.RegistryBuilder;
import shade.polaris.org.apache.org.apache.http.conn.ConnectTimeoutException;
import shade.polaris.org.apache.org.apache.http.conn.routing.HttpRoute;
import shade.polaris.org.apache.org.apache.http.conn.socket.ConnectionSocketFactory;
import shade.polaris.org.apache.org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import shade.polaris.org.apache.org.apache.http.conn.socket.PlainConnectionSocketFactory;
import shade.polaris.org.apache.org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import shade.polaris.org.apache.org.apache.http.entity.StringEntity;
import shade.polaris.org.apache.org.apache.http.impl.client.CloseableHttpClient;
import shade.polaris.org.apache.org.apache.http.impl.client.HttpClients;
import shade.polaris.org.apache.org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import shade.polaris.org.apache.org.apache.http.message.BasicNameValuePair;
import shade.polaris.org.apache.org.apache.http.protocol.HttpContext;
import shade.polaris.org.apache.org.apache.http.util.EntityUtils;
/**
* @ClassName HttpConnectionPoolUtil
* @Description httpclient
* @Author vmershen
* @Date 2019/7/8 11:56
* @Version 1.0
*/
public final class HttpConnectionPoolUtil {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final int CONNECT_TIMEOUT = HttpConfigConstant.HTTP_CONNECT_TIMEOUT;
private static final int SOCKET_TIMEOUT = HttpConfigConstant.HTTP_SOCKET_TIMEOUT;
private static final int MAX_CONN = HttpConfigConstant.HTTP_MAX_POOL_SIZE;
private static final int MAX_PRE_ROUTE = HttpConfigConstant.HTTP_MAX_POOL_SIZE;
private static final int MAX_ROUTE = HttpConfigConstant.HTTP_MAX_POOL_SIZE;
private final static Object syncLock = new Object();
private volatile static CloseableHttpClient httpClient;
private static PoolingHttpClientConnectionManager manager;
private static ScheduledExecutorService monitorExecutor;
//程序退出时,释放资源
static {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
closeConnectionPool();
}
});
}
private HttpConnectionPoolUtil() {
}
private static void setRequestConfig(HttpRequestBase httpRequestBase, Integer timeout) {
RequestConfig requestConfig = RequestConfig
.custom()
.setConnectionRequestTimeout(timeout == null ? CONNECT_TIMEOUT : timeout)
.setConnectTimeout(timeout == null ? CONNECT_TIMEOUT : timeout)
.setSocketTimeout(timeout == null ? SOCKET_TIMEOUT : timeout)
.build();
httpRequestBase.setConfig(requestConfig);
}
public static CloseableHttpClient getHttpClient(String url) {
logger.info("url is : {}", url);
if (httpClient == null) {
//多线程下多个线程同时调用getHttpClient容易导致重复创建httpClient对象的问题,所以加上了同步锁
synchronized (syncLock) {
if (httpClient == null) {
httpClient = createHttpClient(url);
//开启监控线程,对异常和空闲线程进行关闭
monitorExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("gw-client", true));
monitorExecutor.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
//关闭异常连接
manager.closeExpiredConnections();
//关闭5s空闲的连接
manager.closeIdleConnections(HttpConfigConstant.HTTP_IDLE_TIMEOUT, TimeUnit.MILLISECONDS);
logger.debug("close expired and idle for over {} ms connection", HttpConfigConstant.HTTP_IDLE_TIMEOUT);
}
}, HttpConfigConstant.HTTP_MONITOR_INTERVAL, HttpConfigConstant.HTTP_MONITOR_INTERVAL, TimeUnit.MILLISECONDS);
}
}
}
return httpClient;
}
public static CloseableHttpClient createHttpClient(String url) {
ConnectionSocketFactory plainSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
LayeredConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", plainSocketFactory)
.register("https", sslSocketFactory).build();
manager = new PoolingHttpClientConnectionManager(registry);
//设置连接参数
manager.setMaxTotal(MAX_CONN); // 最大连接数
manager.setDefaultMaxPerRoute(MAX_PRE_ROUTE); // 路由最大连接数
//HttpHost httpHost = new HttpHost(host, port);
HttpHost httpHost = new HttpHost(url);
manager.setMaxPerRoute(new HttpRoute(httpHost), MAX_ROUTE);
//请求失败时,进行请求重试
HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
if (i > 3) {
//重试超过3次,放弃请求
logger.error("retry has more than 3 time, give up request");
return false;
}
if (e instanceof NoHttpResponseException) {
//服务器没有响应,可能是服务器断开了连接,应该重试
logger.error("receive no response from server, retry");
return true;
}
if (e instanceof SSLHandshakeException) {
// SSL握手异常
logger.error("SSL hand shake exception");
return false;
}
if (e instanceof InterruptedIOException) {
//超时
logger.error("InterruptedIOException");
return false;
}
if (e instanceof UnknownHostException) {
// 服务器不可达
logger.error("server host unknown");
return false;
}
if (e instanceof ConnectTimeoutException) {
// 连接超时
logger.error("Connection Time out");
return false;
}
if (e instanceof SSLException) {
logger.error("SSLException");
return false;
}
HttpClientContext context = HttpClientContext.adapt(httpContext);
HttpRequest request = context.getRequest();
//如果请求不是关闭连接的请求
return !(request instanceof HttpEntityEnclosingRequest);
}
};
CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).setRetryHandler(handler)
.build();
return client;
}
/**
* get,?url.
*
* @param url url
* @param paramsMap
* @param headerParamsMap header
* @param timeout
* @return
*/
public static String httpGet(String url, Map<String, String> paramsMap, Map<String, String> headerParamsMap, Integer timeout) {
// 数据必填项校验
if (StringUtils.isEmpty(url)) {
throw new IllegalArgumentException("url can not be empty");
}
CloseableHttpResponse res = null;
CloseableHttpClient httpClient = getHttpClient(url);
String result = null;
String baseUrl = buildUrl(url, paramsMap);
logger.info("get request: {}", baseUrl);
HttpGet httpGet = new HttpGet(baseUrl);
setRequestConfig(httpGet, timeout);
httpGet.addHeader("Content-type", "application/json; charset=utf-8");
httpGet.setHeader("Accept", "application/json");
// 添加传入的header参数
buildHeaderParams(httpGet, headerParamsMap);
try {
res = httpClient.execute(httpGet, HttpClientContext.create());
result = EntityUtils.toString(res.getEntity());
logger.info("get response :{}", result);
if (res.getStatusLine().getStatusCode() != 200) {
logger.info("response error: {}", result);
throw new IllegalStateException(String.format("call url: %s failed, response: code[%s] body[%s]",
baseUrl, res.getStatusLine().getStatusCode(), result));
}
return result;
}
catch (IOException e) {
logger.warn("Get request failed, url:" + baseUrl, e);
throw new RuntimeException(e);
}
finally {
closeHttpResponse(res);
}
}
private static void closeHttpResponse(CloseableHttpResponse res) {
try {
if (res != null) {
res.close();
}
}
catch (IOException e) {
logger.warn("Close httpClient failed!", e);
throw new RuntimeException(e);
}
}
/**
* .
*/
public static void closeConnectionPool() {
try {
httpClient.close();
manager.close();
monitorExecutor.shutdown();
}
catch (IOException e) {
e.printStackTrace();
}
}
public static String httpPostWithJSON(String url, Map<String, String> paramsMap, String json, Map<String, String> headerParamsMap, Integer timeout)
throws Exception {
// 数据必填项校验
if (StringUtils.isBlank(url)) {
throw new Exception("url can't be empty");
}
// 数据必填项校验
if (StringUtils.isBlank(json)) {
json = "";
}
String baseUrl = buildUrl(url, paramsMap);
String result = null;
CloseableHttpResponse res = null;
CloseableHttpClient httpClient = getHttpClient(url);
HttpPost httpPost = new HttpPost(baseUrl);
setRequestConfig(httpPost, timeout);
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.setHeader("Accept", "application/json");
// 添加传入的header参数
buildHeaderParams(httpPost, headerParamsMap);
httpPost.setEntity(new StringEntity(json, StandardCharsets.UTF_8));
logger.info("post url: {}", url);
try {
res = httpClient.execute(httpPost, HttpClientContext.create());
result = EntityUtils.toString(res.getEntity());
if (res.getStatusLine().getStatusCode() != 200 && res.getStatusLine().getStatusCode() != 201) {
logger.info("response error: {}", result);
throw new IllegalStateException(String.format("call url: %s failed, response: code[%s] body[%s]",
baseUrl, res.getStatusLine().getStatusCode(), result));
}
return result;
}
catch (IOException e) {
logger.warn("Get request failed, url:" + baseUrl, e);
throw new RuntimeException(e);
}
finally {
closeHttpResponse(res);
}
}
private static String buildUrl(String url, Map<String, String> paramsMap) {
String baseUrl = null;
if (paramsMap != null && !paramsMap.isEmpty()) {
List params = new ArrayList();
for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (value != null) {
params.add(new BasicNameValuePair(key, value));
}
}
if (url.contains("?")) {
baseUrl = url + "&" + URLEncodedUtils.format(params, "UTF-8");
}
else {
baseUrl = url + "?" + URLEncodedUtils.format(params, "UTF-8");
}
}
else {
baseUrl = url;
}
return baseUrl;
}
private static void buildHeaderParams(HttpRequestBase httpRequestBase, Map<String, String> headerParamsMap) {
if (null != headerParamsMap) {
for (Map.Entry<String, String> entry : headerParamsMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
httpRequestBase.setHeader(key, value);
}
}
}
}

@ -0,0 +1,63 @@
/*
* 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.tsf.gateway.core.model;
import java.io.Serializable;
/**
* @ClassName ClaimMapping
* @Description TODO
* @Author vmershen
* @Date 2019/7/12 16:12
* @Version 1.0
*/
public class ClaimMapping implements Serializable {
private static final long serialVersionUID = -8768365572845806890L;
//参数名称
private String parameterName;
//映射后参数名称
private String mappingParameterName;
//映射后参数名称
private String location;
public String getParameterName() {
return parameterName;
}
public void setParameterName(String parameterName) {
this.parameterName = parameterName;
}
public String getMappingParameterName() {
return mappingParameterName;
}
public void setMappingParameterName(String mappingParameterName) {
this.mappingParameterName = mappingParameterName;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}

@ -0,0 +1,70 @@
/*
* 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.tsf.gateway.core.model;
public class GatewayAllResult {
private GroupResult groupResult;
private GroupApiResult groupApiResult;
private PathRewriteResult pathRewriteResult;
private PathWildcardResult pathWildcardResult;
public GatewayAllResult(GroupResult groupResult, GroupApiResult groupApiResult,
PathRewriteResult pathRewriteResult, PathWildcardResult pathWildcardResult) {
this.groupResult = groupResult;
this.groupApiResult = groupApiResult;
this.pathRewriteResult = pathRewriteResult;
this.pathWildcardResult = pathWildcardResult;
}
public GroupResult getGroupResult() {
return groupResult;
}
public void setGroupResult(GroupResult groupResult) {
this.groupResult = groupResult;
}
public GroupApiResult getGroupApiResult() {
return groupApiResult;
}
public void setGroupApiResult(GroupApiResult groupApiResult) {
this.groupApiResult = groupApiResult;
}
public PathRewriteResult getPathRewriteResult() {
return pathRewriteResult;
}
public void setPathRewriteResult(PathRewriteResult pathRewriteResult) {
this.pathRewriteResult = pathRewriteResult;
}
public PathWildcardResult getPathWildcardResult() {
return pathWildcardResult;
}
public void setPathWildcardResult(PathWildcardResult pathWildcardResult) {
this.pathWildcardResult = pathWildcardResult;
}
}

@ -0,0 +1,89 @@
/*
* 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.tsf.gateway.core.model;
import java.io.Serializable;
import java.util.List;
/**
* @author kysonli
* 2019/4/10 11:14
*/
public class GatewayResult<T> implements Serializable {
private static final long serialVersionUID = -8391900871963415134L;
private String gatewayId;
private String gatewayName;
private String gatewayGroupId;
private Integer reversion;
private String updatedTime;
private List<T> result;
public String getGatewayId() {
return gatewayId;
}
public void setGatewayId(String gatewayId) {
this.gatewayId = gatewayId;
}
public String getGatewayName() {
return gatewayName;
}
public void setGatewayName(String gatewayName) {
this.gatewayName = gatewayName;
}
public String getGatewayGroupId() {
return gatewayGroupId;
}
public void setGatewayGroupId(String gatewayGroupId) {
this.gatewayGroupId = gatewayGroupId;
}
public Integer getReversion() {
return reversion;
}
public void setReversion(Integer reversion) {
this.reversion = reversion;
}
public String getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(String updatedTime) {
this.updatedTime = updatedTime;
}
public List<T> getResult() {
return result;
}
public void setResult(List<T> result) {
this.result = result;
}
}

@ -0,0 +1,171 @@
/*
* 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.tsf.gateway.core.model;
import java.io.Serializable;
import java.util.List;
import java.util.Locale;
import com.tencent.cloud.plugin.gateway.context.Position;
/**
* @author kysonli
* 2019/4/10 12:23
*/
public class Group implements Serializable {
private static final long serialVersionUID = -7714152839551413735L;
private String groupId;
private String groupName;
private String groupContext;
private String releaseStatus;
private String authMode;
private String groupType;
private List<GroupSecret> secretList;
/**
* key.
*/
private String namespaceNameKey;
/**
* key.
*/
private String serviceNameKey;
/**
* PathHeaderQueryPath.
*/
private String namespaceNameKeyPosition = Position.PATH.name().toLowerCase(Locale.ROOT);
/**
* PathHeaderQueryPath.
*/
private String serviceNameKeyPosition = Position.PATH.name().toLowerCase(Locale.ROOT);
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
public String getGroupContext() {
return groupContext;
}
public void setGroupContext(String groupContext) {
this.groupContext = groupContext;
}
public String getReleaseStatus() {
return releaseStatus;
}
public void setReleaseStatus(String releaseStatus) {
this.releaseStatus = releaseStatus;
}
public String getAuthMode() {
return authMode;
}
public void setAuthMode(String authMode) {
this.authMode = authMode;
}
public String getGroupType() {
return groupType;
}
public void setGroupType(String groupType) {
this.groupType = groupType;
}
public List<GroupSecret> getSecretList() {
return secretList;
}
public void setSecretList(List<GroupSecret> secretList) {
this.secretList = secretList;
}
public String getNamespaceNameKey() {
return namespaceNameKey;
}
public void setNamespaceNameKey(String namespaceNameKey) {
this.namespaceNameKey = namespaceNameKey;
}
public String getServiceNameKey() {
return serviceNameKey;
}
public void setServiceNameKey(String serviceNameKey) {
this.serviceNameKey = serviceNameKey;
}
public String getNamespaceNameKeyPosition() {
return namespaceNameKeyPosition;
}
public void setNamespaceNameKeyPosition(String namespaceNameKeyPosition) {
this.namespaceNameKeyPosition = namespaceNameKeyPosition;
}
public String getServiceNameKeyPosition() {
return serviceNameKeyPosition;
}
public void setServiceNameKeyPosition(String serviceNameKeyPosition) {
this.serviceNameKeyPosition = serviceNameKeyPosition;
}
@Override
public String toString() {
return "Group{" +
"groupId='" + groupId + '\'' +
", groupName='" + groupName + '\'' +
", groupContext='" + groupContext + '\'' +
", releaseStatus='" + releaseStatus + '\'' +
", authMode='" + authMode + '\'' +
", groupType='" + groupType + '\'' +
", secretList=" + secretList +
", namespaceNameKey='" + namespaceNameKey + '\'' +
", serviceNameKey='" + serviceNameKey + '\'' +
", namespaceNameKeyPosition='" + namespaceNameKeyPosition + '\'' +
", serviceNameKeyPosition='" + serviceNameKeyPosition + '\'' +
'}';
}
}

@ -0,0 +1,221 @@
/*
* 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.tsf.gateway.core.model;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* @author kysonli
* 2019/4/10 12:20
*/
public class GroupApi implements Serializable {
private static final long serialVersionUID = 5772774534522075805L;
private String apiId;
private String groupId;
private String path;
private String method;
private String serviceName;
private String namespaceId;
private String namespaceName;
private String releaseStatus;
private String usableStatus;
private String pathMapping;
private Integer timeout;
/**
* apihost,: http://localhost:8080.
*/
private String host;
/**
* .
*/
private String description;
/**
* API ms API external :Api.
*/
private String apiType;
/**
* RPC httpspring cloud, dubbo ...
*/
private String rpcType;
/**
* RPC json rpcType .
*/
private String rpcExt;
/**
* rpcExt.
*/
@JsonIgnore
private Object rpcExtObj;
public String getApiId() {
return apiId;
}
public void setApiId(String apiId) {
this.apiId = apiId;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getNamespaceId() {
return namespaceId;
}
public void setNamespaceId(String namespaceId) {
this.namespaceId = namespaceId;
}
public String getNamespaceName() {
return namespaceName;
}
public void setNamespaceName(String namespaceName) {
this.namespaceName = namespaceName;
}
public String getReleaseStatus() {
return releaseStatus;
}
public void setReleaseStatus(String releaseStatus) {
this.releaseStatus = releaseStatus;
}
public String getUsableStatus() {
return usableStatus;
}
public void setUsableStatus(String usableStatus) {
this.usableStatus = usableStatus;
}
public String getPathMapping() {
return pathMapping;
}
public void setPathMapping(String pathMapping) {
this.pathMapping = pathMapping;
}
public Integer getTimeout() {
return timeout;
}
public void setTimeout(Integer timeout) {
this.timeout = timeout;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getApiType() {
return apiType;
}
public void setApiType(String apiType) {
this.apiType = apiType;
}
public String getRpcType() {
return rpcType;
}
public void setRpcType(String rpcType) {
this.rpcType = rpcType;
}
public String getRpcExt() {
return rpcExt;
}
public void setRpcExt(String rpcExt) {
this.rpcExt = rpcExt;
}
public Object getRpcExtObj() {
return rpcExtObj;
}
public void setRpcExtObj(Object rpcExtObj) {
this.rpcExtObj = rpcExtObj;
}
}

@ -0,0 +1,26 @@
/*
* 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.tsf.gateway.core.model;
/**
* @author kysonli
* 2019/4/10 10:18
*/
public class GroupApiResult extends GatewayResult<GroupApi> {
private static final long serialVersionUID = -408021255097231992L;
}

@ -0,0 +1,26 @@
/*
* 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.tsf.gateway.core.model;
/**
* @author kysonli
* 2019/4/10 10:17
*/
public class GroupResult extends GatewayResult<Group> {
private static final long serialVersionUID = -7882585922852119178L;
}

@ -0,0 +1,104 @@
/*
* 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.tsf.gateway.core.model;
import java.io.Serializable;
/**
* @author kysonli
* 2019/4/10 10:18
*/
public class GroupSecret implements Serializable {
private static final long serialVersionUID = 4619764526878050813L;
private String secretId;
/**
* .
*/
private String secretKey;
/**
* .
*/
private String secretName;
/**
* API ID.
*/
private String groupId;
/**
* /.
*/
private String status;
private String expiredTime;
public String getSecretId() {
return secretId;
}
public void setSecretId(String secretId) {
this.secretId = secretId;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getSecretName() {
return secretName;
}
public void setSecretName(String secretName) {
this.secretName = secretName;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getExpiredTime() {
return expiredTime;
}
public void setExpiredTime(String expiredTime) {
this.expiredTime = expiredTime;
}
}

@ -0,0 +1,119 @@
/*
* 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.tsf.gateway.core.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
/**
* @ClassName JwtPlugin
* @Description TODO
* @Author vmershen
* @Date 2019/7/3 17:15
* @Version 1.0
*/
public class JwtPlugin extends PluginInfo {
private static final long serialVersionUID = 2238976166882026848L;
//公钥对kid
private String kid;
//公钥对
private String publicKeyJson;
//token携带位置
private String tokenBaggagePosition;
//token的key值,校验参数名称
private String tokenKeyName;
//重定向地址,非必填
private String redirectUrl;
//claim参数映射关系json
private String claimMappingJson;
public String getPublicKeyJson() {
return publicKeyJson;
}
public void setPublicKeyJson(String publicKeyJson) {
this.publicKeyJson = publicKeyJson;
}
public String getTokenBaggagePosition() {
return tokenBaggagePosition;
}
public void setTokenBaggagePosition(String tokenBaggagePosition) {
this.tokenBaggagePosition = tokenBaggagePosition;
}
public String getTokenKeyName() {
return tokenKeyName;
}
public void setTokenKeyName(String tokenKeyName) {
this.tokenKeyName = tokenKeyName;
}
public String getRedirectUrl() {
return redirectUrl;
}
public void setRedirectUrl(String redirectUrl) {
this.redirectUrl = redirectUrl;
}
public String getKid() {
return kid;
}
public void setKid(String kid) {
this.kid = kid;
}
public String getClaimMappingJson() {
return claimMappingJson;
}
public void setClaimMappingJson(String claimMappingJson) {
this.claimMappingJson = claimMappingJson;
}
@Override
@JsonIgnore
public void check() {
super.check();
if (StringUtils.isEmpty(publicKeyJson)) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "publicKeyJson");
}
if (StringUtils.isEmpty(tokenBaggagePosition) || !(tokenBaggagePosition.equalsIgnoreCase("query") ||
tokenBaggagePosition.equalsIgnoreCase("header"))) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenBaggagePosition");
}
if (StringUtils.isEmpty(tokenKeyName)) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenKeyName");
}
if (StringUtils.isEmpty(kid)) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "kid");
}
}
}

@ -0,0 +1,166 @@
/*
* 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.tsf.gateway.core.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
/**
* @ClassName OAuthPlugin
* @Description OAuthString,vo使
* @Author vmershen
* @Date 2019/7/1 11:56
* @Version 1.0
*/
public class OAuthPlugin extends PluginInfo {
private static final long serialVersionUID = -8269345448912085323L;
//验证token微服务名包含命名空间当选择微服务时才传入值值的格式为命名空间/服务名
//例如ns-xxxx/provider-demo
private String tokenAuthServiceName;
//验证token路径
//1、当选择外部请求地址时是完整url地址例如http://127.0.0.1:8080/oauth/token
//2、当选择微服务时是微服务的方法path例如/oauth/token
private String tokenAuthUrl;
//验证token请求方法
private String tokenAuthMethod;
//认证请求超时时间,单位:秒 范围:0~30
private Integer expireTime;
//重定向地址,非必填
private String redirectUrl;
//token携带位置网关取token位置与发送认证请求时token位置一致
private String tokenBaggagePosition;
//token的key值
private String tokenKeyName;
//payload的映射参数名称
private String payloadMappingName;
//payload映射到后端服务的携带位置
private String payloadMappingPosition;
public String getTokenAuthServiceName() {
return tokenAuthServiceName;
}
public void setTokenAuthServiceName(String tokenAuthServiceName) {
this.tokenAuthServiceName = tokenAuthServiceName;
}
public String getTokenAuthUrl() {
return tokenAuthUrl;
}
public void setTokenAuthUrl(String tokenAuthUrl) {
this.tokenAuthUrl = tokenAuthUrl;
}
public String getTokenAuthMethod() {
return tokenAuthMethod;
}
public void setTokenAuthMethod(String tokenAuthMethod) {
this.tokenAuthMethod = tokenAuthMethod;
}
public Integer getExpireTime() {
return expireTime;
}
public void setExpireTime(Integer expireTime) {
this.expireTime = expireTime;
}
public String getRedirectUrl() {
return redirectUrl;
}
public void setRedirectUrl(String redirectUrl) {
this.redirectUrl = redirectUrl;
}
public String getTokenBaggagePosition() {
return tokenBaggagePosition;
}
public void setTokenBaggagePosition(String tokenBaggagePosition) {
this.tokenBaggagePosition = tokenBaggagePosition;
}
public String getTokenKeyName() {
return tokenKeyName;
}
public void setTokenKeyName(String tokenKeyName) {
this.tokenKeyName = tokenKeyName;
}
public String getPayloadMappingName() {
return payloadMappingName;
}
public void setPayloadMappingName(String payloadMappingName) {
this.payloadMappingName = payloadMappingName;
}
public String getPayloadMappingPosition() {
return payloadMappingPosition;
}
public void setPayloadMappingPosition(String payloadMappingPosition) {
this.payloadMappingPosition = payloadMappingPosition;
}
@Override
@JsonIgnore
public void check() {
super.check();
if (StringUtils.isEmpty(tokenAuthUrl)) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenAuthUrl");
}
if (StringUtils.isEmpty(tokenAuthMethod) || !(tokenAuthMethod.equalsIgnoreCase("get") ||
tokenAuthMethod.equalsIgnoreCase("post"))) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenAuthMethod");
}
if (StringUtils.isEmpty(tokenBaggagePosition) || !(tokenBaggagePosition.equalsIgnoreCase("query") ||
tokenBaggagePosition.equalsIgnoreCase("header"))) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenBaggagePosition");
}
if (StringUtils.isEmpty(tokenKeyName)) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "tokenKeyName");
}
if (!StringUtils.isEmpty(payloadMappingPosition)
&& !payloadMappingPosition.equalsIgnoreCase("query")
&& !payloadMappingPosition.equalsIgnoreCase("header")) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "payloadMappingPosition");
}
if (expireTime == null || expireTime < 0 || expireTime > 30) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_INVALID, "expireTime");
}
}
}

@ -0,0 +1,51 @@
/*
* 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.tsf.gateway.core.model;
import java.io.Serializable;
/**
* @ClassName OAuthResult
* @Description TODO
* @Author vmershen
* @Date 2019/7/8 15:51
* @Version 1.0
*/
public class OAuthResult implements Serializable {
private static final long serialVersionUID = 863918261072626284L;
private Boolean result;
private String payload;
public Boolean getResult() {
return result;
}
public void setResult(Boolean result) {
this.result = result;
}
public String getPayload() {
return payload;
}
public void setPayload(String payload) {
this.payload = payload;
}
}

@ -0,0 +1,28 @@
/*
* 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.tsf.gateway.core.model;
import com.tencent.cloud.plugin.gateway.context.PathRewrite;
/**
* @author seanlxliu
* @date 2020/5/19
*/
public class PathRewriteResult extends GatewayResult<PathRewrite> {
}

@ -0,0 +1,25 @@
/*
* 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.tsf.gateway.core.model;
/**
* @author clarezzhang
*/
public class PathWildcardResult extends GatewayResult<PathWildcardRule> {
}

@ -0,0 +1,176 @@
/*
* 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.tsf.gateway.core.model;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* .
* @author clarezzhang
*/
public class PathWildcardRule {
/**
* ID.
*/
private String wildCardId;
/**
* ID.
*/
private String groupId;
/**
* .
*/
private String wildCardPath;
/**
* .
*/
private String method;
/**
* ID.
*/
private String serviceId;
/**
* .
*/
private String serviceName;
/**
* ID.
*/
private String namespaceId;
/**
* .
*/
private String namespaceName;
/**
* .
*/
private Integer timeout;
/**
* IDs.
*/
@JsonIgnore
private List<String> wildCardIds;
public String getWildCardId() {
return wildCardId;
}
public void setWildCardId(String wildCardId) {
this.wildCardId = wildCardId;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getServiceId() {
return serviceId;
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
public String getNamespaceId() {
return namespaceId;
}
public void setNamespaceId(String namespaceId) {
this.namespaceId = namespaceId;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getNamespaceName() {
return namespaceName;
}
public void setNamespaceName(String namespaceName) {
this.namespaceName = namespaceName;
}
public String getWildCardPath() {
return wildCardPath;
}
public void setWildCardPath(String wildCardPath) {
this.wildCardPath = wildCardPath;
}
public List<String> getWildCardIds() {
return wildCardIds;
}
public void setWildCardIds(List<String> wildCardIds) {
this.wildCardIds = wildCardIds;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public Integer getTimeout() {
return timeout;
}
public void setTimeout(Integer timeout) {
this.timeout = timeout;
}
@Override
public String toString() {
return "PathWildcardRule{" +
"wildCardId='" + wildCardId + '\'' +
", groupId='" + groupId + '\'' +
", wildCardPath='" + wildCardPath + '\'' +
", method='" + method + '\'' +
", serviceId='" + serviceId + '\'' +
", serviceName='" + serviceName + '\'' +
", namespaceId='" + namespaceId + '\'' +
", namespaceName='" + namespaceName + '\'' +
", wildCardIds=" + wildCardIds +
", timeout=" + timeout +
'}';
}
}

@ -0,0 +1,68 @@
/*
* 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.tsf.gateway.core.model;
import java.io.Serializable;
public class PluginArgInfo implements Serializable {
private static final long serialVersionUID = -4811191577457792816L;
//插件参数id
private String id;
//插件id
private String pluginId;
//插件属性名
private String key;
//插件属性值
private Object value;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPluginId() {
return pluginId;
}
public void setPluginId(String pluginId) {
this.pluginId = pluginId;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}

@ -0,0 +1,60 @@
/*
* 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.tsf.gateway.core.model;
import java.util.List;
import java.util.Objects;
/**
* @ClassName PluginDetail
* @Description TODO
* @Author vmershen
* @Date 2019/7/2 11:53
* @Version 1.0
*/
public class PluginDetail extends PluginInfo {
//插件参数
private List<PluginArgInfo> pluginArgInfos;
public List<PluginArgInfo> getPluginArgInfos() {
return pluginArgInfos;
}
public void setPluginArgInfos(List<PluginArgInfo> pluginArgInfos) {
this.pluginArgInfos = pluginArgInfos;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof PluginDetail)) {
return false;
}
PluginDetail that = (PluginDetail) o;
return Objects.equals(getId(), that.getId());
}
@Override
public int hashCode() {
return Objects.hash(getPluginArgInfos());
}
}

@ -0,0 +1,128 @@
/*
* 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.tsf.gateway.core.model;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
public class PluginInfo implements Serializable {
private static final long serialVersionUID = -4823276300296184640L;
//网关插件id
private String id;
//插件名称
private String name;
//插件类型
private String type;
//插件执行顺序
private Integer order;
//插件描述
private String description;
private String createdTime;
private String updatedTime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCreatedTime() {
return createdTime;
}
public void setCreatedTime(String createdTime) {
this.createdTime = createdTime;
}
public String getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(String updatedTime) {
this.updatedTime = updatedTime;
}
public Integer getOrder() {
return order;
}
public void setOrder(Integer order) {
this.order = order;
}
@JsonIgnore
public void check() {
if (StringUtils.isEmpty(name)) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "插件名称参数错误");
}
if (StringUtils.isEmpty(type)) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "插件类型参数错误");
}
}
@Override
public String toString() {
return "PluginInfo{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", type='" + type + '\'' +
", order=" + order +
", description='" + description + '\'' +
", createdTime='" + createdTime + '\'' +
", updatedTime='" + updatedTime + '\'' +
'}';
}
}

@ -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.tsf.gateway.core.model;
import java.io.Serializable;
import java.util.List;
/**
*
* @author vmershen
* @date 2019/7/5 10:41
*/
public class PluginInstanceInfo implements Serializable {
private static final long serialVersionUID = -8167195405736835024L;
/**
* APIID.
*/
private String scopeValue;
/**
* :group/api.
*/
private String scopeType;
//分组绑定的插件列表
private List<PluginDetail> pluginDetails;
public static long getSerialVersionUID() {
return serialVersionUID;
}
public String getScopeValue() {
return scopeValue;
}
public void setScopeValue(String scopeValue) {
this.scopeValue = scopeValue;
}
public List<PluginDetail> getPluginDetails() {
return pluginDetails;
}
public void setPluginDetails(List<PluginDetail> pluginDetails) {
this.pluginDetails = pluginDetails;
}
public String getScopeType() {
return scopeType;
}
public void setScopeType(String scopeType) {
this.scopeType = scopeType;
}
}

@ -0,0 +1,26 @@
/*
* 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.tsf.gateway.core.model;
/**
* @author seanlxliu
* @since 2019/9/11
*/
public class PluginInstanceInfoResult extends GatewayResult<PluginInstanceInfo> {
}

@ -0,0 +1,103 @@
/*
* 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.tsf.gateway.core.model;
import java.util.Map;
/**
* @ClassName PluginPayload
* @Description
* @Author vmershen
* @Date 2019/7/8 16:21
* @Version 1.0
*/
public class PluginPayload {
/**
* .
*/
private Map<String, String> requestHeaders;
/**
* .
*/
private Map<String, String> responseHeaders;
/**
* cookie.
*/
private Map<String, String> requestCookies;
/**
* .
*/
private Map<String, String[]> parameterMap;
private String redirectUrl;
public String getRedirectUrl() {
return redirectUrl;
}
public void setRedirectUrl(String redirectUrl) {
this.redirectUrl = redirectUrl;
}
public Map<String, String> getRequestHeaders() {
return requestHeaders;
}
public void setRequestHeaders(Map<String, String> requestHeaders) {
this.requestHeaders = requestHeaders;
}
public Map<String, String[]> getParameterMap() {
return parameterMap;
}
public void setParameterMap(Map<String, String[]> parameterMap) {
this.parameterMap = parameterMap;
}
public Map<String, String> getResponseHeaders() {
return responseHeaders;
}
public void setResponseHeaders(Map<String, String> responseHeaders) {
this.responseHeaders = responseHeaders;
}
public Map<String, String> getRequestCookies() {
return requestCookies;
}
public void setRequestCookies(Map<String, String> requestCookies) {
this.requestCookies = requestCookies;
}
@Override
public String toString() {
return "PluginPayload{" +
"requestHeaders=" + requestHeaders +
", responseHeaders=" + responseHeaders +
", requestCookies=" + requestCookies +
", parameterMap=" + parameterMap +
", redirectUrl='" + redirectUrl + '\'' +
'}';
}
}

@ -0,0 +1,85 @@
/*
* 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.tsf.gateway.core.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.type.TypeReference;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
public class RequestTransformerPlugin extends PluginInfo {
private static final long serialVersionUID = -2682243185036956532L;
/**
* JSON.
*/
private String pluginInfo;
@JsonIgnore
private RequestTransformerPluginInfo requestTransformerPluginInfo;
public String getPluginInfo() {
return pluginInfo;
}
public void setPluginInfo(String pluginInfo) {
this.pluginInfo = pluginInfo;
}
public RequestTransformerPluginInfo getRequestTransformerPluginInfo() {
return requestTransformerPluginInfo;
}
public void setRequestTransformerPluginInfo(
RequestTransformerPluginInfo requestTransformerPluginInfo) {
this.requestTransformerPluginInfo = requestTransformerPluginInfo;
}
@Override
@JsonIgnore
public void check() {
super.check();
if (StringUtils.isEmpty(pluginInfo)) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "验证插件参数");
}
try {
requestTransformerPluginInfo = JacksonUtils.deserialize(pluginInfo, new TypeReference<RequestTransformerPluginInfo>() { });
}
catch (Throwable t) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "验证插件格式");
}
int sum = 0;
for (TransformerAction action : requestTransformerPluginInfo.getActions()) {
if (action.getWeight() == null || action.getWeight() < 0) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED,
"权重值不合法:" + action.getWeight());
}
sum += action.getWeight();
}
if (sum > 100) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED,
"验证插件权重失败,当前权重总和为" + sum);
}
}
}

@ -0,0 +1,58 @@
/*
* 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.tsf.gateway.core.model;
import java.util.List;
public class RequestTransformerPluginInfo {
/**
* null .
*/
private List<TransformerTag> filters;
/**
* .
*/
private List<TransformerAction> actions;
public List<TransformerTag> getFilters() {
return filters;
}
public void setFilters(List<TransformerTag> filters) {
this.filters = filters;
}
public List<TransformerAction> getActions() {
return actions;
}
public void setActions(List<TransformerAction> actions) {
this.actions = actions;
}
@Override
public String toString() {
return "RequestTransformerPluginInfo{" +
"filters=" + filters +
", actions=" + actions +
'}';
}
}

@ -0,0 +1,61 @@
/*
* 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.tsf.gateway.core.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.constant.PluginConstants;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
import com.tencent.tsf.gateway.core.util.PluginUtil;
/**
* @author seanlxliu
* @since 2019/9/12
*/
public class TagPlugin extends PluginInfo {
private static final long serialVersionUID = -2682243185036956532L;
/**
* JSON.
*/
private String tagPluginInfoList;
public String getTagPluginInfoList() {
return tagPluginInfoList;
}
public void setTagPluginInfoList(String tagPluginInfoList) {
this.tagPluginInfoList = tagPluginInfoList;
}
@Override
@JsonIgnore
public void check() {
super.check();
if (StringUtils.isEmpty(tagPluginInfoList)) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, "验证Tag插件参数");
}
if (StringUtils.length(tagPluginInfoList) > PluginConstants.TAG_PLUGIN_INFO_LIST_LIMIT ||
!PluginUtil.predicateJsonFormat(tagPluginInfoList)) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_INVALID, "验证Tag插件参数");
}
}
}

@ -0,0 +1,81 @@
/*
* 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.tsf.gateway.core.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.tencent.cloud.plugin.gateway.context.Position;
/**
* @author seanlxliu
* @since 2019/9/12
*/
public class TagPluginInfo {
/**
* .
*/
private Position tagPosition;
/**
* .
*/
private String preTagName;
/**
* 使.
*/
private String postTagName;
/**
* TraceIdN/YN.
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
private String traceIdEnabled;
public Position getTagPosition() {
return tagPosition;
}
public void setTagPosition(Position tagPosition) {
this.tagPosition = tagPosition;
}
public String getPreTagName() {
return preTagName;
}
public void setPreTagName(String preTagName) {
this.preTagName = preTagName;
}
public String getPostTagName() {
return postTagName;
}
public void setPostTagName(String postTagName) {
this.postTagName = postTagName;
}
public String getTraceIdEnabled() {
return traceIdEnabled;
}
public void setTraceIdEnabled(String traceIdEnabled) {
this.traceIdEnabled = traceIdEnabled;
}
}

@ -0,0 +1,92 @@
/*
* 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.tsf.gateway.core.model;
import com.tencent.cloud.plugin.gateway.context.Position;
public class TransformerAction {
/**
* add editdelete.
*/
private String action;
private Position tagPosition;
/**
* .
*/
private String tagName;
/**
* tagValue .
*/
private String tagValue;
private Integer weight;
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Position getTagPosition() {
return tagPosition;
}
public void setTagPosition(Position tagPosition) {
this.tagPosition = tagPosition;
}
public String getTagName() {
return tagName;
}
public void setTagName(String tagName) {
this.tagName = tagName;
}
public String getTagValue() {
return tagValue;
}
public void setTagValue(String tagValue) {
this.tagValue = tagValue;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
@Override
public String toString() {
return "TransformerAction{" +
"action='" + action + '\'' +
", tagPosition=" + tagPosition +
", tagName='" + tagName + '\'' +
", tagValue='" + tagValue + '\'' +
", weight=" + weight +
'}';
}
}

@ -0,0 +1,41 @@
/*
* 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.tsf.gateway.core.model;
import com.tencent.cloud.plugin.gateway.context.Position;
import com.tencent.polaris.plugins.connector.consul.service.common.TagCondition;
public class TransformerTag extends TagCondition {
private Position tagPosition;
public Position getTagPosition() {
return tagPosition;
}
public void setTagPosition(Position tagPosition) {
this.tagPosition = tagPosition;
}
@Override
public String toString() {
return "TransformerTag{" +
"tagPosition=" + tagPosition +
"} " + super.toString();
}
}

@ -0,0 +1,59 @@
/*
* 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.tsf.gateway.core.plugin;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;
import com.tencent.polaris.api.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @ClassName GatewayPluginContext
* @Description TODO
* @Author vmershen
* @Date 2019/7/9 15:44
* @Version 1.0
*/
public class GatewayPluginFactory {
//根据插件type找对应的插件执行类
private static final Map<String, IGatewayPlugin> gatewayPluginExecutorMap = new HashMap<>();
private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
//获取插件业务执行器
public static IGatewayPlugin getGatewayPluginExecutor(String type) {
if (StringUtils.isEmpty(type)) {
return null;
}
return gatewayPluginExecutorMap.get(type);
}
public void putGatewayPlugin(String type, IGatewayPlugin gatewayPlugin) {
gatewayPluginExecutorMap.put(type, gatewayPlugin);
}
public void close() {
logger.info("gatewayPluginExecutorMap start clear");
gatewayPluginExecutorMap.clear();
logger.info("gatewayPluginExecutorMap clear success");
}
}

@ -0,0 +1,35 @@
/*
* 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.tsf.gateway.core.plugin;
import com.tencent.tsf.gateway.core.TsfGatewayRequest;
import com.tencent.tsf.gateway.core.model.PluginInfo;
import com.tencent.tsf.gateway.core.model.PluginPayload;
/**
* @author vmershen
* 2019/7/7 17:08
*/
public interface IGatewayPlugin<T extends PluginInfo> {
default PluginPayload invoke(PluginInfo info, TsfGatewayRequest tsfGatewayRequest) {
return startUp((T) info, tsfGatewayRequest);
}
PluginPayload startUp(T pluginInfo, TsfGatewayRequest tsfGatewayRequest);
}

@ -0,0 +1,174 @@
/*
* 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.tsf.gateway.core.plugin;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.plugin.gateway.context.Position;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.TsfGatewayRequest;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
import com.tencent.tsf.gateway.core.model.ClaimMapping;
import com.tencent.tsf.gateway.core.model.JwtPlugin;
import com.tencent.tsf.gateway.core.model.PluginPayload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import shade.polaris.org.jose4j.jwa.AlgorithmConstraints;
import shade.polaris.org.jose4j.jwk.PublicJsonWebKey;
import shade.polaris.org.jose4j.jwt.JwtClaims;
import shade.polaris.org.jose4j.jwt.MalformedClaimException;
import shade.polaris.org.jose4j.jwt.consumer.InvalidJwtException;
import shade.polaris.org.jose4j.jwt.consumer.JwtConsumer;
import shade.polaris.org.jose4j.jwt.consumer.JwtConsumerBuilder;
import shade.polaris.org.jose4j.lang.JoseException;
/**
* @ClassName JwtGatewayPlugin
* @Description TODO
* @Author vmershen
* @Date 2019/7/8 16:45
* @Version 1.0
*/
public class JwtGatewayPlugin implements IGatewayPlugin<JwtPlugin> {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Override
public PluginPayload startUp(JwtPlugin pluginInfo, TsfGatewayRequest tsfGatewayRequest) {
Map<String, String[]> parameterMap = tsfGatewayRequest.getParameterMap();
String tokenBaggagePosition = pluginInfo.getTokenBaggagePosition();
if (Position.fromString(tokenBaggagePosition) == null) {
logger.error("tokenBaggagePosition is wrong, tokenBaggagePosition: {}, tsfGatewayRequest: {}", tokenBaggagePosition, tsfGatewayRequest);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "tokenBaggagePosition is wrong");
}
String idToken;
if (Position.HEADER.equals(Position.fromString(tokenBaggagePosition))) {
idToken = tsfGatewayRequest.getRequestHeader(pluginInfo.getTokenKeyName());
}
else {
//queryParam中取
if (parameterMap.get(pluginInfo.getTokenKeyName()) == null || parameterMap.get(pluginInfo.getTokenKeyName()).length == 0) {
logger.error("idToken is empty, tsfGatewayRequest: {}", tsfGatewayRequest);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "idToken is empty");
}
idToken = parameterMap.get(pluginInfo.getTokenKeyName())[0];
}
if (StringUtils.isEmpty(idToken)) {
logger.error("idToken is empty, tsfGatewayRequest: {}", tsfGatewayRequest);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "idToken is empty");
}
//获取公钥
String publicKeyJson = pluginInfo.getPublicKeyJson();
PublicJsonWebKey jwk;
//验签
try {
jwk = PublicJsonWebKey.Factory.newPublicJwk(publicKeyJson);
jwk.setKeyId(pluginInfo.getKid());
}
catch (JoseException e) {
logger.error("Generate PublicKey Error", e);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "Generate PublicKey Error");
}
//解析idToken, 验签
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime() // the JWT must have an expiration time
.setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
.setRequireSubject() // the JWT must have a subject claim
.setVerificationKey(jwk.getPublicKey())
.setJwsAlgorithmConstraints(new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST, jwk.getAlgorithm()))
// ignore audience
.setSkipDefaultAudienceValidation()
.build(); // create the JwtConsumer instance
JwtClaims jwtClaims;
try {
jwtClaims = jwtConsumer.processToClaims(idToken);
}
catch (InvalidJwtException e) {
// Whether or not the JWT has expired being one common reason for invalidity
if (e.hasExpired()) {
try {
long expirationTime = e.getJwtContext()
.getJwtClaims()
.getExpirationTime()
.getValueInMillis();
String msg = String
.format("JWT expired at (%d)", expirationTime);
logger.error(msg);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, msg);
}
catch (MalformedClaimException e1) {
// ignore
}
}
logger.warn("Invalid JWT! tsfGatewayRequest:{}, error:{}", tsfGatewayRequest, e.getMessage());
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "Invalid JWT");
}
if (jwtClaims == null) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "Invalid JWT");
}
PluginPayload pluginPayload = new PluginPayload();
//若成功,则封装claim字段映射到后台服务
if (StringUtils.isNotEmpty(pluginInfo.getClaimMappingJson())) {
logger.info("claim json is : " + pluginInfo.getClaimMappingJson());
List<ClaimMapping> claimMappings;
try {
claimMappings = JacksonUtils.deserializeCollection(pluginInfo.getClaimMappingJson(), ClaimMapping.class);
}
catch (Throwable t) {
logger.error("[tsf-gateway] claimMappingJson deserialize data to claimMappings occur exception.", t);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_SERIALIZE_ERROR,
"claimMappingJson deserialize data to claimMappings occur exception");
}
Map<String, String[]> paramsMap = new HashMap<>();
Map<String, String> headerParamsMap = new HashMap<>();
for (ClaimMapping claimMapping : claimMappings) {
try {
String claimValue = jwtClaims.getStringClaimValue(claimMapping.getParameterName());
if (!StringUtils.isEmpty(claimValue)) {
if (Position.HEADER.equals(Position.fromString(claimMapping.getLocation()))) {
headerParamsMap.put(claimMapping.getMappingParameterName(), claimValue);
}
else if (Position.QUERY.equals(Position.fromString(claimMapping.getLocation()))) {
paramsMap.put(claimMapping.getMappingParameterName(), new String[] {claimValue});
}
}
}
catch (MalformedClaimException e) {
logger.warn("Claim mapping error, parameterName: " + claimMapping.getParameterName(), e);
}
}
pluginPayload.setRequestHeaders(headerParamsMap);
pluginPayload.setParameterMap(paramsMap);
}
return pluginPayload;
}
}

@ -0,0 +1,304 @@
/*
* 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.tsf.gateway.core.plugin;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.util.HashMap;
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 com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.plugin.gateway.context.Position;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.assembly.api.AssemblyAPI;
import com.tencent.polaris.assembly.api.pojo.TraceAttributes;
import com.tencent.tsf.gateway.core.TsfGatewayRequest;
import com.tencent.tsf.gateway.core.constant.HttpMethod;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
import com.tencent.tsf.gateway.core.http.HttpConnectionPoolUtil;
import com.tencent.tsf.gateway.core.model.OAuthPlugin;
import com.tencent.tsf.gateway.core.model.OAuthResult;
import com.tencent.tsf.gateway.core.model.PluginPayload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import static com.tencent.tsf.gateway.core.constant.GatewayConstant.GATEWAY_WILDCARD_SERVICE_NAME;
public class OAuthGatewayPlugin implements IGatewayPlugin<OAuthPlugin> {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final LoadBalancerClientFactory clientFactory;
@Value("${tsf.gateway.plugin.oauth.authorization:true}")
Boolean openAuthorization;
private final PolarisSDKContextManager polarisSDKContextManager;
public OAuthGatewayPlugin(LoadBalancerClientFactory clientFactory, PolarisSDKContextManager polarisSDKContextManager) {
this.clientFactory = clientFactory;
this.polarisSDKContextManager = polarisSDKContextManager;
}
@Override
public PluginPayload startUp(OAuthPlugin pluginInfo, TsfGatewayRequest tsfGatewayRequest) {
//OAuth认证逻辑
//构建http请求访问第三方认证服务器
Map<String, String[]> parameterMap = tsfGatewayRequest.getParameterMap();
Map<String, String> paramsMap = new HashMap<>();
Map<String, String> headerParamsMap = new HashMap<>();
String tokenAuthUrl = pluginInfo.getTokenAuthUrl();
//认证结果
String authResult = null;
//获取token携带位置
String tokenBaggagePosition = pluginInfo.getTokenBaggagePosition();
if (Position.fromString(tokenBaggagePosition) == null) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "tokenBaggagePosition is wrong");
}
if (Position.HEADER.equals(Position.fromString(tokenBaggagePosition))) {
logger.info("header is : {} = {} ", pluginInfo.getTokenKeyName(), tsfGatewayRequest.getRequestHeader(
pluginInfo.getTokenKeyName()));
if (StringUtils.isEmpty(tsfGatewayRequest.getRequestHeader(pluginInfo.getTokenKeyName()))) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "header token value is empty");
}
headerParamsMap.put(pluginInfo.getTokenKeyName(), tsfGatewayRequest.getRequestHeader(pluginInfo.getTokenKeyName()));
}
else {
//queryParam中取
if (parameterMap.get(pluginInfo.getTokenKeyName()) == null || parameterMap.get(pluginInfo.getTokenKeyName()).length == 0) {
logger.info("parameterMap is empty");
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "parameter token value is empty");
}
paramsMap.put(pluginInfo.getTokenKeyName(), parameterMap.get(pluginInfo.getTokenKeyName())[0]);
}
// 添加Oauth Authorization授权
String headerAuthorization = tsfGatewayRequest.getRequestHeader("Authorization");
if (openAuthorization && StringUtils.isNotBlank(headerAuthorization)) {
logger.info("[tsf-gateway] OAuthGatewayPlugin openAuthorization is true, put headerAuthorization:{}", headerAuthorization);
headerParamsMap.put("authorization", headerAuthorization);
}
//构建认证请求方法
String tokenAuthMethod = pluginInfo.getTokenAuthMethod();
if (HttpMethod.getHttpMethod(tokenAuthMethod) == null) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "tokenAuthMethod is wrong");
}
Integer timeout = pluginInfo.getExpireTime() != null ? 1000 * pluginInfo.getExpireTime() : null;
if (StringUtils.isNotBlank(pluginInfo.getTokenAuthServiceName())) {
// 认证地址选择的是微服务时
authResult = tokenAuthByMicroservice(pluginInfo, tsfGatewayRequest, paramsMap, headerParamsMap, tokenAuthUrl, tokenAuthMethod, timeout);
}
else {
// 认证地址选择的是外部地址时
authResult = sendAuthRequestByHttpMethod(paramsMap, headerParamsMap, tokenAuthUrl, tokenAuthMethod, timeout);
}
OAuthResult oAuthResult = null;
try {
//反序列化authResult为OAuthResult
//若反序列化失败则认为用户的认证服务器未遵守tsf微服务网关OAuth插件的使用协议导致认证失败。
oAuthResult = JacksonUtils.deserialize(authResult, OAuthResult.class);
}
catch (Throwable t) {
logger.error("[tsf-gateway] authResult deserialize data to OAuthResult occur exception.", t);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_SERIALIZE_ERROR, "oAuthResult deserialize occur error");
}
PluginPayload pluginPayload = new PluginPayload();
if (!oAuthResult.getResult()) {
//若认证失败,判断是否跳转到用户重定向页面
if (StringUtils.isNotEmpty(pluginInfo.getRedirectUrl())) {
pluginPayload.setRedirectUrl(pluginInfo.getRedirectUrl());
}
else {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "Token Auth failed");
}
}
if (StringUtils.isNotBlank(pluginInfo.getPayloadMappingName())) {
//若成功,则封装payload字段映射到后台服务
if (Position.fromString(pluginInfo.getPayloadMappingPosition()) == null) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_FAILED, "payloadMappingPosition is wrong");
}
if (Position.HEADER.equals(Position.fromString(pluginInfo.getPayloadMappingPosition()))) {
Map<String, String> headerMap = new HashMap<>();
headerMap.put(pluginInfo.getPayloadMappingName(), oAuthResult.getPayload());
pluginPayload.setRequestHeaders(headerMap);
}
else if (Position.QUERY.equals(Position.fromString(pluginInfo.getPayloadMappingPosition()))) {
//queryParam中取
Map<String, String[]> queryMap = new HashMap<>();
queryMap.put(pluginInfo.getPayloadMappingName(), new String[] {oAuthResult.getPayload()});
pluginPayload.setParameterMap(queryMap);
}
}
return pluginPayload;
}
private String tokenAuthByMicroservice(OAuthPlugin pluginInfo, TsfGatewayRequest tsfGatewayRequest,
Map<String, String> paramsMap, Map<String, String> headerParamsMap,
String tokenAuthUrl, String tokenAuthMethod, Integer timeout) {
String serviceName = pluginInfo.getTokenAuthServiceName();
String namespace = null;
String backupNamespace = MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
MetadataConstant.POLARIS_TARGET_NAMESPACE);
if (serviceName.contains("/")) {
String[] parts = serviceName.split("/");
namespace = parts[0];
serviceName = parts[1];
}
try {
MetadataContextHolder.get().putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
MetadataConstant.POLARIS_TARGET_NAMESPACE, namespace);
ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance(serviceName,
ReactorServiceInstanceLoadBalancer.class);
Response<ServiceInstance> instance = loadBalancer.choose(new DefaultRequest<>()).toFuture().get();
URI uri = tsfGatewayRequest.getUri();
if (logger.isDebugEnabled()) {
logger.debug("[tokenAuthByMicroservice] plugin:{}, tsfGatewayRequest:{}", pluginInfo, tsfGatewayRequest);
}
if (instance.getServer() == null) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_REQUEST_NOT_FOUND, "Unable to find instance for " + pluginInfo.getTokenAuthServiceName());
}
fillTracingContext(namespace, serviceName);
String newRequestUrl = uri.getScheme() + "://" + GATEWAY_WILDCARD_SERVICE_NAME + tokenAuthUrl;
URI newUri = new URI(newRequestUrl);
// 这个requestUrl是从注册中心拿到服务列表并且balance之后带有IP信息的URL
// http://127.0.0.1:8080/group1/namespace1/Consumer-demo/echo-rest/1?user=1
URI requestUrl = this.reconstructURI(new OauthDelegatingServiceInstance(instance.getServer()), newUri);
logger.debug("LoadBalancerClientFilter url chosen: " + requestUrl);
return sendAuthRequestByHttpMethod(paramsMap, headerParamsMap, requestUrl.toASCIIString(), tokenAuthMethod, timeout);
}
catch (Exception e) {
logger.error("MicroService {} Request Auth Server Error", tokenAuthMethod, e);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "MicroService {} Request Auth Server Error", tokenAuthMethod);
}
finally {
// restore context
MetadataContextHolder.get().putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
MetadataConstant.POLARIS_TARGET_NAMESPACE, backupNamespace);
}
}
protected URI reconstructURI(ServiceInstance serviceInstance, URI original) {
return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
}
private String sendAuthRequestByHttpMethod(Map<String, String> paramsMap, Map<String, String> headerParamsMap,
String tokenAuthUrl, String tokenAuthMethod, Integer timeout) {
if (HttpMethod.GET.equals(HttpMethod.getHttpMethod(tokenAuthMethod))) {
try {
return HttpConnectionPoolUtil.httpGet(tokenAuthUrl, paramsMap, headerParamsMap, timeout);
}
catch (Throwable e) {
logger.error("GET Request Auth Server Error", e);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "DirectAddress GET Request Auth Server Error");
}
}
else {
//POST请求
try {
return HttpConnectionPoolUtil.httpPostWithJSON(tokenAuthUrl, paramsMap, null, headerParamsMap, timeout);
}
catch (Throwable e) {
logger.error("GET Request Auth Server Error", e);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_AUTH_ERROR, "DirectAddress POST Request Auth Server Error");
}
}
}
void fillTracingContext(String namespace, String serviceName) {
Map<String, String> attributes = new HashMap<>();
attributes.put("net.peer.service", serviceName);
attributes.put("remote.namespace-id", namespace);
TraceAttributes traceAttributes = new TraceAttributes();
traceAttributes.setAttributes(attributes);
traceAttributes.setAttributeLocation(TraceAttributes.AttributeLocation.BAGGAGE);
AssemblyAPI assemblyAPI = polarisSDKContextManager.getAssemblyAPI();
assemblyAPI.updateTraceAttributes(traceAttributes);
}
class OauthDelegatingServiceInstance implements ServiceInstance {
final ServiceInstance delegate;
OauthDelegatingServiceInstance(ServiceInstance delegate) {
this.delegate = delegate;
}
@Override
public String getServiceId() {
return delegate.getServiceId();
}
@Override
public String getHost() {
return delegate.getHost();
}
@Override
public int getPort() {
return delegate.getPort();
}
@Override
public boolean isSecure() {
return delegate.isSecure();
}
@Override
public URI getUri() {
return delegate.getUri();
}
@Override
public Map<String, String> getMetadata() {
return delegate.getMetadata();
}
@Override
public String getScheme() {
return delegate.getScheme();
}
}
}

@ -0,0 +1,40 @@
/*
* 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.tsf.gateway.core.plugin;
import java.lang.invoke.MethodHandles;
import com.tencent.tsf.gateway.core.TsfGatewayRequest;
import com.tencent.tsf.gateway.core.model.PluginPayload;
import com.tencent.tsf.gateway.core.model.RequestTransformerPlugin;
import com.tencent.tsf.gateway.core.util.PluginUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ReqTransformerGatewayPlugin implements IGatewayPlugin<RequestTransformerPlugin> {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Override
public PluginPayload startUp(RequestTransformerPlugin plugin, TsfGatewayRequest tsfGatewayRequest) {
PluginPayload payload = new PluginPayload();
return PluginUtil.doRequestTransformer(plugin.getRequestTransformerPluginInfo(), tsfGatewayRequest, payload);
}
}

@ -0,0 +1,46 @@
/*
* 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.tsf.gateway.core.plugin;
import java.lang.invoke.MethodHandles;
import com.tencent.tsf.gateway.core.TsfGatewayRequest;
import com.tencent.tsf.gateway.core.model.PluginPayload;
import com.tencent.tsf.gateway.core.model.TagPlugin;
import com.tencent.tsf.gateway.core.util.PluginUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author seanlxliu
* @since 2019/9/12
*/
public class TagGatewayPlugin implements IGatewayPlugin<TagPlugin> {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public TagGatewayPlugin() {
}
@Override
public PluginPayload startUp(TagPlugin tagPlugin, TsfGatewayRequest tsfGatewayRequest) {
PluginPayload payload = new PluginPayload();
return PluginUtil.transferToTag(tagPlugin.getTagPluginInfoList(), tsfGatewayRequest, payload);
}
}

@ -0,0 +1,47 @@
/*
* 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.tsf.gateway.core.util;
import java.util.Map;
import org.springframework.util.CollectionUtils;
/**
* @author: vmershen
* @description:
* @create: 2020-05-23 17:41
**/
public final class CookieUtil {
private CookieUtil() {
}
public static void buildCookie(StringBuilder cookieStringBuilder, Map<String, String> requestCookieMap) {
if (!CollectionUtils.isEmpty(requestCookieMap)) {
for (Map.Entry<String, String> cookieEntry : requestCookieMap.entrySet()) {
if (cookieStringBuilder.length() == 0) {
cookieStringBuilder.append(cookieEntry.getKey() + "=" + cookieEntry.getValue());
continue;
}
cookieStringBuilder.append("; ");
cookieStringBuilder.append(cookieEntry.getKey() + "=" + cookieEntry.getValue());
}
}
}
}

@ -0,0 +1,95 @@
/*
* 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.tsf.gateway.core.util;
import java.util.UUID;
import shade.polaris.org.apache.commons.codec.binary.Base64;
/**
* @author kysonli
* 2019/2/26 16:20
*/
public final class IdGenerator {
private IdGenerator() {
}
public static String uuid() {
UUID uuid = UUID.randomUUID();
return uuid.toString();
}
public static String generate() {
UUID uuid = UUID.randomUUID();
return compressedUUID(uuid);
}
public static String generateId(String prefix) {
return generateId(prefix, '#');
}
public static String generateId(String prefix, char splitor) {
String uuid = generate();
return prefix + splitor + uuid;
}
private static String compressedUUID(UUID uuid) {
byte[] byUuid = new byte[16];
long least = uuid.getLeastSignificantBits();
long most = uuid.getMostSignificantBits();
long2bytes(most, byUuid, 0);
long2bytes(least, byUuid, 8);
return Base64.encodeBase64URLSafeString(byUuid);
}
private static void long2bytes(long value, byte[] bytes, int offset) {
for (int i = 7; i > -1; --i) {
bytes[offset++] = (byte) ((int) (value >> 8 * i & 255L));
}
}
private static String compress(String uuidString) {
UUID uuid = UUID.fromString(uuidString);
return compressedUUID(uuid);
}
private static String uncompress(String compressedUuid) {
if (compressedUuid.length() != 22) {
throw new IllegalArgumentException("Invalid uuid!");
}
else {
byte[] byUuid = Base64.decodeBase64(compressedUuid + "==");
long most = bytes2long(byUuid, 0);
long least = bytes2long(byUuid, 8);
UUID uuid = new UUID(most, least);
return uuid.toString();
}
}
private static long bytes2long(byte[] bytes, int offset) {
long value = 0L;
for (int i = 7; i > -1; --i) {
value |= ((long) bytes[offset++] & 255L) << 8 * i;
}
return value;
}
}

@ -0,0 +1,339 @@
/*
* 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.tsf.gateway.core.util;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.core.type.TypeReference;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.plugin.gateway.context.Position;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.RuleUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.plugins.connector.consul.service.common.TagCondition;
import com.tencent.polaris.plugins.connector.consul.service.common.TagConditionUtil;
import com.tencent.polaris.specification.api.v1.model.ModelProto;
import com.tencent.tsf.gateway.core.TsfGatewayRequest;
import com.tencent.tsf.gateway.core.constant.PluginConstants;
import com.tencent.tsf.gateway.core.exception.TsfGatewayError;
import com.tencent.tsf.gateway.core.exception.TsfGatewayException;
import com.tencent.tsf.gateway.core.model.PluginDetail;
import com.tencent.tsf.gateway.core.model.PluginInfo;
import com.tencent.tsf.gateway.core.model.PluginPayload;
import com.tencent.tsf.gateway.core.model.RequestTransformerPluginInfo;
import com.tencent.tsf.gateway.core.model.TagPluginInfo;
import com.tencent.tsf.gateway.core.model.TransformerAction;
import com.tencent.tsf.gateway.core.model.TransformerTag;
import io.opentelemetry.api.trace.Span;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import shade.polaris.com.google.common.base.Joiner;
import org.springframework.tsf.core.TsfContext;
import org.springframework.tsf.core.entity.Tag;
import org.springframework.util.AntPathMatcher;
public final class PluginUtil {
private static final Logger logger = LoggerFactory.getLogger(PluginUtil.class);
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
private PluginUtil() {
}
public static List<PluginDetail> sortPluginDetail(Stream<PluginDetail> pluginDetailStream) {
return pluginDetailStream.distinct().sorted(Comparator.comparing(PluginInfo::getOrder)).collect(
Collectors.toList());
}
public static List<TagPluginInfo> deserializeTagPluginInfoList(String tagPluginInfoListJson) {
if (StringUtils.length(tagPluginInfoListJson) == 0) {
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_REQUIRED, tagPluginInfoListJson);
}
List<TagPluginInfo> tagPluginInfoList = null;
try {
tagPluginInfoList = JacksonUtils.deserialize(tagPluginInfoListJson, new TypeReference<List<TagPluginInfo>>() { });
}
catch (Throwable t) {
logger.error("deserialize tagPluginInfoList : {} occur exception : {}", tagPluginInfoListJson, t);
throw new TsfGatewayException(TsfGatewayError.GATEWAY_PARAMETER_INVALID, tagPluginInfoListJson);
}
return tagPluginInfoList;
}
public static PluginPayload transferToTag(String tagPluginListJson, TsfGatewayRequest tsfGatewayRequest,
PluginPayload payload) {
List<TagPluginInfo> tagPluginInfoList = PluginUtil.deserializeTagPluginInfoList(tagPluginListJson);
if (CollectionUtils.isEmpty(tagPluginInfoList)) {
return payload;
}
Map<String, String> responseHeader = new HashMap<>();
Map<String, String> tagMap = new HashMap<>(tagPluginInfoList.size());
for (TagPluginInfo tagPluginInfo : tagPluginInfoList) {
Position tagPosition = tagPluginInfo.getTagPosition();
String preTagName = tagPluginInfo.getPreTagName();
String postTagName = tagPluginInfo.getPostTagName();
String traceIdEnabled = tagPluginInfo.getTraceIdEnabled();
switch (tagPosition) {
case QUERY:
Map<String, String[]> parameterMap = tsfGatewayRequest.getParameterMap();
String[] queryTags = parameterMap.get(preTagName);
if (queryTags != null) {
if (StringUtils.isBlank(postTagName)) {
postTagName = preTagName;
}
String tagValue = String.join(",", queryTags);
tagMap.put(postTagName, tagValue);
relateTraceId(responseHeader, postTagName, traceIdEnabled, tagValue);
}
break;
case COOKIE:
String cookieTag = tsfGatewayRequest.getCookie(preTagName);
if (cookieTag != null) {
if (StringUtils.isBlank(postTagName)) {
postTagName = preTagName;
}
tagMap.put(postTagName, cookieTag);
relateTraceId(responseHeader, postTagName, traceIdEnabled, cookieTag);
}
break;
case HEADER:
String headerTag = tsfGatewayRequest.getRequestHeader(preTagName);
if (headerTag != null) {
if (StringUtils.isBlank(postTagName)) {
postTagName = preTagName;
}
tagMap.put(postTagName, headerTag);
relateTraceId(responseHeader, postTagName, traceIdEnabled, headerTag);
}
break;
case PATH:
String path = tsfGatewayRequest.getUri().getPath();
if (StringUtils.isNotBlank(path)) {
// ensure path start with '/'
if (path.charAt(0) != '/') {
path = "/" + path;
}
String apiPath = StringUtils.substring(path, StringUtils.ordinalIndexOf(path, "/", 4));
boolean matched = antPathMatcher.match(preTagName, apiPath);
if (matched) {
Map<String, String> pathTagMap = antPathMatcher.extractUriTemplateVariables(preTagName, apiPath);
if (CollectionUtils.isNotEmpty(pathTagMap)) {
for (Map.Entry<String, String> entry : pathTagMap.entrySet()) {
String pathTag = entry.getValue();
// take the first one
if (pathTag != null) {
postTagName = StringUtils.isBlank(postTagName) ? entry.getKey() : postTagName;
tagMap.put(postTagName, pathTag);
relateTraceId(responseHeader, postTagName, traceIdEnabled, pathTag);
break;
}
}
}
}
}
break;
default:
break;
}
}
TsfContext.putTags(tagMap, Tag.ControlFlag.TRANSITIVE);
if (!responseHeader.isEmpty()) {
Map<String, String> originalResponseHeaders = payload.getResponseHeaders();
if (originalResponseHeaders == null) {
payload.setResponseHeaders(responseHeader);
}
else {
originalResponseHeaders.putAll(responseHeader);
}
}
return payload;
}
private static void relateTraceId(Map<String, String> responseHeader, String postTagName, String traceIdEnabled,
String tagValue) {
if (PluginConstants.TraceIdEnabledType.Y.equals(PluginConstants.TraceIdEnabledType
.getTraceIdEnabledType(traceIdEnabled))) {
String traceId = Span.current().getSpanContext().getTraceId();
responseHeader.put(postTagName, tagValue);
responseHeader.put("X-Tsf-TraceId", traceId);
logger.info("TraceId is {} , which is related to tag {}:{}", traceId, postTagName,
tagValue);
}
}
/**
* Tag.
* @param tagPluginInfoListJson TagJson
* @return
*/
public static boolean predicateJsonFormat(String tagPluginInfoListJson) {
if (StringUtils.length(tagPluginInfoListJson) == 0) {
return false;
}
List<TagPluginInfo> tagPluginInfos = null;
try {
tagPluginInfos = JacksonUtils.deserialize(tagPluginInfoListJson,
new TypeReference<List<TagPluginInfo>>() { });
}
catch (Throwable t) {
logger.error("deserialize tagPluginInfoList : {} occur exception : ", tagPluginInfoListJson, t);
return false;
}
tagPluginInfos.forEach(tagPluginInfo -> {
Optional.ofNullable(tagPluginInfo.getTraceIdEnabled())
.ifPresent(PluginConstants.TraceIdEnabledType::checkValidity);
});
return true;
}
public static PluginPayload doRequestTransformer(
RequestTransformerPluginInfo requestTransformerPluginInfo,
TsfGatewayRequest tsfGatewayRequest,
PluginPayload payload) {
if (requestTransformerPluginInfo == null ||
org.apache.commons.collections.CollectionUtils.isEmpty(requestTransformerPluginInfo.getActions())) {
return payload;
}
Map<String, String> requestHeader = new HashMap<>();
Map<String, String> tagMap = new HashMap<>(requestTransformerPluginInfo.getActions().size());
boolean reqMatch = true;
if (!org.apache.commons.collections.CollectionUtils.isEmpty(requestTransformerPluginInfo.getFilters())) {
for (TransformerTag transformerTag : requestTransformerPluginInfo.getFilters()) {
// 多个条件时是与关系,有一个为 false 时直接退出
if (!reqMatch) {
break;
}
switch (transformerTag.getTagPosition()) {
case QUERY:
Map<String, String[]> parameterMap = tsfGatewayRequest.getParameterMap();
String[] queryTags = parameterMap.get(transformerTag.getTagField());
String queryValue = null;
if (queryTags != null) {
queryValue = Joiner.on(",").join(queryTags);
}
reqMatch &= matchTag(transformerTag, queryValue);
break;
case COOKIE:
String cookieTag = tsfGatewayRequest.getCookie(transformerTag.getTagField());
reqMatch &= matchTag(transformerTag, cookieTag);
break;
case HEADER:
String headerTag = tsfGatewayRequest.getRequestHeader(transformerTag.getTagField());
reqMatch &= matchTag(transformerTag, headerTag);
break;
case PATH:
String path = tsfGatewayRequest.getUri().getPath();
if (StringUtils.isNotBlank(path)) {
// ensure path start with '/'
if (path.charAt(0) != '/') {
path = "/" + path;
}
String apiPath = StringUtils.substring(path, StringUtils.ordinalIndexOf(path, "/", 4));
boolean matched = antPathMatcher.match(transformerTag.getTagField(), apiPath);
if (!matched) {
logger.debug("[doRequestTransformer] path pattern not match, field:{}, apiPath:{}", transformerTag.getTagField(), apiPath);
reqMatch = false;
break;
}
Map<String, String> pathTagMap = antPathMatcher.extractUriTemplateVariables(transformerTag.getTagField(), apiPath);
if (MapUtils.isNotEmpty(pathTagMap)) {
for (Map.Entry<String, String> entry : pathTagMap.entrySet()) {
String pathTag = entry.getValue();
// take the first one
reqMatch &= matchTag(transformerTag, pathTag);
break;
}
}
}
break;
default:
break;
}
}
}
if (reqMatch) {
// 每次请求只会进入这里一次,不需要像 RequestRouteDestRandomValueUtil 那样弄个 thread local 变量
// 规则权重总和可能小于 100但按 100 来随机
int random = (int) (Math.random() * 100);
int current = 0;
for (TransformerAction action : requestTransformerPluginInfo.getActions()) {
current += action.getWeight();
// match
if (random < current) {
switch (action.getTagPosition()) {
case TSF_TAG:
tagMap.put(action.getTagName(), action.getTagValue());
break;
case HEADER:
requestHeader.put(action.getTagName(), action.getTagValue());
break;
default:
break;
}
// 命中权重,退出
break;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("[doRequestTransformer] put tags:{}, reqMatch:{}", tagMap, reqMatch);
}
TsfContext.putTags(tagMap, Tag.ControlFlag.TRANSITIVE);
if (!requestHeader.isEmpty()) {
// request header 是网关向后请求时携带的response header 是后端业务返回经过网关时携带的
Map<String, String> originalRequestHeaders = payload.getRequestHeaders();
if (originalRequestHeaders == null) {
payload.setRequestHeaders(requestHeader);
}
else {
originalRequestHeaders.putAll(requestHeader);
}
}
return payload;
}
public static boolean matchTag(TagCondition transformerTag, String targetTagValue) {
ModelProto.MatchString.MatchStringType matchType = TagConditionUtil.parseMatchStringType(transformerTag);
return RuleUtils.matchStringValue(matchType, targetTagValue, transformerTag.getTagValue());
}
}

@ -0,0 +1,73 @@
/*
* 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.tsf.gateway.core.util;
import com.tencent.tsf.gateway.core.constant.TsfAlgType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import shade.polaris.org.apache.commons.codec.binary.Base64;
import shade.polaris.org.apache.commons.codec.digest.HmacUtils;
/**
* @author kysonli
* 2019/2/26 16:06
*/
public final class TsfSignUtil {
private static final Logger logger = LoggerFactory.getLogger(TsfSignUtil.class);
private TsfSignUtil() {
}
/**
* .
*
* @param nonce
* @param secretId ID
* @param secretKey
* @param algType {@link TsfAlgType}
*/
@SuppressWarnings("deprecation")
public static String generate(String nonce, String secretId, String secretKey, TsfAlgType algType) {
String digestValue = nonce + secretId + secretKey;
byte[] serverSignBytes;
switch (algType) {
case HMAC_MD5:
serverSignBytes = HmacUtils.hmacMd5(secretKey, digestValue);
break;
case HMAC_SHA_1:
serverSignBytes = HmacUtils.hmacSha1(secretKey, digestValue);
break;
case HMAC_SHA_256:
serverSignBytes = HmacUtils.hmacSha256(secretKey, digestValue);
break;
case HMAC_SHA_512:
serverSignBytes = HmacUtils.hmacSha512(secretKey, digestValue);
break;
default:
throw new UnsupportedOperationException("不支持的鉴权算法: " + algType);
}
String signValue = Base64.encodeBase64String(serverSignBytes);
if (logger.isDebugEnabled()) {
logger.debug("签名明文:{},签名密文:{}", digestValue, signValue);
}
return signValue;
}
}

@ -0,0 +1,50 @@
/*
* 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.tsf.gateway.scg;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
/**
* Compatible with old versions TSF SDK.
*
* @author Shedfree Wu
*/
public abstract class AbstractTsfGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (shouldFilter(exchange, chain)) {
return doFilter(exchange, chain);
}
else {
return chain.filter(exchange);
}
}
@Override
abstract public int getOrder();
abstract public boolean shouldFilter(ServerWebExchange exchange, GatewayFilterChain chain);
abstract public Mono<Void> doFilter(ServerWebExchange exchange, GatewayFilterChain chain);
}

@ -35,6 +35,7 @@ import org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAut
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -139,5 +140,10 @@ class GatewayPluginAutoConfigurationTest {
PolarisConfigProperties polarisConfigProperties() {
return new PolarisConfigProperties();
}
@Bean
LoadBalancerClientFactory loadBalancerClientFactory() {
return mock(LoadBalancerClientFactory.class);
}
}
}

@ -17,7 +17,6 @@
package com.tencent.cloud.plugin.gateway;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
@ -28,6 +27,7 @@ import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -62,7 +62,7 @@ public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessorTest {
@Test
void testGetOrder() {
int order = processor.getOrder();
Assertions.assertEquals(PolarisReactiveLoadBalancerClientFilterBeanPostProcessor.ORDER, order);
assertThat(order).isEqualTo(PolarisReactiveLoadBalancerClientFilterBeanPostProcessor.ORDER);
}
@Test
@ -75,7 +75,7 @@ public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessorTest {
Object result = processor.postProcessAfterInitialization(originalInterceptor, beanName);
// Assert
Assertions.assertInstanceOf(PolarisReactiveLoadBalancerClientFilter.class, result);
assertThat(result).isInstanceOf(PolarisReactiveLoadBalancerClientFilter.class);
}
@Test
@ -88,7 +88,7 @@ public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessorTest {
Object result = processor.postProcessAfterInitialization(originalBean, beanName);
// Assert
Assertions.assertSame(originalBean, result);
assertThat(result).isSameAs(originalBean);
}
}

@ -20,7 +20,6 @@ package com.tencent.cloud.plugin.gateway;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -44,6 +43,7 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class PolarisReactiveLoadBalancerClientFilterTest {
private final MetadataContext testContext = new MetadataContext();
@Mock
private LoadBalancerClientFactory clientFactory;
@Mock
@ -54,9 +54,7 @@ class PolarisReactiveLoadBalancerClientFilterTest {
private ServerWebExchange exchange;
@Mock
private GatewayFilterChain chain;
private PolarisReactiveLoadBalancerClientFilter polarisFilter;
private final MetadataContext testContext = new MetadataContext();
@BeforeEach
void setUp() {
@ -82,11 +80,11 @@ class PolarisReactiveLoadBalancerClientFilterTest {
when(originalFilter.filter(exchange, chain))
.thenReturn(Mono.empty());
MetadataContext before = MetadataContextHolder.get();
Assertions.assertNotEquals(testContext, before);
assertThat(before).isNotEqualTo(testContext);
// Act
polarisFilter.filter(exchange, chain);
MetadataContext after = MetadataContextHolder.get();
Assertions.assertEquals(testContext, after);
assertThat(after).isEqualTo(testContext);
}
@Test
@ -102,6 +100,6 @@ class PolarisReactiveLoadBalancerClientFilterTest {
MetadataContext after = MetadataContextHolder.get();
// Assert
Assertions.assertEquals(before, after);
assertThat(after).isEqualTo(before);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save