diff --git a/CHANGELOG.md b/CHANGELOG.md index 491208e4c..cc24833ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,4 @@ - [Feature: delete implement ServiceInstance](https://github.com/Tencent/spring-cloud-tencent/pull/481) - [Upgrade owasp esapi's configuration](https://github.com/Tencent/spring-cloud-tencent/pull/492) - [Bugfix: update byte-buddy scope test to compile](https://github.com/Tencent/spring-cloud-tencent/pull/495) +- [Feature: zuul supports polaris router](https://github.com/Tencent/spring-cloud-tencent/pull/502) \ No newline at end of file diff --git a/spring-cloud-starter-tencent-polaris-router/pom.xml b/spring-cloud-starter-tencent-polaris-router/pom.xml index 1f68017ef..104dd433e 100644 --- a/spring-cloud-starter-tencent-polaris-router/pom.xml +++ b/spring-cloud-starter-tencent-polaris-router/pom.xml @@ -62,6 +62,18 @@ true + + org.springframework.cloud + spring-cloud-netflix-zuul + true + + + + com.netflix.zuul + zuul-core + true + + org.springframework.boot spring-boot-actuator @@ -73,7 +85,7 @@ spring-boot-actuator-autoconfigure true - + org.springframework.boot spring-boot-starter-test @@ -92,6 +104,12 @@ test + + com.squareup.okhttp3 + okhttp + test + + net.bytebuddy byte-buddy diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java index 0ac1b7021..ae9a37e17 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java @@ -18,6 +18,9 @@ package com.tencent.cloud.polaris.router.config; +import java.util.List; + +import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.beanprocessor.LoadBalancerClientFilterBeanPostProcessor; @@ -28,10 +31,14 @@ import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouter import com.tencent.cloud.polaris.router.interceptor.MetadataRouterRequestInterceptor; import com.tencent.cloud.polaris.router.interceptor.NearbyRouterRequestInterceptor; import com.tencent.cloud.polaris.router.interceptor.RuleBasedRouterRequestInterceptor; +import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver; +import com.tencent.cloud.polaris.router.zuul.PolarisRibbonRoutingFilter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.netflix.ribbon.RibbonClients; +import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -85,4 +92,15 @@ public class RouterAutoConfiguration { public RuleBasedRouterRequestInterceptor ruleBasedRouterRequestInterceptor(PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) { return new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties); } + + @Bean(initMethod = "init") + @ConditionalOnClass(name = "org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter") + public PolarisRibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper, + RibbonCommandFactory ribbonCommandFactory, + MetadataLocalProperties metadataLocalProperties, + RouterRuleLabelResolver routerRuleLabelResolver, + List routerLabelResolvers) { + return new PolarisRibbonRoutingFilter(helper, ribbonCommandFactory, metadataLocalProperties, + routerRuleLabelResolver, routerLabelResolvers); + } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/ServletRouterLabelResolver.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/ServletRouterLabelResolver.java new file mode 100644 index 000000000..db3347378 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/ServletRouterLabelResolver.java @@ -0,0 +1,45 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.router.spi; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.core.Ordered; + +/** + * Router label resolver for spring web http request. + * + * @author jarvisxiong 2022-08-04 + */ +public interface ServletRouterLabelResolver extends Ordered { + + /** + * resolve labels from servlet http request. User can customize expression parser to extract labels. + * + * @param request the servlet http request. + * @param expressionLabelKeys the expression labels which are configured in router rule. + * @return resolved labels + */ + default Map resolve(HttpServletRequest request, Set expressionLabelKeys) { + return Collections.emptyMap(); + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilter.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilter.java new file mode 100644 index 000000000..e9e737629 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilter.java @@ -0,0 +1,195 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.router.zuul; + +import java.io.InputStream; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +import com.netflix.zuul.context.RequestContext; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.util.BeanFactoryUtils; +import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils; +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; +import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext; +import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; +import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter; +import org.springframework.core.Ordered; +import org.springframework.util.CollectionUtils; +import org.springframework.util.MultiValueMap; + +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.LOAD_BALANCER_KEY; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.RETRYABLE_KEY; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY; + +/** + * Replaces the default RibbonRoutingFilter implementation. + * + * @author jarvisxiong 2022-08-04 + */ +public class PolarisRibbonRoutingFilter extends RibbonRoutingFilter implements BeanFactoryAware { + + private static final Logger LOGGER = LoggerFactory.getLogger(PolarisRibbonRoutingFilter.class); + + private BeanFactory factory; + + private final MetadataLocalProperties metadataLocalProperties; + + private final RouterRuleLabelResolver routerRuleLabelResolver; + + private final List routerLabelResolvers; + + private boolean useServlet31 = true; + + public PolarisRibbonRoutingFilter(ProxyRequestHelper helper, + RibbonCommandFactory ribbonCommandFactory, + MetadataLocalProperties metadataLocalProperties, + RouterRuleLabelResolver routerRuleLabelResolver, + List routerLabelResolvers) { + super(helper, ribbonCommandFactory, Collections.emptyList()); + this.metadataLocalProperties = metadataLocalProperties; + this.routerRuleLabelResolver = routerRuleLabelResolver; + + if (!CollectionUtils.isEmpty(routerLabelResolvers)) { + routerLabelResolvers.sort(Comparator.comparingInt(Ordered::getOrder)); + this.routerLabelResolvers = routerLabelResolvers; + } + else { + this.routerLabelResolvers = null; + } + // To support Servlet API 3.1 we need to check if getContentLengthLong exists + // Spring 5 minimum support is 3.0, so this stays + try { + HttpServletRequest.class.getMethod("getContentLengthLong"); + } + catch (NoSuchMethodException e) { + useServlet31 = false; + } + } + + + @Override + protected RibbonCommandContext buildCommandContext(RequestContext context) { + HttpServletRequest request = context.getRequest(); + + MultiValueMap headers = this.helper + .buildZuulRequestHeaders(request); + MultiValueMap params = this.helper + .buildZuulRequestQueryParams(request); + String verb = getVerb(request); + InputStream requestEntity = getRequestBody(request); + if (request.getContentLength() < 0 && !verb.equalsIgnoreCase("GET")) { + context.setChunkedRequestBody(); + } + + String serviceId = (String) context.get(SERVICE_ID_KEY); + Boolean retryable = (Boolean) context.get(RETRYABLE_KEY); + Object loadBalancerKey = context.get(LOAD_BALANCER_KEY); + if (loadBalancerKey == null) { + // By default, use the routerContext as loadBalancerKey + loadBalancerKey = genRouterContext(request, serviceId); + } + + String uri = this.helper.buildZuulRequestURI(request); + + // remove double slashes + uri = uri.replace("//", "/"); + + long contentLength = useServlet31 ? request.getContentLengthLong() + : request.getContentLength(); + + return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params, + requestEntity, this.requestCustomizers, contentLength, loadBalancerKey); + } + + PolarisRouterContext genRouterContext(HttpServletRequest request, String serviceId) { + // local service labels + Map labels = new HashMap<>(metadataLocalProperties.getContent()); + + // labels from rule expression + Set expressionLabelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE, + MetadataContext.LOCAL_SERVICE, serviceId); + Map ruleExpressionLabels = getExpressionLabels(request, expressionLabelKeys); + if (!CollectionUtils.isEmpty(ruleExpressionLabels)) { + labels.putAll(ruleExpressionLabels); + } + + // labels from request + if (!CollectionUtils.isEmpty(routerLabelResolvers)) { + routerLabelResolvers.forEach(resolver -> { + try { + Map customResolvedLabels = resolver.resolve(request, expressionLabelKeys); + if (!CollectionUtils.isEmpty(customResolvedLabels)) { + labels.putAll(customResolvedLabels); + } + } + catch (Throwable t) { + LOGGER.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t); + } + }); + } + + // labels from downstream + Map transitiveLabels = MetadataContextHolder.get() + .getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); + labels.putAll(transitiveLabels); + + PolarisRouterContext routerContext = new PolarisRouterContext(); + + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); + + return routerContext; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.factory = beanFactory; + } + + private Map getExpressionLabels(HttpServletRequest request, Set labelKeys) { + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + return ServletExpressionLabelUtils.resolve(request, labelKeys); + } + + private void init() { + this.requestCustomizers = BeanFactoryUtils.getBeans(factory, RibbonRequestCustomizer.class); + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilterTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilterTest.java new file mode 100644 index 000000000..537e4eca5 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilterTest.java @@ -0,0 +1,373 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.router.zuul; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.netflix.client.config.CommonClientConfigKey; +import com.netflix.client.config.IClientConfig; +import com.netflix.hystrix.HystrixCommandProperties; +import com.netflix.niws.client.http.RestClient; +import com.netflix.zuul.context.RequestContext; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.cloud.polaris.loadbalancer.PolarisLoadBalancer; +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; +import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver; +import okhttp3.OkHttpClient; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; +import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient; +import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext; +import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; +import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; +import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; +import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommand; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; +import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommand; +import org.springframework.cloud.netflix.zuul.filters.route.okhttp.OkHttpRibbonCommand; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.LOAD_BALANCER_KEY; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.RETRYABLE_KEY; +import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY; + +/** + * test for {@link PolarisRibbonRoutingFilter}. + * + * @author jarvisxiong 2022-08-09 + */ +@RunWith(MockitoJUnitRunner.class) +public class PolarisRibbonRoutingFilterTest { + + private static final String callerService = "callerService"; + private static final String calleeService = "calleeService"; + private static MockedStatic mockedApplicationContextAwareUtils; + private static MockedStatic mockedMetadataContextHolder; + @Mock + private MetadataLocalProperties metadataLocalProperties; + @Mock + private RouterRuleLabelResolver routerRuleLabelResolver; + @Mock + private ServletRouterLabelResolver routerLabelResolver; + @Mock + private ProxyRequestHelper proxyRequestHelper; + @Mock + private RibbonCommandFactory ribbonCommandFactory; + @Mock + private FallbackProvider fallbackProvider; + @Mock + private PolarisLoadBalancer polarisLoadBalancer; + + @BeforeClass + public static void beforeClass() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn(callerService); + + MetadataContext metadataContext = Mockito.mock(MetadataContext.class); + + // mock transitive metadata + Map transitiveLabels = new HashMap<>(); + transitiveLabels.put("t1", "v1"); + transitiveLabels.put("t2", "v2"); + when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels); + + mockedMetadataContextHolder = Mockito.mockStatic(MetadataContextHolder.class); + mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext); + } + + @AfterClass + public static void afterClass() { + mockedApplicationContextAwareUtils.close(); + mockedMetadataContextHolder.close(); + } + + @Test + public void testGenRouterContext() { + PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter(proxyRequestHelper, + ribbonCommandFactory, metadataLocalProperties, routerRuleLabelResolver, + Lists.newArrayList(routerLabelResolver)); + + Map localMetadata = new HashMap<>(); + localMetadata.put("env", "blue"); + when(metadataLocalProperties.getContent()).thenReturn(localMetadata); + + Set expressionLabelKeys = Sets.newHashSet("${http.header.k1}", "${http.query.userid}"); + when(routerRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString(), anyString())) + .thenReturn(expressionLabelKeys); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/" + calleeService + "/users"); + request.addHeader("k1", "v1"); + request.setQueryString("userid=zhangsan"); + + Map customMetadata = new HashMap<>(); + customMetadata.put("k2", "v2"); + when(routerLabelResolver.resolve(request, expressionLabelKeys)).thenReturn(customMetadata); + + PolarisRouterContext routerContext = polarisRibbonRoutingFilter.genRouterContext(request, calleeService); + + Map routerLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS); + Assert.assertEquals("v1", routerLabels.get("${http.header.k1}")); + Assert.assertEquals("zhangsan", routerLabels.get("${http.query.userid}")); + Assert.assertEquals("blue", routerLabels.get("env")); + Assert.assertEquals("v1", routerLabels.get("t1")); + Assert.assertEquals("v2", routerLabels.get("t2")); + } + + @Test + public void testHttpCallWithoutRouter() { + ZuulProperties zuulProperties = new ZuulProperties(); + zuulProperties.setRibbonIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); + zuulProperties.setThreadPool(new ZuulProperties.HystrixThreadPool()); + + PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter( + new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, metadataLocalProperties, + routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("http://" + calleeService + "/users"); + request.addHeader("k1", "v1"); + request.setQueryString("userid=zhangsan"); + + RequestContext context = new RequestContext(); + context.setRequest(request); + context.set(SERVICE_ID_KEY, calleeService); + context.set(RETRYABLE_KEY, Boolean.FALSE); + context.set(LOAD_BALANCER_KEY, calleeService); + RequestContext.testSetCurrentContext(context); + RibbonCommandContext commandContext = polarisRibbonRoutingFilter.buildCommandContext(context); + + IClientConfig clientConfig = IClientConfig.Builder.newBuilder().build(); + RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(clientConfig, null); + client.setLoadBalancer(polarisLoadBalancer); + + HttpClientRibbonCommand command = new HttpClientRibbonCommand(calleeService, client, commandContext, + zuulProperties, fallbackProvider, clientConfig); + command.execute(); + + verify(polarisLoadBalancer).chooseServer(calleeService); + verify(metadataLocalProperties, times(0)).getContent(); + } + + @Test + public void testRestCallWithoutRouter() { + ZuulProperties zuulProperties = new ZuulProperties(); + zuulProperties.setRibbonIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); + zuulProperties.setThreadPool(new ZuulProperties.HystrixThreadPool()); + + PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter( + new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, metadataLocalProperties, + routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("http://" + calleeService + "/users"); + request.addHeader("k1", "v1"); + request.setQueryString("userid=zhangsan"); + request.setMethod("GET"); + + RequestContext context = new RequestContext(); + context.setRequest(request); + context.set(SERVICE_ID_KEY, calleeService); + context.set(RETRYABLE_KEY, Boolean.FALSE); + context.set(LOAD_BALANCER_KEY, calleeService); + RequestContext.testSetCurrentContext(context); + RibbonCommandContext commandContext = polarisRibbonRoutingFilter.buildCommandContext(context); + + IClientConfig clientConfig = IClientConfig.Builder.newBuilder().build(); + RestClient restClient = new RestClient(polarisLoadBalancer); + + RestClientRibbonCommand command = new RestClientRibbonCommand(calleeService, restClient, commandContext, + zuulProperties, fallbackProvider, clientConfig); + command.execute(); + + // RestClient not use loadBalancerKey + verify(polarisLoadBalancer).chooseServer(null); + verify(metadataLocalProperties, times(0)).getContent(); + } + + @Test + public void testOkHttpCallWithoutRouter() { + ZuulProperties zuulProperties = new ZuulProperties(); + zuulProperties.setRibbonIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); + zuulProperties.setThreadPool(new ZuulProperties.HystrixThreadPool()); + + PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter( + new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, metadataLocalProperties, + routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("http://" + calleeService + "/users"); + request.addHeader("k1", "v1"); + request.setQueryString("userid=zhangsan"); + + RequestContext context = new RequestContext(); + context.setRequest(request); + context.set(SERVICE_ID_KEY, calleeService); + context.set(RETRYABLE_KEY, Boolean.FALSE); + context.set(LOAD_BALANCER_KEY, calleeService); + RequestContext.testSetCurrentContext(context); + RibbonCommandContext commandContext = polarisRibbonRoutingFilter.buildCommandContext(context); + + IClientConfig clientConfig = IClientConfig.Builder.newBuilder().build(); + OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(new OkHttpClient(), clientConfig, null); + client.setLoadBalancer(polarisLoadBalancer); + + OkHttpRibbonCommand command = new OkHttpRibbonCommand(calleeService, client, commandContext, zuulProperties, + fallbackProvider, clientConfig); + command.execute(); + + verify(polarisLoadBalancer).chooseServer(calleeService); + verify(metadataLocalProperties, times(0)).getContent(); + } + + @Test + public void testHttpCallWithRouter() { + ZuulProperties zuulProperties = new ZuulProperties(); + zuulProperties.setRibbonIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); + zuulProperties.setThreadPool(new ZuulProperties.HystrixThreadPool()); + + PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter( + new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, metadataLocalProperties, + routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("http://" + calleeService + "/users"); + request.addHeader("k1", "v1"); + request.setQueryString("userid=zhangsan"); + + RequestContext context = new RequestContext(); + context.setRequest(request); + context.set(SERVICE_ID_KEY, calleeService); + context.set(RETRYABLE_KEY, Boolean.FALSE); + PolarisRouterContext routerContext = polarisRibbonRoutingFilter.genRouterContext(request, calleeService); + context.set(LOAD_BALANCER_KEY, routerContext); + RequestContext.testSetCurrentContext(context); + RibbonCommandContext commandContext = polarisRibbonRoutingFilter.buildCommandContext(context); + + IClientConfig clientConfig = IClientConfig.Builder.newBuilder().build(); + RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(clientConfig, null); + client.setLoadBalancer(polarisLoadBalancer); + + HttpClientRibbonCommand command = new HttpClientRibbonCommand(calleeService, client, commandContext, + zuulProperties, fallbackProvider, clientConfig); + command.execute(); + verify(polarisLoadBalancer).chooseServer(routerContext); + verify(metadataLocalProperties, times(1)).getContent(); + } + + @Test + public void testRestCallWithRouter() { + ZuulProperties zuulProperties = new ZuulProperties(); + zuulProperties.setRibbonIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); + zuulProperties.setThreadPool(new ZuulProperties.HystrixThreadPool()); + + PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter( + new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, metadataLocalProperties, + routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("http://" + calleeService + "/users"); + request.addHeader("k1", "v1"); + request.setQueryString("userid=zhangsan"); + request.setMethod("GET"); + + RequestContext context = new RequestContext(); + context.setRequest(request); + context.set(SERVICE_ID_KEY, calleeService); + context.set(RETRYABLE_KEY, Boolean.FALSE); + PolarisRouterContext routerContext = polarisRibbonRoutingFilter.genRouterContext(request, calleeService); + context.set(LOAD_BALANCER_KEY, routerContext); + RequestContext.testSetCurrentContext(context); + RibbonCommandContext commandContext = polarisRibbonRoutingFilter.buildCommandContext(context); + + IClientConfig clientConfig = IClientConfig.Builder.newBuilder().build(); + RestClient restClient = new RestClient(polarisLoadBalancer); + + RestClientRibbonCommand command = new RestClientRibbonCommand(calleeService, restClient, commandContext, + zuulProperties, fallbackProvider, clientConfig); + command.execute(); + + // RestClient not use loadBalancerKey + verify(polarisLoadBalancer).chooseServer(null); + verify(metadataLocalProperties, times(1)).getContent(); + } + + @Test + public void testOkHttpCallWithRouter() { + ZuulProperties zuulProperties = new ZuulProperties(); + zuulProperties.setRibbonIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); + zuulProperties.setThreadPool(new ZuulProperties.HystrixThreadPool()); + + PolarisRibbonRoutingFilter polarisRibbonRoutingFilter = new PolarisRibbonRoutingFilter( + new ProxyRequestHelper(zuulProperties), ribbonCommandFactory, metadataLocalProperties, + routerRuleLabelResolver, Lists.newArrayList(routerLabelResolver)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("http://" + calleeService + "/users"); + request.addHeader("k1", "v1"); + request.setQueryString("userid=zhangsan"); + request.setMethod("GET"); + + RequestContext context = new RequestContext(); + context.setRequest(request); + context.set(SERVICE_ID_KEY, calleeService); + context.set(RETRYABLE_KEY, Boolean.FALSE); + PolarisRouterContext routerContext = polarisRibbonRoutingFilter.genRouterContext(request, calleeService); + context.set(LOAD_BALANCER_KEY, routerContext); + RequestContext.testSetCurrentContext(context); + RibbonCommandContext commandContext = polarisRibbonRoutingFilter.buildCommandContext(context); + + IClientConfig clientConfig = IClientConfig.Builder.newBuilder().build(); + // Retry once by default, so close retry + clientConfig.set(CommonClientConfigKey.MaxAutoRetriesNextServer, 0); + + OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(new OkHttpClient(), clientConfig, null); + client.setLoadBalancer(polarisLoadBalancer); + + OkHttpRibbonCommand command = new OkHttpRibbonCommand(calleeService, client, commandContext, zuulProperties, + fallbackProvider, clientConfig); + command.execute(); + + verify(polarisLoadBalancer).chooseServer(routerContext); + verify(metadataLocalProperties, times(1)).getContent(); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml index a0956f9ae..f4dda3aa8 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml @@ -15,6 +15,11 @@ spring: router: feature-env: enabled: true + metadata: + content: + a: 1 + transitive: + - a polaris: address: grpc://183.47.111.80:8091 namespace: default diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-zuul-service/pom.xml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-zuul-service/pom.xml index 2e4060d28..b937a3046 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-zuul-service/pom.xml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-zuul-service/pom.xml @@ -24,6 +24,11 @@ com.tencent.cloud + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-router + + com.tencent.cloud spring-cloud-starter-tencent-metadata-transfer