feat:support traffic mirroring. (#1687)
Co-authored-by: Haotian Zhang <928016560@qq.com>pull/1688/head
parent
a01e79d279
commit
c4e78c9ad8
@ -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.quickstart.callee.pojo;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
public class User {
|
||||
|
||||
private String name;
|
||||
|
||||
private int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"name='" + name + '\'' +
|
||||
", age=" + age +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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.quickstart.callee.pojo;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
public class User {
|
||||
|
||||
private String name;
|
||||
|
||||
private int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"name='" + name + '\'' +
|
||||
", age=" + age +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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.quickstart.caller.pojo;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
public class User {
|
||||
|
||||
private String name;
|
||||
|
||||
private int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"name='" + name + '\'' +
|
||||
", age=" + age +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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.entity;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
public class User {
|
||||
|
||||
private String name;
|
||||
|
||||
private int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"name='" + name + '\'' +
|
||||
", age=" + age +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>tsf-example</artifactId>
|
||||
<groupId>com.tencent.cloud</groupId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>msgw-scg</artifactId>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.tencent.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-tencent-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.tsf.msgw.scg;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.tsf.annotation.EnableTsf;
|
||||
|
||||
/**
|
||||
* @author seanlxliu
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableTsf
|
||||
public class ScgApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ScgApplication.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
server:
|
||||
port: 8080
|
||||
error:
|
||||
include-exception: true
|
||||
spring:
|
||||
application:
|
||||
name: msgw-scg
|
||||
cloud:
|
||||
gateway:
|
||||
discovery:
|
||||
locator:
|
||||
enabled: true
|
||||
lower-case-service-id: false
|
||||
httpclient:
|
||||
# The connect timeout in millis, the default is 45s.
|
||||
connectTimeout: 200
|
||||
responseTimeout: 10s
|
||||
consul:
|
||||
enabled: true
|
||||
scheme: HTTP
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
file:
|
||||
name: /tsf-demo-logs/${spring.application.name}/root.log
|
||||
pattern:
|
||||
level: "%-5level [${spring.application.name},%mdc{trace_id},%mdc{span_id},]"
|
@ -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.provider.entity;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
public class User {
|
||||
|
||||
private String name;
|
||||
|
||||
private int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"name='" + name + '\'' +
|
||||
", age=" + age +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
|
||||
<groupId>com.tencent.cloud</groupId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-starter-tencent-traffic-mirroring-plugin</artifactId>
|
||||
<name>Spring Cloud Starter Tencent Traffic Mirroring Plugin</name>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Cloud Tencent dependencies start -->
|
||||
<dependency>
|
||||
<groupId>com.tencent.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
|
||||
</dependency>
|
||||
<!-- Spring Cloud Tencent dependencies end -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.trafficmirroring;
|
||||
|
||||
import com.tencent.cloud.plugin.trafficmirroring.config.TrafficMirroringProperties;
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Traffic mirroring exception plugin.
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
public class TrafficMirroringExceptionPlugin extends TrafficMirroringPostPlugin {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TrafficMirroringExceptionPlugin.class);
|
||||
|
||||
public TrafficMirroringExceptionPlugin(TrafficMirroringProperties trafficMirroringProperties) {
|
||||
super(trafficMirroringProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TrafficMirroringExceptionPlugin.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnhancedPluginType getType() {
|
||||
return EnhancedPluginType.Client.EXCEPTION;
|
||||
}
|
||||
}
|
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.trafficmirroring;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.tencent.cloud.common.metadata.MetadataContextHolder;
|
||||
import com.tencent.cloud.plugin.trafficmirroring.config.TrafficMirroringProperties;
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
|
||||
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
|
||||
import com.tencent.cloud.rpc.enhancement.utils.EnhancedRequestUtils;
|
||||
import com.tencent.polaris.api.plugin.route.RouterConstants;
|
||||
import com.tencent.polaris.api.utils.ClassUtils;
|
||||
import com.tencent.polaris.api.utils.StringUtils;
|
||||
import com.tencent.polaris.client.pojo.Node;
|
||||
import com.tencent.polaris.client.util.NamedThreadFactory;
|
||||
import com.tencent.polaris.metadata.core.MetadataObjectValue;
|
||||
import com.tencent.polaris.metadata.core.MetadataType;
|
||||
import feign.Request;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpRequest;
|
||||
|
||||
import static com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant.ClientPluginOrder.TRAFFIC_MIRRORING_PLUGIN_ORDER;
|
||||
|
||||
/**
|
||||
* Traffic mirroring post plugin.
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
public class TrafficMirroringPostPlugin implements EnhancedPlugin {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TrafficMirroringPostPlugin.class);
|
||||
|
||||
private final TrafficMirroringProperties trafficMirroringProperties;
|
||||
|
||||
private ThreadPoolExecutor threadPoolExecutor;
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
public TrafficMirroringPostPlugin(TrafficMirroringProperties trafficMirroringProperties) {
|
||||
this.trafficMirroringProperties = trafficMirroringProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TrafficMirroringPostPlugin.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnhancedPluginType getType() {
|
||||
return EnhancedPluginType.Client.POST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(EnhancedPluginContext context) throws Throwable {
|
||||
if (!trafficMirroringProperties.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object originalRequest = context.getOriginRequest();
|
||||
// only RestTemplate and Feign request can be mirrored.
|
||||
if (!(ClassUtils.isClassPresent("org.springframework.http.HttpRequest")
|
||||
&& originalRequest instanceof HttpRequest)
|
||||
&& !(ClassUtils.isClassPresent("feign.Request")
|
||||
&& originalRequest instanceof Request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get traffic mirroring address.
|
||||
String destination = null;
|
||||
try {
|
||||
MetadataObjectValue<Node> metadataValue = MetadataContextHolder.get()
|
||||
.getMetadataContainer(MetadataType.CUSTOM, false)
|
||||
.getMetadataValue(RouterConstants.TRAFFIC_MIRRORING_NODE_KEY);
|
||||
if (metadataValue != null) {
|
||||
Optional<Node> trafficMirroringNode = metadataValue.getObjectValue();
|
||||
if (trafficMirroringNode.isPresent()) {
|
||||
destination = trafficMirroringNode.get().getHostPort();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
LOG.warn("Get traffic mirroring node failed.", throwable);
|
||||
}
|
||||
if (StringUtils.isBlank(destination)) {
|
||||
return;
|
||||
}
|
||||
|
||||
initIfNeeded();
|
||||
|
||||
byte[] body = context.getOriginBody();
|
||||
// send mirroring request.
|
||||
try {
|
||||
sendMirroringRequest(originalRequest, body, destination);
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.warn("Traffic mirroring request failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initIfNeeded() {
|
||||
if (threadPoolExecutor == null) {
|
||||
// create thread pool executor.
|
||||
int cpuCores = Runtime.getRuntime().availableProcessors();
|
||||
int corePoolSize = cpuCores;
|
||||
int maxPoolSize = trafficMirroringProperties.getRequestPoolSize();
|
||||
if (cpuCores > trafficMirroringProperties.getRequestPoolSize()) {
|
||||
corePoolSize = trafficMirroringProperties.getRequestPoolSize();
|
||||
}
|
||||
this.threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,
|
||||
60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new NamedThreadFactory("polaris-traffic-mirroring"));
|
||||
|
||||
}
|
||||
if (httpClient == null) {
|
||||
// create JDK HttpClient instance.
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofMillis(trafficMirroringProperties.getRequestConnectionTimeout()))
|
||||
.executor(threadPoolExecutor)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private void setHeaders(java.net.http.HttpRequest.Builder requestBuilder, Object originalRequest) {
|
||||
if (ClassUtils.isClassPresent("org.springframework.http.HttpRequest")
|
||||
&& originalRequest instanceof HttpRequest) {
|
||||
HttpHeaders httpHeaders = ((HttpRequest) originalRequest).getHeaders();
|
||||
httpHeaders.forEach((headerName, headerValues) -> {
|
||||
if (shouldKeepHeader(headerName)) {
|
||||
headerValues.forEach(headerValue ->
|
||||
requestBuilder.header(headerName, headerValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (ClassUtils.isClassPresent("feign.Request")
|
||||
&& originalRequest instanceof Request) {
|
||||
Request request = (Request) originalRequest;
|
||||
request.headers().forEach((headerName, headerValues) -> {
|
||||
if (shouldKeepHeader(headerName)) {
|
||||
headerValues.forEach(headerValue ->
|
||||
requestBuilder.header(headerName, headerValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send mirroring request to specified destination.
|
||||
*/
|
||||
private void sendMirroringRequest(Object originalRequest, byte[] originalBody, String destination) {
|
||||
// rebuilt mirroring url: replace original host with specified destination.
|
||||
URI originalUri = EnhancedRequestUtils.getUri(originalRequest);
|
||||
String method = EnhancedRequestUtils.getMethod(originalRequest);
|
||||
String mirroringUrl = rebuildUrlWithDestination(originalUri, destination);
|
||||
if (StringUtils.isBlank(mirroringUrl)) {
|
||||
LOG.debug("Traffic mirroring request url is empty.");
|
||||
return;
|
||||
}
|
||||
if (originalBody == null) {
|
||||
originalBody = new byte[0];
|
||||
}
|
||||
|
||||
// create JDK HttpRequest builder.
|
||||
java.net.http.HttpRequest.Builder requestBuilder = java.net.http.HttpRequest.newBuilder()
|
||||
.uri(URI.create(mirroringUrl))
|
||||
.method(method,
|
||||
java.net.http.HttpRequest.BodyPublishers.ofByteArray(originalBody));
|
||||
|
||||
// copy request headers (except some special headers).
|
||||
setHeaders(requestBuilder, originalRequest);
|
||||
|
||||
// build request.
|
||||
java.net.http.HttpRequest mirrorRequest = requestBuilder.build();
|
||||
|
||||
// send async request (ignore response).
|
||||
httpClient.sendAsync(mirrorRequest, HttpResponse.BodyHandlers.discarding())
|
||||
.thenAccept(response -> {
|
||||
if (response.statusCode() >= 400) {
|
||||
LOG.warn("Traffic mirroring async request return: {}", response.statusCode());
|
||||
}
|
||||
else {
|
||||
LOG.debug("Traffic mirroring async request return: {}", response.statusCode());
|
||||
}
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
LOG.warn("Traffic mirroring async request failed", ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild url with specified destination.
|
||||
*/
|
||||
private String rebuildUrlWithDestination(URI originalUri, String destination) {
|
||||
if (StringUtils.isBlank(destination)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String scheme = originalUri.getScheme();
|
||||
String path = originalUri.getRawPath();
|
||||
String query = originalUri.getRawQuery();
|
||||
|
||||
// fill default port.
|
||||
if (!StringUtils.contains(destination, ":")) {
|
||||
destination += (scheme.equals("https") ? ":443" : ":80");
|
||||
}
|
||||
|
||||
// build full mirroring url.
|
||||
String finalUrl = String.format("%s://%s%s%s",
|
||||
scheme,
|
||||
destination,
|
||||
path != null ? path : "",
|
||||
query != null ? "?" + query : "");
|
||||
LOG.debug("Traffic mirroring request url: {}", finalUrl);
|
||||
|
||||
return finalUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* check header should be kept.
|
||||
*/
|
||||
private boolean shouldKeepHeader(String headerName) {
|
||||
return !headerName.equalsIgnoreCase("connection") &&
|
||||
!headerName.equalsIgnoreCase("content-length") &&
|
||||
!headerName.equalsIgnoreCase("expect") &&
|
||||
!headerName.equalsIgnoreCase("host") &&
|
||||
!headerName.equalsIgnoreCase("upgrade");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerThrowable(EnhancedPluginContext context, Throwable throwable) {
|
||||
LOG.error("TrafficMirroringPostPlugin runs failed. context=[{}].", context, throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return TRAFFIC_MIRRORING_PLUGIN_ORDER;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.trafficmirroring.config;
|
||||
|
||||
import com.tencent.cloud.plugin.trafficmirroring.TrafficMirroringExceptionPlugin;
|
||||
import com.tencent.cloud.plugin.trafficmirroring.TrafficMirroringPostPlugin;
|
||||
import com.tencent.cloud.polaris.router.config.ConditionalOnPolarisRouterEnabled;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Auto configuration for traffic mirroring plugin.
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnPolarisRouterEnabled
|
||||
@ConditionalOnProperty(value = "spring.cloud.polaris.traffic-mirroring.enabled", matchIfMissing = true)
|
||||
@EnableConfigurationProperties(TrafficMirroringProperties.class)
|
||||
public class TrafficMirroringAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public TrafficMirroringPostPlugin trafficMirrorPostPlugin(TrafficMirroringProperties trafficMirrorProperties) {
|
||||
return new TrafficMirroringPostPlugin(trafficMirrorProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TrafficMirroringExceptionPlugin trafficMirrorExceptionPlugin(TrafficMirroringProperties trafficMirrorProperties) {
|
||||
return new TrafficMirroringExceptionPlugin(trafficMirrorProperties);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.trafficmirroring.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties for traffic mirroring.
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.polaris.traffic-mirroring")
|
||||
public class TrafficMirroringProperties {
|
||||
|
||||
/**
|
||||
* If traffic mirroring is enabled. Default is true.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Traffic mirroring request pool size. Default is 100.
|
||||
*/
|
||||
private int requestPoolSize = 100;
|
||||
|
||||
/**
|
||||
* Traffic mirroring request connection timeout in millisecond. Default is 5000.
|
||||
*/
|
||||
private long requestConnectionTimeout = 5000;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public int getRequestPoolSize() {
|
||||
return requestPoolSize;
|
||||
}
|
||||
|
||||
public void setRequestPoolSize(int requestPoolSize) {
|
||||
this.requestPoolSize = requestPoolSize;
|
||||
}
|
||||
|
||||
public long getRequestConnectionTimeout() {
|
||||
return requestConnectionTimeout;
|
||||
}
|
||||
|
||||
public void setRequestConnectionTimeout(long requestConnectionTimeout) {
|
||||
this.requestConnectionTimeout = requestConnectionTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TrafficMirroringProperties{" +
|
||||
"enabled=" + enabled +
|
||||
", requestPoolSize=" + requestPoolSize +
|
||||
", requestConnectionTimeout=" + requestConnectionTimeout +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
com.tencent.cloud.plugin.trafficmirroring.config.TrafficMirroringAutoConfiguration
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.plugin.trafficmirroring.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link TrafficMirroringProperties}.
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
public class TrafficMirroringPropertiesTest {
|
||||
@Test
|
||||
void testDefaultValues() {
|
||||
TrafficMirroringProperties properties = new TrafficMirroringProperties();
|
||||
|
||||
assertThat(properties.isEnabled()).isTrue();
|
||||
assertThat(properties.getRequestPoolSize()).isEqualTo(4);
|
||||
assertThat(properties.getRequestConnectionTimeout()).isEqualTo(5000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEnabledProperty() {
|
||||
TrafficMirroringProperties properties = new TrafficMirroringProperties();
|
||||
|
||||
properties.setEnabled(false);
|
||||
|
||||
assertThat(properties.isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRequestPoolSizeProperty() {
|
||||
TrafficMirroringProperties properties = new TrafficMirroringProperties();
|
||||
|
||||
properties.setRequestPoolSize(10);
|
||||
|
||||
assertThat(properties.getRequestPoolSize()).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRequestPoolSizeBoundaryValues() {
|
||||
TrafficMirroringProperties properties = new TrafficMirroringProperties();
|
||||
|
||||
properties.setRequestPoolSize(0);
|
||||
assertThat(properties.getRequestPoolSize()).isZero();
|
||||
|
||||
properties.setRequestPoolSize(-5);
|
||||
assertThat(properties.getRequestPoolSize()).isNegative();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRequestConnectionTimeoutProperty() {
|
||||
TrafficMirroringProperties properties = new TrafficMirroringProperties();
|
||||
|
||||
properties.setRequestConnectionTimeout(3000L);
|
||||
|
||||
assertThat(properties.getRequestConnectionTimeout()).isEqualTo(3000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRequestConnectionTimeoutBoundaryValues() {
|
||||
TrafficMirroringProperties properties = new TrafficMirroringProperties();
|
||||
|
||||
properties.setRequestConnectionTimeout(0L);
|
||||
assertThat(properties.getRequestConnectionTimeout()).isZero();
|
||||
|
||||
properties.setRequestConnectionTimeout(-1000L);
|
||||
assertThat(properties.getRequestConnectionTimeout()).isNegative();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString() {
|
||||
TrafficMirroringProperties properties = new TrafficMirroringProperties();
|
||||
properties.setEnabled(false);
|
||||
properties.setRequestPoolSize(8);
|
||||
properties.setRequestConnectionTimeout(2000L);
|
||||
|
||||
String result = properties.toString();
|
||||
|
||||
assertThat(result)
|
||||
.contains("enabled=false")
|
||||
.contains("requestPoolSize=8")
|
||||
.contains("requestConnectionTimeout=2000");
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
|
||||
*
|
||||
* Copyright (C) 2021 Tencent. All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed
|
||||
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tencent.cloud.rpc.enhancement.utils;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import com.tencent.polaris.api.utils.ClassUtils;
|
||||
import feign.Request;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.http.HttpRequest;
|
||||
|
||||
/**
|
||||
* Utils for enhanced request.
|
||||
*
|
||||
* @author Haotian Zhang
|
||||
*/
|
||||
public final class EnhancedRequestUtils {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(EnhancedRequestUtils.class);
|
||||
|
||||
private EnhancedRequestUtils() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uri from original request.
|
||||
*
|
||||
* @param originalRequest original request
|
||||
* @return URI
|
||||
*/
|
||||
public static URI getUri(Object originalRequest) {
|
||||
URI uri = null;
|
||||
if (ClassUtils.isClassPresent("org.springframework.http.HttpRequest")
|
||||
&& originalRequest instanceof HttpRequest) {
|
||||
HttpRequest httpRequest = (HttpRequest) originalRequest;
|
||||
uri = httpRequest.getURI();
|
||||
LOG.debug("Get uri: {} from org.springframework.http.HttpRequest", uri);
|
||||
}
|
||||
else if (ClassUtils.isClassPresent("feign.Request")
|
||||
&& originalRequest instanceof Request) {
|
||||
Request request = (Request) originalRequest;
|
||||
uri = URI.create(request.url());
|
||||
LOG.debug("Get uri: {} from feign.Request", uri);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method from original request.
|
||||
*
|
||||
* @param originalRequest original request
|
||||
* @return method
|
||||
*/
|
||||
public static String getMethod(Object originalRequest) {
|
||||
String method = "GET";
|
||||
if (ClassUtils.isClassPresent("org.springframework.http.HttpRequest")
|
||||
&& originalRequest instanceof HttpRequest) {
|
||||
method = ((HttpRequest) originalRequest).getMethod().name();
|
||||
LOG.debug("Get method: {} from org.springframework.http.HttpRequest", method);
|
||||
}
|
||||
else if (ClassUtils.isClassPresent("feign.Request")
|
||||
&& originalRequest instanceof Request) {
|
||||
method = ((Request) originalRequest).httpMethod().name();
|
||||
LOG.debug("Get method: {} from feign.Request", method);
|
||||
}
|
||||
return method;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue