feat: support spring-retry and feign config refresh and feign eager load support schema (#1650)
parent
726ce70a2d
commit
4894ab83c0
@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.polaris.circuitbreaker.beanprocessor;
|
||||
|
||||
import com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate.PolarisLoadBalancerInterceptor;
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* LoadBalancerInterceptorBeanPostProcessor is used to wrap the default LoadBalancerInterceptor implementation and returns a custom PolarisLoadBalancerInterceptor.
|
||||
*
|
||||
* @author Shedfree Wu
|
||||
*/
|
||||
public class LoadBalancerInterceptorBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, Ordered {
|
||||
/**
|
||||
* The order of the bean post processor. if user want to wrap it(CustomLoadBalancerInterceptor -> PolarisLoadBalancerInterceptor), CustomLoadBalancerInterceptorBeanPostProcessor's order should be bigger than ${@link POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER}.
|
||||
*/
|
||||
public static final int POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER = 0;
|
||||
|
||||
private BeanFactory factory;
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
|
||||
this.factory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
|
||||
if (bean instanceof LoadBalancerInterceptor) {
|
||||
// Support rest template router.
|
||||
// Replaces the default LoadBalancerInterceptor implementation and returns a custom PolarisLoadBalancerInterceptor
|
||||
LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class);
|
||||
LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class);
|
||||
EnhancedPluginRunner pluginRunner = this.factory.getBean(EnhancedPluginRunner.class);
|
||||
return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory, pluginRunner);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER;
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import com.tencent.cloud.rpc.enhancement.instrument.resttemplate.EnhancedRestTemplateWrapInterceptor;
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* PolarisLoadBalancerInterceptor is a wrapper of LoadBalancerInterceptor.
|
||||
*
|
||||
* @author Shedfree Wu
|
||||
*/
|
||||
public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
|
||||
|
||||
private final LoadBalancerClient loadBalancer;
|
||||
private final LoadBalancerRequestFactory requestFactory;
|
||||
|
||||
private final EnhancedPluginRunner enhancedPluginRunner;
|
||||
|
||||
public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory,
|
||||
EnhancedPluginRunner enhancedPluginRunner) {
|
||||
super(loadBalancer, requestFactory);
|
||||
this.loadBalancer = loadBalancer;
|
||||
this.requestFactory = requestFactory;
|
||||
this.enhancedPluginRunner = enhancedPluginRunner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
|
||||
final URI originalUri = request.getURI();
|
||||
String peerServiceName = originalUri.getHost();
|
||||
Assert.state(peerServiceName != null,
|
||||
"Request URI does not contain a valid hostname: " + originalUri);
|
||||
|
||||
if (enhancedPluginRunner != null) {
|
||||
EnhancedRestTemplateWrapInterceptor enhancedRestTemplateWrapInterceptor = new EnhancedRestTemplateWrapInterceptor(enhancedPluginRunner, loadBalancer);
|
||||
return enhancedRestTemplateWrapInterceptor.intercept(request, peerServiceName, this.requestFactory.createRequest(request, body, execution));
|
||||
}
|
||||
else {
|
||||
return super.intercept(request, body, execution);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test for ${@link PolarisLoadBalancerInterceptor}.
|
||||
*
|
||||
* @author Shedfree Wu
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PolarisLoadBalancerInterceptorTest {
|
||||
|
||||
@Mock
|
||||
private LoadBalancerClient loadBalancer;
|
||||
|
||||
@Mock
|
||||
private LoadBalancerRequestFactory requestFactory;
|
||||
|
||||
@Mock
|
||||
private EnhancedPluginRunner enhancedPluginRunner;
|
||||
|
||||
@Mock
|
||||
private HttpRequest request;
|
||||
|
||||
@Mock
|
||||
private ClientHttpRequestExecution execution;
|
||||
|
||||
private PolarisLoadBalancerInterceptor interceptor;
|
||||
private byte[] body;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
body = "test body".getBytes();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInterceptWithEnhancedPlugin() throws IOException {
|
||||
// Arrange
|
||||
ClientHttpResponse mockResponse = mock(ClientHttpResponse.class);
|
||||
interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, enhancedPluginRunner);
|
||||
URI uri = URI.create("http://test-service/path");
|
||||
when(request.getURI()).thenReturn(uri);
|
||||
when(loadBalancer.execute(any(), any())).thenReturn(mockResponse);
|
||||
|
||||
// Act
|
||||
ClientHttpResponse response = interceptor.intercept(request, body, execution);
|
||||
|
||||
// Assert
|
||||
Assertions.assertTrue(Objects.equals(mockResponse, response) || response instanceof PolarisCircuitBreakerHttpResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInterceptWithoutEnhancedPlugin() throws IOException {
|
||||
// Arrange
|
||||
ClientHttpResponse mockResponse = mock(ClientHttpResponse.class);
|
||||
interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, null);
|
||||
URI uri = URI.create("http://test-service/path");
|
||||
when(request.getURI()).thenReturn(uri);
|
||||
when(loadBalancer.execute(any(), any())).thenReturn(mockResponse);
|
||||
|
||||
// Act
|
||||
ClientHttpResponse response = interceptor.intercept(request, body, execution);
|
||||
|
||||
// Assert
|
||||
Assertions.assertEquals(mockResponse, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInterceptWithInvalidUri() {
|
||||
// Arrange
|
||||
interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, enhancedPluginRunner);
|
||||
when(request.getURI()).thenReturn(URI.create("http:///path")); // Invalid URI without host
|
||||
|
||||
// Act & Assert
|
||||
Exception exception = Assertions.assertThrows(IllegalStateException.class, () -> {
|
||||
interceptor.intercept(request, body, execution);
|
||||
});
|
||||
Assertions.assertTrue(exception.getMessage().contains("Request URI does not contain a valid hostname"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConstructor() {
|
||||
// Act
|
||||
interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, enhancedPluginRunner);
|
||||
|
||||
// Assert
|
||||
Assertions.assertNotNull(interceptor);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>tsf-example</artifactId>
|
||||
<groupId>com.tencent.cloud</groupId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>consumer-demo-retry</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.tencent.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-tencent-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.tsf.demo.consumer;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.tsf.annotation.EnableTsf;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableFeignClients // 使用Feign微服务调用时请启用
|
||||
@EnableTsf
|
||||
public class ConsumerApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ConsumerApplication.class, args);
|
||||
}
|
||||
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.tsf.demo.consumer.controller;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import com.tencent.cloud.common.util.PolarisCompletableFutureUtils;
|
||||
import com.tencent.cloud.tsf.demo.consumer.proxy.ProviderDemoService;
|
||||
import com.tencent.cloud.tsf.demo.consumer.proxy.ProviderService;
|
||||
import com.tencent.polaris.api.utils.StringUtils;
|
||||
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.tsf.core.TsfContext;
|
||||
import org.springframework.tsf.core.entity.Tag;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@RestController
|
||||
public class ConsumerController {
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
@Autowired
|
||||
private ProviderService providerService;
|
||||
@Autowired
|
||||
private ProviderDemoService providerDemoService;
|
||||
|
||||
@RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET)
|
||||
public String restProvider(@PathVariable String str,
|
||||
@RequestParam(required = false) String tagName,
|
||||
@RequestParam(required = false) String tagValue) {
|
||||
if (StringUtils.isNotBlank(tagName)) {
|
||||
TsfContext.putTag(tagName, tagValue);
|
||||
}
|
||||
TsfContext.putTag("operation", "rest");
|
||||
Map<String, String> mTags = new HashMap<>();
|
||||
mTags.put("rest-trace-key1", "value1");
|
||||
mTags.put("rest-trace-key2", "value2");
|
||||
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
|
||||
try {
|
||||
return restTemplate.getForObject("http://provider-demo/echo/" + str, String.class);
|
||||
}
|
||||
catch (CallAbortedException callAbortedException) {
|
||||
return callAbortedException.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/echo-slow-rest/{str}", method = RequestMethod.GET)
|
||||
public String restSlowProvider(@PathVariable String str,
|
||||
@RequestParam(required = false) String tagName,
|
||||
@RequestParam(required = false) String tagValue) {
|
||||
if (StringUtils.isNotBlank(tagName)) {
|
||||
TsfContext.putTag(tagName, tagValue);
|
||||
}
|
||||
TsfContext.putTag("operation", "rest");
|
||||
Map<String, String> mTags = new HashMap<>();
|
||||
mTags.put("rest-trace-key1", "value1");
|
||||
mTags.put("rest-trace-key2", "value2");
|
||||
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
|
||||
try {
|
||||
return restTemplate.getForObject("http://provider-demo/echo/slow/" + str, String.class);
|
||||
}
|
||||
catch (CallAbortedException callAbortedException) {
|
||||
return callAbortedException.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(value = "/echo-rest-async/{str}", method = RequestMethod.GET)
|
||||
public String restAsync(@PathVariable String str,
|
||||
@RequestParam(required = false) String tagName,
|
||||
@RequestParam(required = false) String tagValue) throws ExecutionException, InterruptedException {
|
||||
if (StringUtils.isNotBlank(tagName)) {
|
||||
TsfContext.putTag(tagName, tagValue);
|
||||
}
|
||||
TsfContext.putTag("operation", "rest");
|
||||
Map<String, String> mTags = new HashMap<>();
|
||||
mTags.put("rest-trace-key1", "value1");
|
||||
mTags.put("rest-trace-key2", "value2");
|
||||
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
|
||||
CompletableFuture<String> echoFuture = PolarisCompletableFutureUtils.supplyAsync(() -> {
|
||||
try {
|
||||
return restTemplate.getForObject("http://provider-demo/echo/" + str, String.class);
|
||||
}
|
||||
catch (CallAbortedException callAbortedException) {
|
||||
return callAbortedException.getMessage();
|
||||
}
|
||||
});
|
||||
return echoFuture.get();
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET)
|
||||
public String feignProvider(@PathVariable String str,
|
||||
@RequestParam(required = false) String tagName,
|
||||
@RequestParam(required = false) String tagValue) {
|
||||
if (StringUtils.isNotBlank(tagName)) {
|
||||
TsfContext.putTag(tagName, tagValue);
|
||||
}
|
||||
TsfContext.putTag("operation", "feign");
|
||||
Map<String, String> mTags = new HashMap<>();
|
||||
mTags.put("feign-trace-key1", "value1");
|
||||
mTags.put("feign-trace-key2", "value2");
|
||||
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
|
||||
try {
|
||||
return providerDemoService.echo(str);
|
||||
}
|
||||
catch (CallAbortedException callAbortedException) {
|
||||
return callAbortedException.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/echo-slow-feign/{str}", method = RequestMethod.GET)
|
||||
public String feignSlowProvider(@PathVariable String str,
|
||||
@RequestParam(required = false) String tagName,
|
||||
@RequestParam(required = false) String tagValue,
|
||||
@RequestParam(required = false) String delay) {
|
||||
if (StringUtils.isNotBlank(tagName)) {
|
||||
TsfContext.putTag(tagName, tagValue);
|
||||
}
|
||||
TsfContext.putTag("operation", "feign");
|
||||
Map<String, String> mTags = new HashMap<>();
|
||||
mTags.put("feign-trace-key1", "value1");
|
||||
mTags.put("feign-trace-key2", "value2");
|
||||
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
|
||||
int sleepTime = delay == null ? 1000 : Integer.parseInt(delay);
|
||||
try {
|
||||
return providerDemoService.echoSlow(str, sleepTime);
|
||||
}
|
||||
catch (CallAbortedException callAbortedException) {
|
||||
return callAbortedException.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/echo-feign-url/{str}", method = RequestMethod.GET)
|
||||
public String feignUrlProvider(@PathVariable String str,
|
||||
@RequestParam(required = false) String tagName,
|
||||
@RequestParam(required = false) String tagValue) {
|
||||
if (StringUtils.isNotBlank(tagName)) {
|
||||
TsfContext.putTag(tagName, tagValue);
|
||||
}
|
||||
TsfContext.putTag("operation", "feignUrl");
|
||||
Map<String, String> mTags = new HashMap<>();
|
||||
mTags.put("feignUrl-trace-key1", "value1");
|
||||
mTags.put("feignUrl-trace-key2", "value2");
|
||||
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
|
||||
return providerService.echo(str);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.tsf.demo.consumer.controller;
|
||||
|
||||
import com.tencent.polaris.api.utils.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.tsf.core.TsfContext;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@RestController
|
||||
public class SdkBaseTest {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SdkBaseTest.class);
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
// 调用一次provider接口
|
||||
@RequestMapping(value = "/echo-once/{str}", method = RequestMethod.GET)
|
||||
public String restOnceProvider(@PathVariable String str,
|
||||
@RequestParam(required = false) String tagName,
|
||||
@RequestParam(required = false) String tagValue) {
|
||||
if (!StringUtils.isEmpty(tagName)) {
|
||||
TsfContext.putTag(tagName, tagValue);
|
||||
}
|
||||
LOG.info("start call echo-once");
|
||||
String result = restTemplate.getForObject("http://provider-demo/echo/" + str, String.class);
|
||||
LOG.info("end call echo-once, the result is : " + result);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.tsf.demo.consumer.entity;
|
||||
|
||||
/**
|
||||
* 用户自定义 Metadata.
|
||||
*/
|
||||
public class CustomMetadata {
|
||||
|
||||
private String name;
|
||||
private String value;
|
||||
|
||||
public CustomMetadata(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.tsf.demo.consumer.proxy;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@FeignClient(name = "${provider.name:provider-demo}")
|
||||
public interface ProviderDemoService {
|
||||
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
|
||||
String echo(@PathVariable("str") String str);
|
||||
|
||||
@RequestMapping(value = "/echo/error/{str}", method = RequestMethod.GET)
|
||||
String echoError(@PathVariable("str") String str);
|
||||
|
||||
@RequestMapping(value = "/echo/slow/{str}", method = RequestMethod.GET)
|
||||
String echoSlow(@PathVariable("str") String str, @RequestParam("delay") int delay);
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.tsf.demo.consumer.proxy;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* 测试通过URL配置FeignClient
|
||||
* 使用时修改provider-ip:provider-port配置
|
||||
*/
|
||||
@FeignClient(name = "provider", url = "http://127.0.0.1:18081", fallback = FeignClientFallback.class)
|
||||
public interface ProviderService {
|
||||
|
||||
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
|
||||
String echo(@PathVariable("str") String str);
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
class FeignClientFallback implements ProviderService {
|
||||
@Override
|
||||
public String echo(String str) {
|
||||
return "tsf-fault-tolerance-" + str;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
server:
|
||||
port: 18083
|
||||
spring:
|
||||
application:
|
||||
name: consumer-demo
|
||||
config:
|
||||
import: optional:polaris
|
||||
|
||||
feign:
|
||||
tsf:
|
||||
enabled: true
|
||||
|
||||
#本地测试时打开
|
||||
#tsf_namespace_id: default_namespace
|
||||
|
||||
logging:
|
||||
file:
|
||||
name: /tsf-demo-logs/${spring.application.name}/root.log
|
||||
level:
|
||||
root: INFO
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.rpc.enhancement.beanprocessor;
|
||||
|
||||
import com.tencent.cloud.rpc.enhancement.instrument.resttemplate.PolarisBlockingLoadBalancerClient;
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* BlockingLoadBalancerClientBeanPostProcessor is used to wrap the default BlockingLoadBalancerClient implementation and returns a custom PolarisBlockingLoadBalancerClient.
|
||||
*
|
||||
* @author Shedfree Wu
|
||||
*/
|
||||
public class BlockingLoadBalancerClientBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, Ordered {
|
||||
/**
|
||||
* The order of the bean post processor. If user wants to wrap it(CustomBlockingLoadBalancerClient -> PolarisBlockingLoadBalancerClient), CustomBlockingLoadBalancerClientBeanPostProcessor's order should be bigger than ${@link ORDER}.
|
||||
*/
|
||||
public static final int ORDER = 0;
|
||||
|
||||
private BeanFactory factory;
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
|
||||
this.factory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
|
||||
if (bean instanceof BlockingLoadBalancerClient) {
|
||||
BlockingLoadBalancerClient delegate = (BlockingLoadBalancerClient) bean;
|
||||
ReactiveLoadBalancer.Factory<ServiceInstance> requestFactory = this.factory.getBean(ReactiveLoadBalancer.Factory.class);
|
||||
EnhancedPluginRunner pluginRunner = this.factory.getBean(EnhancedPluginRunner.class);
|
||||
return new PolarisBlockingLoadBalancerClient(requestFactory, delegate, pluginRunner);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return ORDER;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.rpc.enhancement.instrument.resttemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerUtils;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.http.HttpRequest;
|
||||
|
||||
/**
|
||||
* PolarisBlockingLoadBalancerClient is a wrapper of BlockingLoadBalancerClient.
|
||||
*
|
||||
* @author Shedfree Wu
|
||||
*/
|
||||
public class PolarisBlockingLoadBalancerClient extends BlockingLoadBalancerClient {
|
||||
|
||||
private BlockingLoadBalancerClient delegate;
|
||||
|
||||
private final EnhancedPluginRunner enhancedPluginRunner;
|
||||
|
||||
public PolarisBlockingLoadBalancerClient(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory,
|
||||
BlockingLoadBalancerClient delegate, EnhancedPluginRunner enhancedPluginRunner) {
|
||||
super(loadBalancerClientFactory);
|
||||
this.delegate = delegate;
|
||||
this.enhancedPluginRunner = enhancedPluginRunner;
|
||||
}
|
||||
|
||||
/**
|
||||
* common rest template.
|
||||
*/
|
||||
@Override
|
||||
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
|
||||
HttpRequest httpRequest = LoadBalancerUtils.getHttpRequestIfAvailable(request);
|
||||
if (httpRequest == null || enhancedPluginRunner == null) {
|
||||
return delegate.execute(serviceId, request);
|
||||
}
|
||||
EnhancedRestTemplateWrapInterceptor enhancedRestTemplateWrapInterceptor = new EnhancedRestTemplateWrapInterceptor(enhancedPluginRunner, delegate);
|
||||
return enhancedRestTemplateWrapInterceptor.intercept(httpRequest, serviceId, null, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* retry rest template.
|
||||
*/
|
||||
@Override
|
||||
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
|
||||
HttpRequest httpRequest = LoadBalancerUtils.getHttpRequestIfAvailable(LoadBalancerUtils.getDelegateLoadBalancerRequestIfAvailable(request));
|
||||
if (httpRequest == null || serviceInstance == null || enhancedPluginRunner == null) {
|
||||
return delegate.execute(serviceId, serviceInstance, request);
|
||||
}
|
||||
EnhancedRestTemplateWrapInterceptor enhancedRestTemplateWrapInterceptor = new EnhancedRestTemplateWrapInterceptor(enhancedPluginRunner, delegate);
|
||||
return enhancedRestTemplateWrapInterceptor.intercept(httpRequest, serviceId, serviceInstance, request);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.http.HttpRequest;
|
||||
|
||||
public final class LoadBalancerUtils {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LoadBalancerUtils.class);
|
||||
|
||||
private LoadBalancerUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* if request is a BlockingLoadBalancerRequest, return its HttpRequest.
|
||||
*/
|
||||
public static HttpRequest getHttpRequestIfAvailable(LoadBalancerRequest<?> request) {
|
||||
if (request instanceof BlockingLoadBalancerRequest) {
|
||||
BlockingLoadBalancerRequest blockingRequest = (BlockingLoadBalancerRequest) request;
|
||||
return blockingRequest.getHttpRequest();
|
||||
}
|
||||
else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("LoadBalancerRequest is not a BlockingLoadBalancerRequest, request:{}", request);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if request is a LoadBalancerRequestAdapter(RetryLoadBalancerInterceptor), return its delegate.
|
||||
*/
|
||||
public static LoadBalancerRequest<?> getDelegateLoadBalancerRequestIfAvailable(LoadBalancerRequest<?> request) {
|
||||
if (!(request instanceof LoadBalancerRequestAdapter)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("LoadBalancerRequest is not a LoadBalancerRequestAdapter, request:{}", request);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
try {
|
||||
Field delegateField = LoadBalancerRequestAdapter.class.getDeclaredField("delegate");
|
||||
delegateField.setAccessible(true);
|
||||
return (LoadBalancerRequest<?>) delegateField.get(request);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// ignore
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to get delegate from LoadBalancerRequestAdapter, request:{}", request, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
52
spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java → spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java
52
spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java → spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java
@ -1,302 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.rpc.enhancement.instrument.resttemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
|
||||
import com.tencent.cloud.common.constant.ContextConstant;
|
||||
import com.tencent.cloud.common.metadata.MetadataContextHolder;
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
|
||||
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
|
||||
import com.tencent.polaris.metadata.core.MetadataType;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.ServiceRequestWrapper;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test for {@link EnhancedRestTemplateWrapInterceptor}.
|
||||
*
|
||||
* @author Shedfree Wu
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class EnhancedRestTemplateWrapInterceptorTest {
|
||||
|
||||
@Mock
|
||||
private EnhancedPluginRunner pluginRunner;
|
||||
|
||||
@Mock
|
||||
private LoadBalancerClient delegate;
|
||||
|
||||
@Mock
|
||||
private HttpRequest request;
|
||||
|
||||
@Mock
|
||||
private ClientHttpResponse response;
|
||||
|
||||
@Mock
|
||||
private ServiceRequestWrapper serviceRequestWrapper;
|
||||
|
||||
@Mock
|
||||
private ServiceInstance localServiceInstance;
|
||||
|
||||
private EnhancedRestTemplateWrapInterceptor interceptor;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
interceptor = new EnhancedRestTemplateWrapInterceptor(pluginRunner, delegate);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInterceptWithNormalRequest() throws IOException {
|
||||
// Arrange
|
||||
URI uri = URI.create("http://test-service/api");
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("test-header", "test-value");
|
||||
|
||||
when(request.getURI()).thenReturn(uri);
|
||||
when(request.getHeaders()).thenReturn(headers);
|
||||
when(request.getMethod()).thenReturn(HttpMethod.GET);
|
||||
when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance);
|
||||
when(delegate.execute(any(), any())).thenReturn(response);
|
||||
when(response.getRawStatusCode()).thenReturn(200);
|
||||
when(response.getHeaders()).thenReturn(new HttpHeaders());
|
||||
|
||||
// Act
|
||||
interceptor.intercept(request, "test-service", mock(LoadBalancerRequest.class));
|
||||
|
||||
// Assert
|
||||
verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class));
|
||||
|
||||
// Verify context setup
|
||||
ArgumentCaptor<EnhancedPluginContext> contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class);
|
||||
verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture());
|
||||
|
||||
EnhancedPluginContext capturedContext = contextCaptor.getValue();
|
||||
Assertions.assertEquals(uri, capturedContext.getRequest().getUrl());
|
||||
Assertions.assertEquals(uri, capturedContext.getRequest().getServiceUrl());
|
||||
Assertions.assertEquals(headers, capturedContext.getRequest().getHttpHeaders());
|
||||
Assertions.assertEquals(HttpMethod.GET, capturedContext.getRequest().getHttpMethod());
|
||||
Assertions.assertEquals(localServiceInstance, capturedContext.getLocalServiceInstance());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInterceptWithServiceRequestWrapper() throws IOException {
|
||||
// Arrange
|
||||
URI originalUri = URI.create("http://original-service/api");
|
||||
URI wrappedUri = URI.create("http://wrapped-service/api");
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
when(serviceRequestWrapper.getURI()).thenReturn(wrappedUri);
|
||||
when(serviceRequestWrapper.getRequest()).thenReturn(request);
|
||||
when(serviceRequestWrapper.getHeaders()).thenReturn(headers);
|
||||
when(serviceRequestWrapper.getMethod()).thenReturn(HttpMethod.POST);
|
||||
when(request.getURI()).thenReturn(originalUri);
|
||||
when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance);
|
||||
when(delegate.execute(any(), any())).thenReturn(response);
|
||||
when(response.getRawStatusCode()).thenReturn(200);
|
||||
when(response.getHeaders()).thenReturn(new HttpHeaders());
|
||||
|
||||
// Act
|
||||
interceptor.intercept(serviceRequestWrapper, "test-service", mock(LoadBalancerRequest.class));
|
||||
|
||||
// Assert
|
||||
verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class));
|
||||
|
||||
ArgumentCaptor<EnhancedPluginContext> contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class);
|
||||
verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture());
|
||||
|
||||
EnhancedPluginContext capturedContext = contextCaptor.getValue();
|
||||
Assertions.assertEquals(wrappedUri, capturedContext.getRequest().getUrl());
|
||||
Assertions.assertEquals(originalUri, capturedContext.getRequest().getServiceUrl());
|
||||
Assertions.assertEquals(serviceRequestWrapper, capturedContext.getOriginRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInterceptWithFallback() throws IOException {
|
||||
// Arrange
|
||||
URI originalUri = URI.create("http://original-service/api");
|
||||
URI wrappedUri = URI.create("http://wrapped-service/api");
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
CallAbortedException abortedException = new CallAbortedException("test-error", null);
|
||||
|
||||
when(serviceRequestWrapper.getURI()).thenReturn(wrappedUri);
|
||||
when(serviceRequestWrapper.getRequest()).thenReturn(request);
|
||||
when(serviceRequestWrapper.getHeaders()).thenReturn(headers);
|
||||
when(serviceRequestWrapper.getMethod()).thenReturn(HttpMethod.POST);
|
||||
when(request.getURI()).thenReturn(originalUri);
|
||||
when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance);
|
||||
doThrow(abortedException)
|
||||
.when(pluginRunner)
|
||||
.run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class));
|
||||
|
||||
Object fallbackResponse = new MockClientHttpResponse();
|
||||
MetadataContextHolder.get().getMetadataContainer(MetadataType.APPLICATION, true).
|
||||
putMetadataObjectValue(ContextConstant.CircuitBreaker.CIRCUIT_BREAKER_FALLBACK_HTTP_RESPONSE, fallbackResponse);
|
||||
|
||||
// Act
|
||||
interceptor.intercept(serviceRequestWrapper, "test-service", mock(LoadBalancerRequest.class));
|
||||
|
||||
// Assert
|
||||
verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class));
|
||||
|
||||
ArgumentCaptor<EnhancedPluginContext> contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class);
|
||||
verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture());
|
||||
|
||||
EnhancedPluginContext capturedContext = contextCaptor.getValue();
|
||||
Assertions.assertEquals(wrappedUri, capturedContext.getRequest().getUrl());
|
||||
Assertions.assertEquals(originalUri, capturedContext.getRequest().getServiceUrl());
|
||||
Assertions.assertEquals(serviceRequestWrapper, capturedContext.getOriginRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInterceptWithNoFallback() {
|
||||
// Arrange
|
||||
URI originalUri = URI.create("http://original-service/api");
|
||||
URI wrappedUri = URI.create("http://wrapped-service/api");
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
CallAbortedException abortedException = new CallAbortedException("test-error", null);
|
||||
|
||||
when(serviceRequestWrapper.getURI()).thenReturn(wrappedUri);
|
||||
when(serviceRequestWrapper.getRequest()).thenReturn(request);
|
||||
when(serviceRequestWrapper.getHeaders()).thenReturn(headers);
|
||||
when(serviceRequestWrapper.getMethod()).thenReturn(HttpMethod.POST);
|
||||
when(request.getURI()).thenReturn(originalUri);
|
||||
when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance);
|
||||
doThrow(abortedException)
|
||||
.when(pluginRunner)
|
||||
.run(any(), any());
|
||||
// Act
|
||||
Assertions.assertThrows(CallAbortedException.class, () -> {
|
||||
interceptor.intercept(serviceRequestWrapper, "test-service", mock(LoadBalancerRequest.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInterceptWithNullLocalServiceInstance() throws IOException {
|
||||
// Arrange
|
||||
URI uri = URI.create("http://test-service/api");
|
||||
when(request.getURI()).thenReturn(uri);
|
||||
when(request.getHeaders()).thenReturn(new HttpHeaders());
|
||||
when(request.getMethod()).thenReturn(HttpMethod.GET);
|
||||
when(pluginRunner.getLocalServiceInstance()).thenReturn(null);
|
||||
when(delegate.execute(any(), any())).thenReturn(response);
|
||||
when(response.getRawStatusCode()).thenReturn(200);
|
||||
when(response.getHeaders()).thenReturn(new HttpHeaders());
|
||||
|
||||
// Act
|
||||
interceptor.intercept(request, "test-service", mock(LoadBalancerRequest.class));
|
||||
|
||||
// Assert
|
||||
verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class));
|
||||
|
||||
ArgumentCaptor<EnhancedPluginContext> contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class);
|
||||
verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture());
|
||||
|
||||
EnhancedPluginContext capturedContext = contextCaptor.getValue();
|
||||
assertThat(capturedContext.getLocalServiceInstance()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExceptionHandling() throws IOException {
|
||||
// Arrange
|
||||
LoadBalancerRequest<ClientHttpResponse> loadBalancerRequest = mock(LoadBalancerRequest.class);
|
||||
IOException expectedException = new IOException("Test exception");
|
||||
when(delegate.execute(anyString(), any(LoadBalancerRequest.class)))
|
||||
.thenThrow(expectedException);
|
||||
|
||||
// Act & Assert
|
||||
Exception actualException = Assertions.assertThrows(IOException.class, () -> {
|
||||
interceptor.intercept(request, "test-service", loadBalancerRequest);
|
||||
});
|
||||
|
||||
// Verify exception handling
|
||||
verify(pluginRunner, times(1))
|
||||
.run(eq(EnhancedPluginType.Client.EXCEPTION), any(EnhancedPluginContext.class));
|
||||
|
||||
// Verify finally block is executed
|
||||
verify(pluginRunner, times(1))
|
||||
.run(eq(EnhancedPluginType.Client.FINALLY), any(EnhancedPluginContext.class));
|
||||
|
||||
// Verify the thrown exception is the same
|
||||
Assertions.assertEquals(expectedException, actualException);
|
||||
}
|
||||
|
||||
static class MockClientHttpResponse implements ClientHttpResponse {
|
||||
@Override
|
||||
public HttpStatus getStatusCode() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawStatusCode() throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatusText() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getBody() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue