feat: support gateway context, feign eager-load support default value. (#1496)
Co-authored-by: Haotian Zhang <928016560@qq.com>pull/1498/head
parent
4aeca717cd
commit
b12c31619c
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.common.util;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author heihuliliu
|
||||
*/
|
||||
public final class FeignUtil {
|
||||
|
||||
/**
|
||||
* Feign client spec.
|
||||
*/
|
||||
public static final String FEIGN_CLIENT_SPECIF = ".FeignClientSpecification";
|
||||
|
||||
/**
|
||||
* Default Feign client spec.
|
||||
*/
|
||||
public static final String FEIGN_CLIENT_DEFAULT = "default.";
|
||||
|
||||
/**
|
||||
* regular expression that parses ${xxx} .
|
||||
*/
|
||||
public static final String REGEX = "^[$][{](.*)[}]$";
|
||||
|
||||
/**
|
||||
* replacement of ${xxx}.
|
||||
*/
|
||||
public static final String REPLACEMENT = "$1";
|
||||
|
||||
private FeignUtil() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO If @FeignClient specifies contextId, the service name will not be obtained correctly, but the contextId will be obtained.
|
||||
*
|
||||
* @param name feign name.
|
||||
* @param context application context.
|
||||
* @return service name.
|
||||
*/
|
||||
public static String analysisFeignName(String name, ApplicationContext context) {
|
||||
String feignName = "";
|
||||
String feignPath = name.substring(0, name.indexOf(FEIGN_CLIENT_SPECIF));
|
||||
// Handle the case where the service name is a variable
|
||||
if (feignPath.matches(REGEX)) {
|
||||
feignPath = context.getEnvironment().getProperty(feignPath.replaceAll(REGEX, REPLACEMENT));
|
||||
}
|
||||
if (StringUtils.hasText(feignPath)) {
|
||||
// The case of multi-level paths
|
||||
String[] feignNames = feignPath.split("/");
|
||||
if (feignNames.length > 1) {
|
||||
for (int i = 0; i < feignNames.length; i++) {
|
||||
if (StringUtils.hasText(feignNames[i])) {
|
||||
feignName = feignNames[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
feignName = feignNames[0];
|
||||
}
|
||||
}
|
||||
return feignName;
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway;
|
||||
|
||||
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.polaris.config.ConditionalOnPolarisConfigEnabled;
|
||||
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
|
||||
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
|
||||
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Auto configuration for spring cloud gateway plugins.
|
||||
* @author lepdou 2022-07-06
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnPolarisEnabled
|
||||
@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.enabled", matchIfMissing = true)
|
||||
public class GatewayPluginAutoConfiguration {
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.context.enabled", matchIfMissing = true)
|
||||
@ConditionalOnPolarisConfigEnabled
|
||||
@ConditionalOnClass(GlobalFilter.class)
|
||||
@Import(ContextGatewayProperties.class)
|
||||
public static class ContextPluginConfiguration {
|
||||
|
||||
@Value("${spring.cloud.polaris.discovery.eager-load.enabled:#{'true'}}")
|
||||
private boolean commonEagerLoadEnabled;
|
||||
|
||||
@Value("${spring.cloud.polaris.discovery.eager-load.gateway.enabled:#{'true'}}")
|
||||
private boolean gatewayEagerLoadEnabled;
|
||||
|
||||
@Bean
|
||||
public ContextGatewayFilterFactory contextGatewayFilterFactory(ContextGatewayPropertiesManager contextGatewayPropertiesManager) {
|
||||
return new ContextGatewayFilterFactory(contextGatewayPropertiesManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ContextPropertiesRouteDefinitionLocator contextPropertiesRouteDefinitionLocator(ContextGatewayProperties properties) {
|
||||
return new ContextPropertiesRouteDefinitionLocator(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ContextRoutePredicateFactory contextServiceRoutePredicateFactory() {
|
||||
return new ContextRoutePredicateFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ContextGatewayPropertiesManager contextGatewayPropertiesManager(ContextGatewayProperties properties,
|
||||
@Autowired(required = false) PolarisDiscoveryClient polarisDiscoveryClient,
|
||||
@Autowired(required = false) PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
|
||||
ContextGatewayPropertiesManager contextGatewayPropertiesManager = new ContextGatewayPropertiesManager();
|
||||
contextGatewayPropertiesManager.setGroupRouteMap(properties.getGroups());
|
||||
if (commonEagerLoadEnabled && gatewayEagerLoadEnabled) {
|
||||
contextGatewayPropertiesManager.eagerLoad(polarisDiscoveryClient, polarisReactiveDiscoveryClient);
|
||||
}
|
||||
return contextGatewayPropertiesManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GatewayRegistrationCustomizer gatewayRegistrationCustomizer() {
|
||||
return new GatewayRegistrationCustomizer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GatewayConfigChangeListener gatewayConfigChangeListener(ContextGatewayPropertiesManager manager,
|
||||
ApplicationEventPublisher publisher, Environment environment) {
|
||||
return new GatewayConfigChangeListener(manager, publisher, environment);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PolarisReactiveLoadBalancerClientFilterBeanPostProcessor polarisReactiveLoadBalancerClientFilterBeanPostProcessor(
|
||||
ApplicationContext applicationContext) {
|
||||
return new PolarisReactiveLoadBalancerClientFilterBeanPostProcessor(applicationContext);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway;
|
||||
|
||||
import com.tencent.cloud.polaris.registry.PolarisRegistration;
|
||||
import com.tencent.cloud.polaris.registry.PolarisRegistrationCustomizer;
|
||||
|
||||
public class GatewayRegistrationCustomizer implements PolarisRegistrationCustomizer {
|
||||
@Override
|
||||
public void customize(PolarisRegistration registration) {
|
||||
registration.getMetadata().put("internal-service-type", "spring-cloud-gateway");
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway;
|
||||
|
||||
import com.tencent.cloud.common.constant.MetadataConstant;
|
||||
import com.tencent.cloud.common.metadata.MetadataContext;
|
||||
import com.tencent.cloud.common.metadata.MetadataContextHolder;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter {
|
||||
|
||||
private final ReactiveLoadBalancerClientFilter clientFilter;
|
||||
|
||||
public PolarisReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
|
||||
GatewayLoadBalancerProperties properties, ReactiveLoadBalancerClientFilter clientFilter) {
|
||||
super(clientFactory, properties);
|
||||
this.clientFilter = clientFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return clientFilter.getOrder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
// restore context from exchange
|
||||
MetadataContext metadataContext = (MetadataContext) exchange.getAttributes().get(
|
||||
MetadataConstant.HeaderName.METADATA_CONTEXT);
|
||||
if (metadataContext != null) {
|
||||
MetadataContextHolder.set(metadataContext);
|
||||
}
|
||||
return clientFilter.filter(exchange, chain);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties;
|
||||
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessor implements BeanPostProcessor, Ordered {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
public PolarisReactiveLoadBalancerClientFilterBeanPostProcessor(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof ReactiveLoadBalancerClientFilter && !(bean instanceof PolarisReactiveLoadBalancerClientFilter)) {
|
||||
LoadBalancerClientFactory clientFactory = applicationContext.getBean(LoadBalancerClientFactory.class);
|
||||
GatewayLoadBalancerProperties properties = applicationContext.getBean(GatewayLoadBalancerProperties.class);
|
||||
|
||||
return new PolarisReactiveLoadBalancerClientFilter(clientFactory, properties, (ReactiveLoadBalancerClientFilter) bean);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
}
|
24
spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java → spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ApiType.java
24
spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java → spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ApiType.java
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.context;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.tencent.cloud.common.constant.MetadataConstant;
|
||||
import com.tencent.cloud.common.metadata.MetadataContext;
|
||||
import com.tencent.polaris.api.utils.CollectionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter;
|
||||
import org.springframework.cloud.gateway.route.Route;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
|
||||
|
||||
public class ContextGatewayFilter implements GatewayFilter, Ordered {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContextGatewayFilter.class);
|
||||
|
||||
private ContextGatewayPropertiesManager manager;
|
||||
|
||||
private ContextGatewayFilterFactory.Config config;
|
||||
|
||||
public ContextGatewayFilter(ContextGatewayPropertiesManager manager, ContextGatewayFilterFactory.Config config) {
|
||||
this.manager = manager;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
GroupContext groupContext = manager.getGroups().get(config.getGroup());
|
||||
|
||||
if (ApiType.MS.equals(groupContext.getPredicate().getApiType())) {
|
||||
return msFilter(exchange, chain, groupContext);
|
||||
}
|
||||
else {
|
||||
return externalFilter(exchange, chain, groupContext);
|
||||
}
|
||||
}
|
||||
|
||||
private Mono<Void> externalFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String[] apis = rebuildExternalApi(request, request.getPath().value());
|
||||
GroupContext.ContextRoute contextRoute = manager.getGroupPathRoute(config.getGroup(), apis[0]);
|
||||
if (contextRoute == null) {
|
||||
throw new RuntimeException(String.format("Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath()));
|
||||
}
|
||||
updateRouteMetadata(exchange, contextRoute);
|
||||
|
||||
URI requestUri = URI.create(contextRoute.getHost() + apis[1]);
|
||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri);
|
||||
// 调整为正确路径
|
||||
ServerHttpRequest newRequest = request.mutate().path(apis[1]).build();
|
||||
return chain.filter(exchange.mutate().request(newRequest).build());
|
||||
}
|
||||
|
||||
private Mono<Void> msFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String[] apis = rebuildMsApi(request, groupContext, request.getPath().value());
|
||||
// 判断 api 是否匹配
|
||||
GroupContext.ContextRoute contextRoute = manager.getGroupPathRoute(config.getGroup(), apis[0]);
|
||||
if (contextRoute == null) {
|
||||
throw new RuntimeException(String.format("Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath()));
|
||||
}
|
||||
updateRouteMetadata(exchange, contextRoute);
|
||||
|
||||
MetadataContext metadataContext = (MetadataContext) exchange.getAttributes().get(
|
||||
MetadataConstant.HeaderName.METADATA_CONTEXT);
|
||||
|
||||
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
|
||||
MetadataConstant.POLARIS_TARGET_NAMESPACE, contextRoute.getNamespace());
|
||||
|
||||
URI requestUri = URI.create("lb://" + contextRoute.getService() + apis[1]);
|
||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri);
|
||||
// 调整为正确路径
|
||||
ServerHttpRequest newRequest = request.mutate().path(apis[1]).build();
|
||||
return chain.filter(exchange.mutate().request(newRequest).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* e.g. "/context/api/test" → [ "GET|/api/test", "/api/test"]
|
||||
*/
|
||||
private String[] rebuildExternalApi(ServerHttpRequest request, String path) {
|
||||
String[] pathSegments = path.split("/");
|
||||
StringBuilder matchPath = new StringBuilder();
|
||||
StringBuilder realPath = new StringBuilder();
|
||||
int index = 2;
|
||||
matchPath.append(request.getMethodValue()).append("|");
|
||||
for (int i = index; i < pathSegments.length; i++) {
|
||||
matchPath.append("/").append(pathSegments[i]);
|
||||
realPath.append("/").append(pathSegments[i]);
|
||||
}
|
||||
if (path.endsWith("/")) {
|
||||
matchPath.append("/");
|
||||
realPath.append("/");
|
||||
}
|
||||
return new String[] {matchPath.toString(), realPath.toString()};
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an array of two strings, the first is the match path, the second is the real path.
|
||||
* e.g. "/context/namespace/svc/api/test" → [ "GET|/namespace/svc/api/test", "/api/test"]
|
||||
*/
|
||||
private String[] rebuildMsApi(ServerHttpRequest request, GroupContext groupContext, String path) {
|
||||
String[] pathSegments = path.split("/");
|
||||
StringBuilder matchPath = new StringBuilder();
|
||||
int index = 2;
|
||||
matchPath.append(request.getMethodValue()).append("|");
|
||||
|
||||
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:
|
||||
matchPath.append("/").append(pathSegments[index++]);
|
||||
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:
|
||||
matchPath.append("/").append(pathSegments[index++]);
|
||||
}
|
||||
StringBuilder realPath = new StringBuilder();
|
||||
for (int i = index; i < pathSegments.length; i++) {
|
||||
matchPath.append("/").append(pathSegments[i]);
|
||||
realPath.append("/").append(pathSegments[i]);
|
||||
}
|
||||
if (path.endsWith("/")) {
|
||||
matchPath.append("/");
|
||||
realPath.append("/");
|
||||
}
|
||||
|
||||
return new String[] {matchPath.toString(), realPath.toString()};
|
||||
}
|
||||
|
||||
private void updateRouteMetadata(ServerWebExchange exchange, GroupContext.ContextRoute contextRoute) {
|
||||
if (CollectionUtils.isEmpty(contextRoute.getMetadata())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Route route = (Route) exchange.getAttributes().get(GATEWAY_ROUTE_ATTR);
|
||||
Constructor constructor = Route.class.getDeclaredConstructors()[1];
|
||||
constructor.setAccessible(true);
|
||||
try {
|
||||
HashMap<String, Object> metadata = new HashMap<>(route.getMetadata());
|
||||
metadata.putAll(contextRoute.getMetadata());
|
||||
Route newRoute = (Route) constructor.newInstance(route.getId(), route.getUri(),
|
||||
route.getOrder(), route.getPredicate(), route.getFilters(), metadata);
|
||||
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, newRoute);
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.debug("[updateRouteMetadata] update route metadata failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
// after RouteToRequestUrlFilter, DecodeTransferMetadataReactiveFilter
|
||||
return RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER + 12;
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.context;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
|
||||
public class ContextGatewayFilterFactory extends AbstractGatewayFilterFactory<ContextGatewayFilterFactory.Config> {
|
||||
|
||||
private ContextGatewayPropertiesManager manager;
|
||||
|
||||
public ContextGatewayFilterFactory(ContextGatewayPropertiesManager manager) {
|
||||
super(Config.class);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList("group");
|
||||
}
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Config config) {
|
||||
return new ContextGatewayFilter(manager, config);
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
private String group;
|
||||
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public void setGroup(String group) {
|
||||
this.group = group;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.context;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
@ConfigurationProperties(ContextGatewayProperties.PREFIX)
|
||||
public class ContextGatewayProperties {
|
||||
|
||||
/**
|
||||
* Properties prefix.
|
||||
*/
|
||||
public static final String PREFIX = "spring.cloud.tencent.gateway";
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private Map<String, RouteDefinition> routes = new HashMap<>();
|
||||
|
||||
private Map<String, GroupContext> groups = new HashMap<>();
|
||||
|
||||
public Map<String, RouteDefinition> getRoutes() {
|
||||
return routes;
|
||||
}
|
||||
|
||||
public void setRoutes(Map<String, RouteDefinition> routes) {
|
||||
this.routes = routes;
|
||||
if (routes != null && routes.size() > 0 && logger.isDebugEnabled()) {
|
||||
logger.debug("Routes supplied from Gateway Properties: " + routes);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, GroupContext> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void setGroups(Map<String, GroupContext> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("routes", routes)
|
||||
.append("groups", groups).toString();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.context;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
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.polaris.discovery.PolarisDiscoveryClient;
|
||||
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
|
||||
import com.tencent.polaris.api.utils.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
public class ContextGatewayPropertiesManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContextGatewayPropertiesManager.class);
|
||||
/**
|
||||
* context -> {path key -> route}.
|
||||
*/
|
||||
private volatile ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> groupPathRouteMap = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* context -> {wildcard path key -> route}.
|
||||
*/
|
||||
private volatile ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> groupWildcardPathRouteMap = new ConcurrentHashMap<>();
|
||||
|
||||
private Map<String, GroupContext> groups = new HashMap<>();
|
||||
|
||||
private AntPathMatcher antPathMatcher = new AntPathMatcher();
|
||||
|
||||
public Map<String, Map<String, GroupContext.ContextRoute>> getGroupPathRouteMap() {
|
||||
return groupPathRouteMap;
|
||||
}
|
||||
|
||||
public void setGroupRouteMap(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()) {
|
||||
Map<String, GroupContext.ContextRoute> newGroupPathRoute = new HashMap<>();
|
||||
Map<String, GroupContext.ContextRoute> newGroupWildcardPathRoute = new HashMap<>();
|
||||
for (GroupContext.ContextRoute route : entry.getValue().getRoutes()) {
|
||||
String path = route.getPath();
|
||||
// convert path parameter to group wildcard path
|
||||
if (path.contains("{") && path.contains("}") || path.contains("*")) {
|
||||
newGroupWildcardPathRoute.put(buildPathKey(entry.getValue(), route), route);
|
||||
}
|
||||
else {
|
||||
newGroupPathRoute.put(buildPathKey(entry.getValue(), route), route);
|
||||
}
|
||||
}
|
||||
newGroupWildcardPathRouteMap.put(entry.getKey(), newGroupWildcardPathRoute);
|
||||
newGroupPathRouteMap.put(entry.getKey(), newGroupPathRoute);
|
||||
}
|
||||
}
|
||||
this.groupPathRouteMap = newGroupPathRouteMap;
|
||||
this.groupWildcardPathRouteMap = newGroupWildcardPathRouteMap;
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
public Map<String, GroupContext> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public GroupContext.ContextRoute getGroupPathRoute(String group, String path) {
|
||||
Map<String, GroupContext.ContextRoute> groupPathRouteMap = this.groupPathRouteMap.get(group);
|
||||
if (groupPathRouteMap != null && groupPathRouteMap.containsKey(path)) {
|
||||
return groupPathRouteMap.get(path);
|
||||
}
|
||||
|
||||
Map<String, GroupContext.ContextRoute> groupWildcardPathRouteMap = this.groupWildcardPathRouteMap.get(group);
|
||||
if (groupWildcardPathRouteMap != null) {
|
||||
for (Map.Entry<String, GroupContext.ContextRoute> entry : groupWildcardPathRouteMap.entrySet()) {
|
||||
boolean matched = antPathMatcher.match(entry.getKey(), path);
|
||||
if (matched) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void eagerLoad(PolarisDiscoveryClient polarisDiscoveryClient,
|
||||
PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
|
||||
for (Map<String, GroupContext.ContextRoute> contextRouteMap : groupPathRouteMap.values()) {
|
||||
for (GroupContext.ContextRoute contextRoute : contextRouteMap.values()) {
|
||||
eagerLoadFromRoute(contextRoute, polarisDiscoveryClient, polarisReactiveDiscoveryClient);
|
||||
}
|
||||
}
|
||||
for (Map<String, GroupContext.ContextRoute> contextRouteMap : groupWildcardPathRouteMap.values()) {
|
||||
for (GroupContext.ContextRoute contextRoute : contextRouteMap.values()) {
|
||||
eagerLoadFromRoute(contextRoute, polarisDiscoveryClient, polarisReactiveDiscoveryClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void eagerLoadFromRoute(GroupContext.ContextRoute contextRoute, PolarisDiscoveryClient polarisDiscoveryClient,
|
||||
PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
|
||||
String namespace = contextRoute.getNamespace();
|
||||
String service = contextRoute.getService();
|
||||
if (StringUtils.isNotEmpty(namespace) && StringUtils.isNotEmpty(service)) {
|
||||
logger.info("[{},{}] eager-load start", namespace, service);
|
||||
MetadataContextHolder.get().putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
|
||||
MetadataConstant.POLARIS_TARGET_NAMESPACE, namespace);
|
||||
|
||||
if (polarisDiscoveryClient != null) {
|
||||
polarisDiscoveryClient.getInstances(service);
|
||||
}
|
||||
else if (polarisReactiveDiscoveryClient != null) {
|
||||
polarisReactiveDiscoveryClient.getInstances(service).subscribe();
|
||||
}
|
||||
else {
|
||||
logger.warn("[{}] no discovery client found.", service);
|
||||
}
|
||||
logger.info("[{},{}] eager-load end", namespace, service);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.context;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
|
||||
|
||||
public class ContextPropertiesRouteDefinitionLocator implements RouteDefinitionLocator {
|
||||
|
||||
private final ContextGatewayProperties properties;
|
||||
|
||||
public ContextPropertiesRouteDefinitionLocator(ContextGatewayProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<RouteDefinition> getRouteDefinitions() {
|
||||
return Flux.fromIterable(this.properties.getRoutes().values());
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.context;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
|
||||
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
public class ContextRoutePredicateFactory extends AbstractRoutePredicateFactory<ContextRoutePredicateFactory.Config> {
|
||||
|
||||
public ContextRoutePredicateFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> apply(Config config) {
|
||||
return new GatewayPredicate() {
|
||||
@Override
|
||||
public boolean test(ServerWebExchange exchange) {
|
||||
// TODO: do path-rewriting , put to GATEWAY_PREDICATE_PATH_CONTAINER_ATTR
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Config: %s", config);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList("group");
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
private String group;
|
||||
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public void setGroup(String group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Config{" +
|
||||
"group='" + group + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.context;
|
||||
|
||||
import com.tencent.cloud.polaris.config.annotation.PolarisConfigKVFileChangeListener;
|
||||
import com.tencent.cloud.polaris.config.listener.ConfigChangeEvent;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.BindResult;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
public class GatewayConfigChangeListener {
|
||||
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
private ContextGatewayPropertiesManager manager;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
public GatewayConfigChangeListener(ContextGatewayPropertiesManager manager,
|
||||
ApplicationEventPublisher publisher, Environment environment) {
|
||||
this.manager = manager;
|
||||
this.publisher = publisher;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@PolarisConfigKVFileChangeListener(interestedKeyPrefixes = ContextGatewayProperties.PREFIX)
|
||||
public void onChangeTencentGatewayProperties(ConfigChangeEvent event) {
|
||||
Binder binder = Binder.get(environment);
|
||||
BindResult<ContextGatewayProperties> result = binder.bind(ContextGatewayProperties.PREFIX, ContextGatewayProperties.class);
|
||||
manager.setGroupRouteMap(result.get().getGroups());
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(event));
|
||||
}
|
||||
|
||||
@PolarisConfigKVFileChangeListener(interestedKeyPrefixes = GatewayProperties.PREFIX)
|
||||
public void onChangeGatewayConfigChangeListener(ConfigChangeEvent event) {
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(event));
|
||||
}
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.context;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GroupContext {
|
||||
|
||||
private String comment;
|
||||
|
||||
private ApiType apiType;
|
||||
|
||||
private ContextPredicate predicate;
|
||||
|
||||
private List<ContextRoute> routes;
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public ApiType getApiType() {
|
||||
return apiType;
|
||||
}
|
||||
|
||||
public void setApiType(ApiType apiType) {
|
||||
this.apiType = apiType;
|
||||
}
|
||||
|
||||
public ContextPredicate getPredicate() {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
public void setPredicate(ContextPredicate predicate) {
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
public List<ContextRoute> getRoutes() {
|
||||
return routes;
|
||||
}
|
||||
|
||||
public void setRoutes(List<ContextRoute> routes) {
|
||||
this.routes = routes;
|
||||
}
|
||||
|
||||
public static class ContextPredicate {
|
||||
private ApiType apiType;
|
||||
|
||||
private String context;
|
||||
|
||||
private ContextNamespace namespace;
|
||||
|
||||
private ContextService service;
|
||||
|
||||
public ApiType getApiType() {
|
||||
return apiType;
|
||||
}
|
||||
|
||||
public void setApiType(ApiType apiType) {
|
||||
this.apiType = apiType;
|
||||
}
|
||||
|
||||
public ContextNamespace getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public void setNamespace(ContextNamespace namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public ContextService getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public void setService(ContextService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public String getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public void setContext(String context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ContextNamespace {
|
||||
private Position position;
|
||||
|
||||
private String key;
|
||||
|
||||
public Position getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(Position position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ContextService {
|
||||
private Position position;
|
||||
|
||||
private String key;
|
||||
|
||||
public Position getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(Position position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ContextRoute {
|
||||
private String path;
|
||||
|
||||
private String pathMapping;
|
||||
|
||||
private String method;
|
||||
|
||||
private String service;
|
||||
|
||||
private String host;
|
||||
|
||||
private String namespace;
|
||||
|
||||
private Map<String, String> metadata;
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getPathMapping() {
|
||||
return pathMapping;
|
||||
}
|
||||
|
||||
public void setPathMapping(String pathMapping) {
|
||||
this.pathMapping = pathMapping;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public void setService(String service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public void setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public Map<String, String> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(Map<String, String> metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.context;
|
||||
|
||||
public enum Position {
|
||||
/**
|
||||
* Path position.
|
||||
*/
|
||||
PATH,
|
||||
/**
|
||||
* Query position.
|
||||
*/
|
||||
QUERY,
|
||||
/**
|
||||
* Header position.
|
||||
*/
|
||||
HEADER,
|
||||
}
|
0
spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json → spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json
0
spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json → spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.tencent.cloud.plugin.gateway.GatewayPluginAutoConfiguration
|
@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.tencent.cloud.plugin.gateway.staining.TrafficStainer;
|
||||
import com.tencent.cloud.plugin.gateway.staining.TrafficStainingGatewayFilter;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingExecutor;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingProperties;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.RuleTrafficStainer;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.StainingRuleManager;
|
||||
import com.tencent.cloud.polaris.config.ConditionalOnPolarisConfigEnabled;
|
||||
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
|
||||
import com.tencent.polaris.configuration.api.core.ConfigFileService;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Auto configuration for spring cloud gateway plugins.
|
||||
* @author lepdou 2022-07-06
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnPolarisEnabled
|
||||
@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.enabled", matchIfMissing = true)
|
||||
public class SCGPluginsAutoConfiguration {
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty("spring.cloud.tencent.plugin.scg.staining.rule-staining.enabled")
|
||||
@ConditionalOnPolarisConfigEnabled
|
||||
public static class RuleStainingPluginConfiguration {
|
||||
|
||||
@Bean
|
||||
public RuleStainingProperties ruleStainingProperties() {
|
||||
return new RuleStainingProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StainingRuleManager stainingRuleManager(RuleStainingProperties stainingProperties, ConfigFileService configFileService) {
|
||||
return new StainingRuleManager(stainingProperties, configFileService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TrafficStainingGatewayFilter trafficStainingGatewayFilter(List<TrafficStainer> trafficStainer) {
|
||||
return new TrafficStainingGatewayFilter(trafficStainer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RuleStainingExecutor ruleStainingExecutor() {
|
||||
return new RuleStainingExecutor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RuleTrafficStainer ruleTrafficStainer(StainingRuleManager stainingRuleManager, RuleStainingExecutor ruleStainingExecutor) {
|
||||
return new RuleTrafficStainer(stainingRuleManager, ruleStainingExecutor);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.staining;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
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.cloud.common.util.JacksonUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER;
|
||||
|
||||
/**
|
||||
* Staining the request, and the stained labels will be passed to the link through transitive metadata.
|
||||
* @author lepdou 2022-07-06
|
||||
*/
|
||||
public class TrafficStainingGatewayFilter implements GlobalFilter, Ordered {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TrafficStainingGatewayFilter.class);
|
||||
|
||||
private final List<TrafficStainer> trafficStainers;
|
||||
|
||||
public TrafficStainingGatewayFilter(List<TrafficStainer> trafficStainers) {
|
||||
if (!CollectionUtils.isEmpty(trafficStainers)) {
|
||||
trafficStainers.sort(Comparator.comparingInt(Ordered::getOrder));
|
||||
}
|
||||
this.trafficStainers = trafficStainers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
if (CollectionUtils.isEmpty(trafficStainers)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 1. get stained labels from request
|
||||
Map<String, String> stainedLabels = getStainedLabels(exchange);
|
||||
|
||||
if (CollectionUtils.isEmpty(stainedLabels)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 2. put stained labels to metadata context
|
||||
ServerHttpRequest request = exchange.getRequest().mutate().headers((httpHeaders) -> {
|
||||
MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT);
|
||||
if (metadataContext == null) {
|
||||
metadataContext = MetadataContextHolder.get();
|
||||
}
|
||||
|
||||
Map<String, String> oldTransitiveMetadata = metadataContext.getTransitiveMetadata();
|
||||
|
||||
// append new transitive metadata
|
||||
Map<String, String> newTransitiveMetadata = new HashMap<>(oldTransitiveMetadata);
|
||||
newTransitiveMetadata.putAll(stainedLabels);
|
||||
|
||||
metadataContext.setTransitiveMetadata(newTransitiveMetadata);
|
||||
}).build();
|
||||
|
||||
return chain.filter(exchange.mutate().request(request).build());
|
||||
}
|
||||
|
||||
Map<String, String> getStainedLabels(ServerWebExchange exchange) {
|
||||
Map<String, String> stainedLabels = new HashMap<>();
|
||||
int size = trafficStainers.size();
|
||||
TrafficStainer stainer = null;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
try {
|
||||
stainer = trafficStainers.get(i);
|
||||
Map<String, String> labels = stainer.apply(exchange);
|
||||
if (!CollectionUtils.isEmpty(labels)) {
|
||||
stainedLabels.putAll(labels);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (stainer != null) {
|
||||
LOGGER.error("[SCT] traffic stained error. stainer = {}", stainer.getClass().getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOGGER.debug("[SCT] traffic stained labels. {}", JacksonUtils.serialize2Json(stainedLabels));
|
||||
|
||||
return stainedLabels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return ROUTE_TO_URL_FILTER_ORDER + 1;
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.staining.rule;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.tencent.cloud.common.rule.Condition;
|
||||
import com.tencent.cloud.common.rule.ConditionUtils;
|
||||
import com.tencent.cloud.common.rule.KVPairUtils;
|
||||
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
|
||||
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Resolve labels from request by staining rule.
|
||||
* @author lepdou 2022-07-11
|
||||
*/
|
||||
public class RuleStainingExecutor {
|
||||
|
||||
Map<String, String> execute(ServerWebExchange exchange, StainingRule stainingRule) {
|
||||
if (stainingRule == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
List<StainingRule.Rule> rules = stainingRule.getRules();
|
||||
if (CollectionUtils.isEmpty(rules)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, String> parsedLabels = new HashMap<>();
|
||||
|
||||
for (StainingRule.Rule rule : rules) {
|
||||
List<Condition> conditions = rule.getConditions();
|
||||
|
||||
Set<String> keys = new HashSet<>();
|
||||
conditions.forEach(condition -> keys.add(condition.getKey()));
|
||||
Map<String, String> actualValues = SpringWebExpressionLabelUtils.resolve(exchange, keys);
|
||||
|
||||
if (!ConditionUtils.match(actualValues, conditions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedLabels.putAll(KVPairUtils.toMap(rule.getLabels()));
|
||||
}
|
||||
|
||||
return parsedLabels;
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.staining.rule;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* The properties for rule staining.
|
||||
* @author lepdou 2022-07-11
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.tencent.plugin.scg.staining.rule-staining")
|
||||
public class RuleStainingProperties {
|
||||
|
||||
@Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.namespace:${spring.cloud.tencent.namespace:default}}")
|
||||
private String namespace;
|
||||
|
||||
@Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.group:${spring.application.name:spring-cloud-gateway}}")
|
||||
private String group;
|
||||
|
||||
@Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.fileName:rule/staining.json}")
|
||||
private String fileName;
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public void setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public void setGroup(String group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.staining.rule;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import com.tencent.cloud.plugin.gateway.staining.TrafficStainer;
|
||||
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Staining the request by staining rules.
|
||||
* @author lepdou 2022-07-06
|
||||
*/
|
||||
public class RuleTrafficStainer implements TrafficStainer {
|
||||
|
||||
private final StainingRuleManager stainingRuleManager;
|
||||
private final RuleStainingExecutor ruleStainingExecutor;
|
||||
|
||||
public RuleTrafficStainer(StainingRuleManager stainingRuleManager, RuleStainingExecutor ruleStainingExecutor) {
|
||||
this.stainingRuleManager = stainingRuleManager;
|
||||
this.ruleStainingExecutor = ruleStainingExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> apply(ServerWebExchange exchange) {
|
||||
StainingRule stainingRule = stainingRuleManager.getStainingRule();
|
||||
|
||||
if (stainingRule == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
return ruleStainingExecutor.execute(exchange, stainingRule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.staining.rule;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.tencent.cloud.common.rule.Condition;
|
||||
import com.tencent.cloud.common.rule.KVPair;
|
||||
|
||||
/**
|
||||
* The rules for staining.
|
||||
* @author lepdou 2022-07-07
|
||||
*/
|
||||
public class StainingRule {
|
||||
|
||||
private List<Rule> rules;
|
||||
|
||||
public List<Rule> getRules() {
|
||||
return rules;
|
||||
}
|
||||
|
||||
public void setRules(List<Rule> rules) {
|
||||
this.rules = rules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StainingRule{" +
|
||||
"rules=" + rules +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static class Rule {
|
||||
private List<Condition> conditions;
|
||||
private List<KVPair> labels;
|
||||
|
||||
public List<Condition> getConditions() {
|
||||
return conditions;
|
||||
}
|
||||
|
||||
public void setConditions(List<Condition> conditions) {
|
||||
this.conditions = conditions;
|
||||
}
|
||||
|
||||
public List<KVPair> getLabels() {
|
||||
return labels;
|
||||
}
|
||||
|
||||
public void setLabels(List<KVPair> labels) {
|
||||
this.labels = labels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Rule{" +
|
||||
"conditions=" + conditions +
|
||||
", labels=" + labels +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.staining.rule;
|
||||
|
||||
import com.tencent.cloud.common.util.JacksonUtils;
|
||||
import com.tencent.polaris.configuration.api.core.ConfigFile;
|
||||
import com.tencent.polaris.configuration.api.core.ConfigFileService;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Fetch staining rule from polaris, and deserialize to {@link StainingRule}.
|
||||
* @author lepdou 2022-07-07
|
||||
*/
|
||||
public class StainingRuleManager {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(StainingRuleManager.class);
|
||||
|
||||
private final RuleStainingProperties stainingProperties;
|
||||
private final ConfigFileService configFileService;
|
||||
|
||||
private StainingRule stainingRule;
|
||||
|
||||
public StainingRuleManager(RuleStainingProperties stainingProperties, ConfigFileService configFileService) {
|
||||
this.stainingProperties = stainingProperties;
|
||||
this.configFileService = configFileService;
|
||||
|
||||
initStainingRule();
|
||||
}
|
||||
|
||||
private void initStainingRule() {
|
||||
ConfigFile rulesFile = configFileService.getConfigFile(stainingProperties.getNamespace(), stainingProperties.getGroup(),
|
||||
stainingProperties.getFileName());
|
||||
|
||||
rulesFile.addChangeListener(event -> {
|
||||
LOGGER.info("[SCT] update scg staining rules. {}", event);
|
||||
deserialize(event.getNewValue());
|
||||
});
|
||||
|
||||
String ruleJson = rulesFile.getContent();
|
||||
LOGGER.info("[SCT] init scg staining rules. {}", ruleJson);
|
||||
|
||||
deserialize(ruleJson);
|
||||
}
|
||||
|
||||
private void deserialize(String ruleJsonStr) {
|
||||
if (StringUtils.isBlank(ruleJsonStr)) {
|
||||
stainingRule = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
stainingRule = JacksonUtils.deserialize(ruleJsonStr, StainingRule.class);
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.error("[SCT] deserialize staining rule error.", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public StainingRule getStainingRule() {
|
||||
return stainingRule;
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.tencent.cloud.plugin.gateway.SCGPluginsAutoConfiguration
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway;
|
||||
|
||||
import com.tencent.cloud.plugin.gateway.staining.TrafficStainingGatewayFilter;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingExecutor;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingProperties;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.RuleTrafficStainer;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.StainingRuleManager;
|
||||
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
|
||||
import com.tencent.polaris.configuration.api.core.ConfigFileService;
|
||||
import com.tencent.polaris.configuration.factory.ConfigFileServiceFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
|
||||
|
||||
/**
|
||||
* Test for {@link SCGPluginsAutoConfiguration}.
|
||||
* @author derek.yi 2022-11-03
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SpringBootTest(webEnvironment = DEFINED_PORT, classes = SCGPluginsAutoConfigurationTest.TestApplication.class,
|
||||
properties = {"server.port=48081", "spring.config.location = classpath:application-test.yml",
|
||||
"spring.cloud.tencent.plugin.scg.staining.rule-staining.enabled = true"})
|
||||
public class SCGPluginsAutoConfigurationTest {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Test
|
||||
public void testAutoConfiguration() {
|
||||
assertThat(applicationContext.getBeansOfType(RuleStainingProperties.class).size()).isEqualTo(1);
|
||||
assertThat(applicationContext.getBeansOfType(StainingRuleManager.class).size()).isEqualTo(1);
|
||||
assertThat(applicationContext.getBeansOfType(TrafficStainingGatewayFilter.class).size()).isEqualTo(1);
|
||||
assertThat(applicationContext.getBeansOfType(RuleStainingExecutor.class).size()).isEqualTo(1);
|
||||
assertThat(applicationContext.getBeansOfType(RuleTrafficStainer.class).size()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
public static class TestApplication {
|
||||
|
||||
@Bean
|
||||
public ConfigFileService configFileService(PolarisSDKContextManager polarisSDKContextManager) {
|
||||
return ConfigFileServiceFactory.createConfigFileService(polarisSDKContextManager.getSDKContext());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.staining;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.tencent.cloud.common.metadata.MetadataContext;
|
||||
import com.tencent.cloud.common.metadata.MetadataContextHolder;
|
||||
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingExecutor;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingProperties;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.RuleTrafficStainer;
|
||||
import com.tencent.cloud.plugin.gateway.staining.rule.StainingRuleManager;
|
||||
import com.tencent.polaris.configuration.api.core.ConfigFile;
|
||||
import com.tencent.polaris.configuration.api.core.ConfigFileService;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test for {@link TrafficStainingGatewayFilter}.
|
||||
* @author lepdou 2022-07-12
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class TrafficStainingGatewayFilterTest {
|
||||
|
||||
private final String testNamespace = "testNamespace";
|
||||
private final String testGroup = "testGroup";
|
||||
private final String testFileName = "rule.json";
|
||||
@Mock
|
||||
private GatewayFilterChain chain;
|
||||
@Mock
|
||||
private ServerWebExchange exchange;
|
||||
@Mock
|
||||
private ConfigFileService configFileService;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
Mockito.mockStatic(ApplicationContextAwareUtils.class);
|
||||
when(ApplicationContextAwareUtils
|
||||
.getProperties(any())).thenReturn("fooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoneTrafficStainingImplement() {
|
||||
TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(null);
|
||||
|
||||
when(chain.filter(exchange)).thenReturn(Mono.empty());
|
||||
|
||||
filter.filter(exchange, chain);
|
||||
|
||||
verify(chain).filter(exchange);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiStaining() {
|
||||
TrafficStainer trafficStainer1 = Mockito.mock(TrafficStainer.class);
|
||||
TrafficStainer trafficStainer2 = Mockito.mock(TrafficStainer.class);
|
||||
|
||||
when(trafficStainer1.getOrder()).thenReturn(1);
|
||||
when(trafficStainer2.getOrder()).thenReturn(2);
|
||||
|
||||
Map<String, String> labels1 = new HashMap<>();
|
||||
labels1.put("k1", "v1");
|
||||
labels1.put("k2", "v2");
|
||||
when(trafficStainer1.apply(exchange)).thenReturn(labels1);
|
||||
|
||||
Map<String, String> labels2 = new HashMap<>();
|
||||
labels2.put("k1", "v11");
|
||||
labels2.put("k3", "v3");
|
||||
when(trafficStainer2.apply(exchange)).thenReturn(labels2);
|
||||
|
||||
TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(Arrays.asList(trafficStainer1, trafficStainer2));
|
||||
Map<String, String> result = filter.getStainedLabels(exchange);
|
||||
|
||||
assertThat(result).isNotEmpty();
|
||||
assertThat(result.get("k1")).isEqualTo("v1");
|
||||
assertThat(result.get("k2")).isEqualTo("v2");
|
||||
assertThat(result.get("k3")).isEqualTo("v3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoTrafficStainers() {
|
||||
MetadataContext metadataContext = new MetadataContext();
|
||||
MetadataContextHolder.set(metadataContext);
|
||||
|
||||
TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(null);
|
||||
filter.filter(exchange, chain);
|
||||
Map<String, String> map = metadataContext.getTransitiveMetadata();
|
||||
assertThat(map).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithTrafficStainers() {
|
||||
MetadataContext metadataContext = new MetadataContext();
|
||||
MetadataContextHolder.set(metadataContext);
|
||||
|
||||
RuleStainingProperties ruleStainingProperties = new RuleStainingProperties();
|
||||
ruleStainingProperties.setNamespace(testNamespace);
|
||||
ruleStainingProperties.setGroup(testGroup);
|
||||
ruleStainingProperties.setFileName(testFileName);
|
||||
|
||||
ConfigFile configFile = Mockito.mock(ConfigFile.class);
|
||||
when(configFile.getContent()).thenReturn("{\n"
|
||||
+ " \"rules\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"conditions\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"key\":\"${http.query.uid}\",\n"
|
||||
+ " \"values\":[\"1000\"],\n"
|
||||
+ " \"operation\":\"EQUALS\"\n"
|
||||
+ " }\n"
|
||||
+ " ],\n"
|
||||
+ " \"labels\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"key\":\"env\",\n"
|
||||
+ " \"value\":\"blue\"\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}");
|
||||
when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile);
|
||||
|
||||
StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService);
|
||||
RuleStainingExecutor ruleStainingExecutor = new RuleStainingExecutor();
|
||||
RuleTrafficStainer ruleTrafficStainer = new RuleTrafficStainer(stainingRuleManager, ruleStainingExecutor);
|
||||
|
||||
TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(Collections.singletonList(ruleTrafficStainer));
|
||||
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/users")
|
||||
.queryParam("uid", "1000").build();
|
||||
MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
|
||||
|
||||
filter.filter(exchange, chain);
|
||||
Map<String, String> map = metadataContext.getTransitiveMetadata();
|
||||
assertThat(map).isNotNull();
|
||||
assertThat(map.size()).isEqualTo(1);
|
||||
assertThat(map.get("env")).isEqualTo("blue");
|
||||
}
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.staining.rule;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import com.tencent.cloud.common.rule.Condition;
|
||||
import com.tencent.cloud.common.rule.KVPair;
|
||||
import com.tencent.cloud.common.rule.Operation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test for {@link RuleStainingExecutor}.
|
||||
* @author lepdou 2022-07-12
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class RuleStainingExecutorTest {
|
||||
|
||||
@Test
|
||||
public void testMatchCondition() {
|
||||
Condition condition1 = new Condition();
|
||||
condition1.setKey("${http.header.uid}");
|
||||
condition1.setOperation(Operation.EQUALS.toString());
|
||||
condition1.setValues(Collections.singletonList("1000"));
|
||||
|
||||
Condition condition2 = new Condition();
|
||||
condition2.setKey("${http.query.source}");
|
||||
condition2.setOperation(Operation.IN.toString());
|
||||
condition2.setValues(Collections.singletonList("wx"));
|
||||
|
||||
StainingRule.Rule rule = new StainingRule.Rule();
|
||||
rule.setConditions(Arrays.asList(condition1, condition2));
|
||||
|
||||
KVPair kvPair = new KVPair();
|
||||
kvPair.setKey("env");
|
||||
kvPair.setValue("blue");
|
||||
rule.setLabels(Collections.singletonList(kvPair));
|
||||
|
||||
StainingRule stainingRule = new StainingRule();
|
||||
stainingRule.setRules(Collections.singletonList(rule));
|
||||
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/users")
|
||||
.queryParam("source", "wx")
|
||||
.header("uid", "1000").build();
|
||||
MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
|
||||
|
||||
RuleStainingExecutor executor = new RuleStainingExecutor();
|
||||
|
||||
Map<String, String> stainedLabels = executor.execute(exchange, stainingRule);
|
||||
|
||||
assertThat(stainedLabels).isNotNull();
|
||||
assertThat(stainedLabels.size()).isEqualTo(1);
|
||||
assertThat(stainedLabels.get("env")).isEqualTo("blue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotMatchCondition() {
|
||||
Condition condition1 = new Condition();
|
||||
condition1.setKey("${http.header.uid}");
|
||||
condition1.setOperation(Operation.EQUALS.toString());
|
||||
condition1.setValues(Collections.singletonList("1000"));
|
||||
|
||||
Condition condition2 = new Condition();
|
||||
condition2.setKey("${http.query.source}");
|
||||
condition2.setOperation(Operation.IN.toString());
|
||||
condition2.setValues(Collections.singletonList("wx"));
|
||||
|
||||
StainingRule.Rule rule = new StainingRule.Rule();
|
||||
rule.setConditions(Arrays.asList(condition1, condition2));
|
||||
|
||||
KVPair kvPair = new KVPair();
|
||||
kvPair.setKey("env");
|
||||
kvPair.setValue("blue");
|
||||
rule.setLabels(Collections.singletonList(kvPair));
|
||||
|
||||
StainingRule stainingRule = new StainingRule();
|
||||
stainingRule.setRules(Collections.singletonList(rule));
|
||||
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/users")
|
||||
.queryParam("source", "wx")
|
||||
.header("uid", "10001").build();
|
||||
MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
|
||||
|
||||
RuleStainingExecutor executor = new RuleStainingExecutor();
|
||||
|
||||
Map<String, String> stainedLabels = executor.execute(exchange, stainingRule);
|
||||
|
||||
assertThat(stainedLabels).isNotNull();
|
||||
assertThat(stainedLabels.size()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchTwoRulesAndNotMatchOneRule() {
|
||||
Condition condition1 = new Condition();
|
||||
condition1.setKey("${http.header.uid}");
|
||||
condition1.setOperation(Operation.EQUALS.toString());
|
||||
condition1.setValues(Collections.singletonList("1000"));
|
||||
|
||||
Condition condition2 = new Condition();
|
||||
condition2.setKey("${http.query.source}");
|
||||
condition2.setOperation(Operation.IN.toString());
|
||||
condition2.setValues(Collections.singletonList("wx"));
|
||||
|
||||
// rule1 matched
|
||||
StainingRule.Rule rule1 = new StainingRule.Rule();
|
||||
rule1.setConditions(Arrays.asList(condition1, condition2));
|
||||
|
||||
KVPair kvPair = new KVPair();
|
||||
kvPair.setKey("env");
|
||||
kvPair.setValue("blue");
|
||||
rule1.setLabels(Collections.singletonList(kvPair));
|
||||
|
||||
// rule2 matched
|
||||
StainingRule.Rule rule2 = new StainingRule.Rule();
|
||||
rule2.setConditions(Collections.singletonList(condition1));
|
||||
|
||||
KVPair kvPair2 = new KVPair();
|
||||
kvPair2.setKey("label1");
|
||||
kvPair2.setValue("value1");
|
||||
KVPair kvPair3 = new KVPair();
|
||||
kvPair3.setKey("label2");
|
||||
kvPair3.setValue("value2");
|
||||
rule2.setLabels(Arrays.asList(kvPair2, kvPair3));
|
||||
|
||||
// rule3 not matched
|
||||
Condition condition3 = new Condition();
|
||||
condition3.setKey("${http.query.type}");
|
||||
condition3.setOperation(Operation.IN.toString());
|
||||
condition3.setValues(Collections.singletonList("wx"));
|
||||
|
||||
StainingRule.Rule rule3 = new StainingRule.Rule();
|
||||
rule3.setConditions(Collections.singletonList(condition3));
|
||||
|
||||
KVPair kvPair4 = new KVPair();
|
||||
kvPair4.setKey("label3");
|
||||
kvPair4.setValue("value3");
|
||||
rule3.setLabels(Collections.singletonList(kvPair4));
|
||||
|
||||
StainingRule stainingRule = new StainingRule();
|
||||
stainingRule.setRules(Arrays.asList(rule1, rule2, rule3));
|
||||
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/users")
|
||||
.queryParam("source", "wx")
|
||||
.header("uid", "1000").build();
|
||||
MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
|
||||
|
||||
RuleStainingExecutor executor = new RuleStainingExecutor();
|
||||
|
||||
Map<String, String> stainedLabels = executor.execute(exchange, stainingRule);
|
||||
|
||||
assertThat(stainedLabels).isNotNull();
|
||||
assertThat(stainedLabels.size()).isEqualTo(3);
|
||||
assertThat(stainedLabels.get("env")).isEqualTo("blue");
|
||||
assertThat(stainedLabels.get("label1")).isEqualTo("value1");
|
||||
assertThat(stainedLabels.get("label2")).isEqualTo("value2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoStainingRule() {
|
||||
RuleStainingExecutor executor = new RuleStainingExecutor();
|
||||
assertThat(executor.execute(null, null)).isEmpty();
|
||||
assertThat(executor.execute(null, new StainingRule())).isEmpty();
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.staining.rule;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.tencent.polaris.configuration.api.core.ConfigFile;
|
||||
import com.tencent.polaris.configuration.api.core.ConfigFileService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test for {@link RuleTrafficStainer}.
|
||||
* @author derek.yi 2022-11-03
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class RuleTrafficStainerTest {
|
||||
|
||||
private final String testNamespace = "testNamespace";
|
||||
private final String testGroup = "testGroup";
|
||||
private final String testFileName = "rule.json";
|
||||
@Mock
|
||||
private ConfigFileService configFileService;
|
||||
|
||||
@Test
|
||||
public void testNoStainingRule() {
|
||||
RuleStainingProperties ruleStainingProperties = new RuleStainingProperties();
|
||||
ruleStainingProperties.setNamespace(testNamespace);
|
||||
ruleStainingProperties.setGroup(testGroup);
|
||||
ruleStainingProperties.setFileName(testFileName);
|
||||
|
||||
ConfigFile configFile = Mockito.mock(ConfigFile.class);
|
||||
when(configFile.getContent()).thenReturn("");
|
||||
when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile);
|
||||
|
||||
StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService);
|
||||
RuleStainingExecutor ruleStainingExecutor = new RuleStainingExecutor();
|
||||
RuleTrafficStainer ruleTrafficStainer = new RuleTrafficStainer(stainingRuleManager, ruleStainingExecutor);
|
||||
Map<String, String> map = ruleTrafficStainer.apply(null);
|
||||
assertThat(map).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithStainingRule() {
|
||||
RuleStainingProperties ruleStainingProperties = new RuleStainingProperties();
|
||||
ruleStainingProperties.setNamespace(testNamespace);
|
||||
ruleStainingProperties.setGroup(testGroup);
|
||||
ruleStainingProperties.setFileName(testFileName);
|
||||
|
||||
ConfigFile configFile = Mockito.mock(ConfigFile.class);
|
||||
when(configFile.getContent()).thenReturn("{\n"
|
||||
+ " \"rules\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"conditions\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"key\":\"${http.query.uid}\",\n"
|
||||
+ " \"values\":[\"1000\"],\n"
|
||||
+ " \"operation\":\"EQUALS\"\n"
|
||||
+ " }\n"
|
||||
+ " ],\n"
|
||||
+ " \"labels\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"key\":\"env\",\n"
|
||||
+ " \"value\":\"blue\"\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}");
|
||||
when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile);
|
||||
|
||||
StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService);
|
||||
RuleStainingExecutor ruleStainingExecutor = new RuleStainingExecutor();
|
||||
RuleTrafficStainer ruleTrafficStainer = new RuleTrafficStainer(stainingRuleManager, ruleStainingExecutor);
|
||||
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/users")
|
||||
.queryParam("uid", "1000").build();
|
||||
MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
|
||||
|
||||
Map<String, String> map = ruleTrafficStainer.apply(exchange);
|
||||
assertThat(map).isNotNull();
|
||||
assertThat(map.size()).isEqualTo(1);
|
||||
assertThat(map.get("env")).isEqualTo("blue");
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.gateway.staining.rule;
|
||||
|
||||
import com.tencent.polaris.configuration.api.core.ConfigFile;
|
||||
import com.tencent.polaris.configuration.api.core.ConfigFileService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test for {@link StainingRuleManager}.
|
||||
* @author lepdou 2022-07-12
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class StainingRuleManagerTest {
|
||||
|
||||
private final String testNamespace = "testNamespace";
|
||||
private final String testGroup = "testGroup";
|
||||
private final String testFileName = "rule.json";
|
||||
@Mock
|
||||
private ConfigFileService configFileService;
|
||||
|
||||
@Test
|
||||
public void testNormalRule() {
|
||||
RuleStainingProperties ruleStainingProperties = new RuleStainingProperties();
|
||||
ruleStainingProperties.setNamespace(testNamespace);
|
||||
ruleStainingProperties.setGroup(testGroup);
|
||||
ruleStainingProperties.setFileName(testFileName);
|
||||
|
||||
ConfigFile configFile = Mockito.mock(ConfigFile.class);
|
||||
when(configFile.getContent()).thenReturn("{\n"
|
||||
+ " \"rules\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"conditions\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"key\":\"${http.query.uid}\",\n"
|
||||
+ " \"values\":[\"1000\"],\n"
|
||||
+ " \"operation\":\"EQUALS\"\n"
|
||||
+ " }\n"
|
||||
+ " ],\n"
|
||||
+ " \"labels\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"key\":\"env\",\n"
|
||||
+ " \"value\":\"blue\"\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}");
|
||||
when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile);
|
||||
|
||||
StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService);
|
||||
|
||||
StainingRule stainingRule = stainingRuleManager.getStainingRule();
|
||||
|
||||
assertThat(stainingRule).isNotNull();
|
||||
assertThat(stainingRule.getRules().size()).isEqualTo(1);
|
||||
StainingRule.Rule rule = stainingRule.getRules().get(0);
|
||||
assertThat(rule.getConditions().size()).isEqualTo(1);
|
||||
assertThat(rule.getLabels().size()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongRule() {
|
||||
assertThatCode(() -> {
|
||||
RuleStainingProperties ruleStainingProperties = new RuleStainingProperties();
|
||||
ruleStainingProperties.setNamespace(testNamespace);
|
||||
ruleStainingProperties.setGroup(testGroup);
|
||||
ruleStainingProperties.setFileName(testFileName);
|
||||
|
||||
ConfigFile configFile = Mockito.mock(ConfigFile.class);
|
||||
when(configFile.getContent()).thenReturn("{\n"
|
||||
+ " \"rules\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"conditionsxxxx\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"key\":\"${http.query.uid}\",\n"
|
||||
+ " \"values\":[\"1000\"],\n"
|
||||
+ " \"operation\":\"EQUALS\"\n"
|
||||
+ " }\n"
|
||||
+ " ],\n"
|
||||
+ " \"labels\":[\n"
|
||||
+ " {\n"
|
||||
+ " \"key\":\"env\",\n"
|
||||
+ " \"value\":\"blue\"\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}");
|
||||
when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile);
|
||||
|
||||
new StainingRuleManager(ruleStainingProperties, configFileService);
|
||||
}).isInstanceOf(RuntimeException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyRule() {
|
||||
RuleStainingProperties ruleStainingProperties = new RuleStainingProperties();
|
||||
ruleStainingProperties.setNamespace(testNamespace);
|
||||
ruleStainingProperties.setGroup(testGroup);
|
||||
ruleStainingProperties.setFileName(testFileName);
|
||||
|
||||
ConfigFile configFile = Mockito.mock(ConfigFile.class);
|
||||
when(configFile.getContent()).thenReturn(null);
|
||||
when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile);
|
||||
|
||||
StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService);
|
||||
assertThat(stainingRuleManager.getStainingRule()).isNull();
|
||||
}
|
||||
}
|
Loading…
Reference in new issue