diff --git a/.github/workflows/plugins-jdk17-test.1.yaml b/.github/workflows/plugins-jdk17-test.1.yaml
index a0c7718d64..09e2092791 100644
--- a/.github/workflows/plugins-jdk17-test.1.yaml
+++ b/.github/workflows/plugins-jdk17-test.1.yaml
@@ -57,6 +57,7 @@ jobs:
case:
- spring-6.x-scenario
- resteasy-6.x-scenario
+ - gateway-4.x-scenario
steps:
- uses: actions/checkout@v2
with:
diff --git a/CHANGES.md b/CHANGES.md
index dd5c143a2d..738f67abc7 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -7,6 +7,8 @@ Release Notes.
* Fix NoSuchMethodError in mvc-annotation-commons and change deprecated method.
* fix forkjoinpool plugin in JDK11。
+* Support for tracing spring-cloud-gateway 4.x in gateway-4.x-plugin.
+
#### Documentation
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/define/AbstractGatewayV3EnhancePluginDefine.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/define/AbstractGatewayV3EnhancePluginDefine.java
index 7fd3408fe5..af08868674 100644
--- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/define/AbstractGatewayV3EnhancePluginDefine.java
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/define/AbstractGatewayV3EnhancePluginDefine.java
@@ -20,13 +20,16 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
/**
- * This abstract class defines the witnessClasses() method,
- * and other plugin define classes need to inherit from this class
+ * This abstract class defines the witnessClasses() method, and other plugin define classes need to inherit
+ * from this class
*/
public abstract class AbstractGatewayV3EnhancePluginDefine extends ClassInstanceMethodsEnhancePluginDefine {
@Override
protected String[] witnessClasses() {
- return new String[]{"org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties"};
+ return new String[] {
+ "org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties",
+ "org.springframework.web.client.AsyncRestTemplate"
+ };
}
}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/pom.xml
new file mode 100644
index 0000000000..c7dfeb2165
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/pom.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+ optional-spring-cloud
+ org.apache.skywalking
+ 9.2.0-SNAPSHOT
+
+ 4.0.0
+
+ apm-spring-cloud-gateway-4.x-plugin
+ jar
+ http://maven.apache.org
+
+
+ 4.0.0
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+ ${spring-cloud-starter-gateway.version}
+ provided
+
+
+ org.apache.skywalking
+ apm-spring-webflux-6.x-plugin
+ ${project.version}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+
+ shade
+
+
+
+
+ org.apache.skywalking:apm-spring-webflux-6.x-plugin
+
+ skywalking-plugin.def
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerConstructorInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerConstructorInterceptor.java
new file mode 100644
index 0000000000..589624f0fb
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerConstructorInterceptor.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
+
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
+import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache;
+import reactor.netty.http.client.HttpClientConfig;
+
+/**
+ * Intercept the constructor and inject {@link EnhanceObjectCache}.
+ *
+ * The first constructor argument is {@link reactor.netty.http.client.HttpClientConfig} class instance which can get the
+ * request uri string.
+ */
+public class HttpClientFinalizerConstructorInterceptor implements InstanceConstructorInterceptor {
+
+ @Override
+ public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
+ final HttpClientConfig httpClientConfig = (HttpClientConfig) allArguments[0];
+ if (httpClientConfig == null) {
+ return;
+ }
+ final EnhanceObjectCache enhanceObjectCache = new EnhanceObjectCache();
+ enhanceObjectCache.setUrl(httpClientConfig.uri());
+ objInst.setSkyWalkingDynamicField(enhanceObjectCache);
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerResponseConnectionInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerResponseConnectionInterceptor.java
new file mode 100644
index 0000000000..cfe059e13b
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerResponseConnectionInterceptor.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
+
+import io.netty.handler.codec.http.HttpResponseStatus;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache;
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.SignalType;
+import reactor.netty.Connection;
+import reactor.netty.http.client.HttpClientResponse;
+
+import java.lang.reflect.Method;
+import java.util.function.BiFunction;
+
+/**
+ * This class intercept responseConnection method.
+ *
+ * After downstream service response, finish the span in the {@link EnhanceObjectCache}.
+ */
+public class HttpClientFinalizerResponseConnectionInterceptor implements InstanceMethodsAroundInterceptor {
+
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ MethodInterceptResult result) {
+ BiFunction super HttpClientResponse, ? super Connection, ? extends Publisher> finalReceiver = (BiFunction super HttpClientResponse, ? super Connection, ? extends Publisher>) allArguments[0];
+ EnhanceObjectCache cache = (EnhanceObjectCache) objInst.getSkyWalkingDynamicField();
+ allArguments[0] = (BiFunction) (response, connection) -> {
+ Publisher publisher = finalReceiver.apply(response, connection);
+ if (cache == null) {
+ return publisher;
+ }
+ // receive the response.
+ if (cache.getSpan() != null) {
+ if (response.status().code() >= HttpResponseStatus.BAD_REQUEST.code()) {
+ cache.getSpan().errorOccurred();
+ }
+ Tags.HTTP_RESPONSE_STATUS_CODE.set(cache.getSpan(), response.status().code());
+ }
+
+ return publisher;
+ };
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ Object ret) {
+ Flux> responseFlux = (Flux>) ret;
+
+ responseFlux = responseFlux
+ .doOnError(e -> {
+ EnhanceObjectCache cache = (EnhanceObjectCache) objInst.getSkyWalkingDynamicField();
+ if (cache == null) {
+ return;
+ }
+
+ if (cache.getSpan() != null) {
+ cache.getSpan().errorOccurred();
+ cache.getSpan().log(e);
+ }
+ })
+ .doFinally(signalType -> {
+ EnhanceObjectCache cache = (EnhanceObjectCache) objInst.getSkyWalkingDynamicField();
+ if (cache == null) {
+ return;
+ }
+ // do finally. Finish the span.
+ if (cache.getSpan() != null) {
+ if (signalType == SignalType.CANCEL) {
+ cache.getSpan().errorOccurred();
+ }
+ cache.getSpan().asyncFinish();
+ }
+
+ if (cache.getSpan1() != null) {
+ cache.getSpan1().asyncFinish();
+ }
+ });
+
+ return responseFlux;
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
+
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerSendInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerSendInterceptor.java
new file mode 100644
index 0000000000..7448fc276d
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerSendInterceptor.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
+
+import org.apache.skywalking.apm.agent.core.context.CarrierItem;
+import org.apache.skywalking.apm.agent.core.context.ContextCarrier;
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache;
+import org.apache.skywalking.apm.util.StringUtil;
+import org.reactivestreams.Publisher;
+import reactor.netty.NettyOutbound;
+import reactor.netty.http.client.HttpClientRequest;
+
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.function.BiFunction;
+
+import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY;
+
+/**
+ * This class intercept send method.
+ *
+ * In before method, create a new BiFunction lambda expression for setting ContextCarrier to http header
+ * and replace the original lambda in argument
+ */
+public class HttpClientFinalizerSendInterceptor implements InstanceMethodsAroundInterceptor {
+
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ MethodInterceptResult result) throws Throwable {
+ EnhanceObjectCache enhanceObjectCache = (EnhanceObjectCache) objInst.getSkyWalkingDynamicField();
+ if (enhanceObjectCache == null) {
+ return;
+ }
+
+ /*
+ In this plug-in, the HttpClientFinalizerSendInterceptor depends on the NettyRoutingFilterInterceptor
+ When the NettyRoutingFilterInterceptor is not executed, the HttpClientFinalizerSendInterceptor has no meaning to be executed independently
+ and using ContextManager.activeSpan() method would cause NPE as active span does not exist.
+ */
+ if (!ContextManager.isActive()) {
+ return;
+ }
+
+ AbstractSpan span = ContextManager.activeSpan();
+ span.prepareForAsync();
+
+ if (StringUtil.isNotEmpty(enhanceObjectCache.getUrl())) {
+ URL url = new URL(enhanceObjectCache.getUrl());
+
+ ContextCarrier contextCarrier = new ContextCarrier();
+ AbstractSpan abstractSpan = ContextManager.createExitSpan(
+ "SpringCloudGateway/sendRequest", contextCarrier, getPeer(url));
+ Tags.URL.set(abstractSpan, enhanceObjectCache.getUrl());
+ abstractSpan.prepareForAsync();
+ abstractSpan.setComponent(SPRING_CLOUD_GATEWAY);
+ abstractSpan.setLayer(SpanLayer.HTTP);
+ ContextManager.stopSpan(abstractSpan);
+
+ BiFunction super HttpClientRequest, ? super NettyOutbound, ? extends Publisher> finalSender = (BiFunction super HttpClientRequest, ? super NettyOutbound, ? extends Publisher>) allArguments[0];
+ allArguments[0] = (BiFunction>) (request, outbound) -> {
+ Publisher publisher = finalSender.apply(request, outbound);
+
+ CarrierItem next = contextCarrier.items();
+ while (next.hasNext()) {
+ next = next.next();
+ request.requestHeaders().remove(next.getHeadKey());
+ request.requestHeaders().set(next.getHeadKey(), next.getHeadValue());
+ }
+ return publisher;
+ };
+ enhanceObjectCache.setCacheSpan(abstractSpan);
+ }
+ ContextManager.stopSpan(span);
+ enhanceObjectCache.setSpan1(span);
+ }
+
+ private String getPeer(URL url) {
+ return url.getHost() + ":" + url.getPort();
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ Object ret) {
+ ((EnhancedInstance) ret).setSkyWalkingDynamicField(objInst.getSkyWalkingDynamicField());
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
+
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerUriInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerUriInterceptor.java
new file mode 100644
index 0000000000..e97a2bcc7a
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerUriInterceptor.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
+
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache;
+
+import java.lang.reflect.Method;
+
+/**
+ * This class intercept uri method to get the url of downstream service
+ */
+public class HttpClientFinalizerUriInterceptor implements InstanceMethodsAroundInterceptor {
+
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ MethodInterceptResult result) throws Throwable {
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ Object ret) throws Throwable {
+ if (ret instanceof EnhancedInstance) {
+ EnhanceObjectCache enhanceObjectCache = (EnhanceObjectCache) ((EnhancedInstance) ret)
+ .getSkyWalkingDynamicField();
+ enhanceObjectCache.setUrl(String.valueOf(allArguments[0]));
+ }
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/NettyRoutingFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/NettyRoutingFilterInterceptor.java
new file mode 100644
index 0000000000..c5271f9a82
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/NettyRoutingFilterInterceptor.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
+
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebExchangeDecorator;
+
+import java.lang.reflect.Method;
+
+import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY;
+
+/**
+ * This class intercept filter method.
+ *
+ * spring-webflux-5.x-plugin will inject context snapshot into skywalking dynamic field, and this
+ * interceptor will continue the span in another thread.
+ *
+ */
+public class NettyRoutingFilterInterceptor implements InstanceMethodsAroundInterceptor {
+ private static final String NETTY_ROUTING_FILTER_TRACED_ATTR = NettyRoutingFilterInterceptor.class.getName() + ".isTraced";
+
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ MethodInterceptResult result) throws Throwable {
+ ServerWebExchange exchange = (ServerWebExchange) allArguments[0];
+ if (isTraced(exchange)) {
+ return;
+ }
+
+ setTracedStatus(exchange);
+
+ EnhancedInstance enhancedInstance = getInstance(allArguments[0]);
+
+ AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/RoutingFilter");
+ if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) {
+ ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField());
+ }
+ span.setComponent(SPRING_CLOUD_GATEWAY);
+ }
+
+ private static void setTracedStatus(ServerWebExchange exchange) {
+ exchange.getAttributes().put(NETTY_ROUTING_FILTER_TRACED_ATTR, true);
+ }
+
+ private static boolean isTraced(ServerWebExchange exchange) {
+ return exchange.getAttributeOrDefault(NETTY_ROUTING_FILTER_TRACED_ATTR, false);
+ }
+
+ private EnhancedInstance getInstance(Object o) {
+ EnhancedInstance instance = null;
+ if (o instanceof EnhancedInstance) {
+ instance = (EnhancedInstance) o;
+ } else if (o instanceof ServerWebExchangeDecorator) {
+ ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate();
+ return getInstance(delegate);
+ }
+ return instance;
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ Object ret) throws Throwable {
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
+ ContextManager.activeSpan().log(t);
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/AbstractGatewayV4EnhancePluginDefine.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/AbstractGatewayV4EnhancePluginDefine.java
new file mode 100644
index 0000000000..803097ba62
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/AbstractGatewayV4EnhancePluginDefine.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define;
+
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
+
+/**
+ * This abstract class defines the witnessClasses() method,
+ * and other plugin define classes need to inherit from this class
+ */
+public abstract class AbstractGatewayV4EnhancePluginDefine extends ClassInstanceMethodsEnhancePluginDefine {
+
+ /**
+ * @since 4.0.0
+ */
+ @Override
+ protected String[] witnessClasses() {
+ return new String[]{"org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties"};
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/DispatcherHandlerInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/DispatcherHandlerInstrumentation.java
new file mode 100644
index 0000000000..47b47d5c4a
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/DispatcherHandlerInstrumentation.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define;
+
+/**
+ * This class is used for enable {@link org.apache.skywalking.apm.plugin.spring.webflux.v6.define.DispatcherHandlerInstrumentation}
+ * class when {@link org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties} class is exist
+ */
+public class DispatcherHandlerInstrumentation extends
+ org.apache.skywalking.apm.plugin.spring.webflux.v6.define.DispatcherHandlerInstrumentation {
+
+ /**
+ * @since 4.0.0
+ */
+ @Override
+ protected String[] witnessClasses() {
+ return new String[]{"org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties"};
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/EnhanceObjectCache.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/EnhanceObjectCache.java
new file mode 100644
index 0000000000..419d5118a2
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/EnhanceObjectCache.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define;
+
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+
+/**
+ * enhance object cache
+ */
+public class EnhanceObjectCache {
+
+ private String url;
+ private AbstractSpan span;
+ private AbstractSpan span1;
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public void setCacheSpan(AbstractSpan span) {
+ this.span = span;
+ }
+
+ public AbstractSpan getSpan() {
+ return span;
+ }
+
+ public AbstractSpan getSpan1() {
+ return span1;
+ }
+
+ public void setSpan1(final AbstractSpan span) {
+ span1 = span;
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/HttpClientFinalizerInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/HttpClientFinalizerInstrumentation.java
new file mode 100644
index 0000000000..5f9de2c43d
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/HttpClientFinalizerInstrumentation.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType;
+import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
+
+/**
+ * This class instrument reactor.netty.http.client.HttpClientFinalizer class.
+ *
+ * This is the class that actually sends the request. By enhancing in different methods,
+ * we can get information such as uri and send trace context to the downstream service through http header.
+ *
+ */
+public class HttpClientFinalizerInstrumentation extends AbstractGatewayV4EnhancePluginDefine {
+
+ private static final String INTERCEPT_CLASS_HTTP_CLIENT_FINALIZER = "reactor.netty.http.client.HttpClientFinalizer";
+ private static final String CLIENT_FINALIZER_CONSTRUCTOR_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.HttpClientFinalizerConstructorInterceptor";
+ private static final String CLIENT_FINALIZER_CONSTRUCTOR_ARGUMENT_TYPE = "reactor.netty.http.client.HttpClientConfig";
+ private static final String HTTP_CLIENT_FINALIZER_URI_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.HttpClientFinalizerUriInterceptor";
+ private static final String HTTP_CLIENT_FINALIZER_SEND_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.HttpClientFinalizerSendInterceptor";
+ private static final String HTTP_CLIENT_FINALIZER_RESPONSE_CONNECTION_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.HttpClientFinalizerResponseConnectionInterceptor";
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return byName(INTERCEPT_CLASS_HTTP_CLIENT_FINALIZER);
+ }
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[]{
+ new ConstructorInterceptPoint() {
+ @Override
+ public ElementMatcher getConstructorMatcher() {
+ return takesArgumentWithType(0, CLIENT_FINALIZER_CONSTRUCTOR_ARGUMENT_TYPE);
+ }
+
+ @Override
+ public String getConstructorInterceptor() {
+ return CLIENT_FINALIZER_CONSTRUCTOR_INTERCEPTOR;
+ }
+ }
+ };
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
+ return new InstanceMethodsInterceptPoint[]{
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named("uri");
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return HTTP_CLIENT_FINALIZER_URI_INTERCEPTOR;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ },
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named("send").and(takesArgumentWithType(0, "java.util.function.BiFunction"));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return HTTP_CLIENT_FINALIZER_SEND_INTERCEPTOR;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return true;
+ }
+ },
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named("responseConnection");
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return HTTP_CLIENT_FINALIZER_RESPONSE_CONNECTION_INTERCEPTOR;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return true;
+ }
+ }
+ };
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/NettyRoutingFilterInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/NettyRoutingFilterInstrumentation.java
new file mode 100644
index 0000000000..2d824f2a6c
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/NettyRoutingFilterInstrumentation.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType;
+import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
+
+/**
+ * This class instrument {@link org.springframework.cloud.gateway.filter.NettyRoutingFilter} class.
+ *
+ * Enhance filter method and get ContextSnapshot from {@link
+ * org.springframework.web.server.ServerWebExchange}. Create a local span to continue context snapshot created in {@link
+ * org.apache.skywalking.apm.plugin.spring.webflux.v6.DispatcherHandlerHandleMethodInterceptor} interceptor.
+ *
+ */
+public class NettyRoutingFilterInstrumentation extends AbstractGatewayV4EnhancePluginDefine {
+
+ private static final String INTERCEPT_CLASS_NETTY_ROUTING_FILTER = "org.springframework.cloud.gateway.filter.NettyRoutingFilter";
+ private static final String NETTY_ROUTING_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.NettyRoutingFilterInterceptor";
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return byName(INTERCEPT_CLASS_NETTY_ROUTING_FILTER);
+ }
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[0];
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
+ return new InstanceMethodsInterceptPoint[]{
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named("filter").and(
+ takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange"));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return NETTY_ROUTING_FILTER_INTERCEPTOR;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return true;
+ }
+ }
+ };
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/ServerWebExchangeInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/ServerWebExchangeInstrumentation.java
new file mode 100644
index 0000000000..1fc4942feb
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/ServerWebExchangeInstrumentation.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define;
+
+/**
+ * This class is used for enable {@link org.apache.skywalking.apm.plugin.spring.webflux.v6.define.ServerWebExchangeInstrumentation}
+ * when {@link org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties} class is exist.
+ */
+public class ServerWebExchangeInstrumentation extends
+ org.apache.skywalking.apm.plugin.spring.webflux.v6.define.ServerWebExchangeInstrumentation {
+
+ /**
+ * @since 4.0.0
+ */
+ @Override
+ protected String[] witnessClasses() {
+ return new String[]{"org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties"};
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/resources/skywalking-plugin.def
new file mode 100644
index 0000000000..5cbcbfcdd8
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/resources/skywalking-plugin.def
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.HttpClientFinalizerInstrumentation
+spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.NettyRoutingFilterInstrumentation
+spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.ServerWebExchangeInstrumentation
+spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.DispatcherHandlerInstrumentation
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerConstructorInterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerConstructorInterceptorTest.java
new file mode 100644
index 0000000000..32a1c9e1f4
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerConstructorInterceptorTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+import reactor.netty.http.client.HttpClientConfig;
+
+@RunWith(MockitoJUnitRunner.class)
+public class HttpClientFinalizerConstructorInterceptorTest {
+
+ private static final String URI = "http://localhost:8080/get";
+ private final EnhancedInstance enhancedInstance = new EnhancedInstance() {
+ private EnhanceObjectCache enhanceObjectCache;
+
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return enhanceObjectCache;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+ this.enhanceObjectCache = (EnhanceObjectCache) value;
+ }
+ };
+ private HttpClientConfig httpClientConfig;
+ private HttpClientFinalizerConstructorInterceptor httpClientFinalizerConstructorInterceptor;
+
+ @Before
+ public void setUp() {
+ httpClientConfig = mock(HttpClientConfig.class);
+ when(httpClientConfig.uri()).thenReturn(URI);
+ httpClientFinalizerConstructorInterceptor = new HttpClientFinalizerConstructorInterceptor();
+ }
+
+ @Test
+ public void onConstruct() {
+ httpClientFinalizerConstructorInterceptor.onConstruct(enhancedInstance, new Object[]{httpClientConfig});
+ final EnhanceObjectCache enhanceCache = (EnhanceObjectCache) enhancedInstance.getSkyWalkingDynamicField();
+ assertNotNull(enhanceCache);
+ assertEquals(enhanceCache.getUrl(), URI);
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerInterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerInterceptorTest.java
new file mode 100644
index 0000000000..542a6d07f3
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/HttpClientFinalizerInterceptorTest.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import java.util.List;
+import java.util.function.BiFunction;
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.test.helper.SegmentHelper;
+import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStorage;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint;
+import org.apache.skywalking.apm.agent.test.tools.SpanAssert;
+import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.EnhanceObjectCache;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.reactivestreams.Publisher;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import reactor.core.publisher.Flux;
+import reactor.netty.Connection;
+import reactor.netty.NettyOutbound;
+import reactor.netty.http.client.HttpClientRequest;
+import reactor.netty.http.client.HttpClientResponse;
+
+@RunWith(TracingSegmentRunner.class)
+public class HttpClientFinalizerInterceptorTest {
+
+ private final static String URI = "http://localhost:8080/get";
+ private final static String ENTRY_OPERATION_NAME = "/get";
+ private final HttpClientFinalizerSendInterceptor sendInterceptor = new HttpClientFinalizerSendInterceptor();
+ private final HttpClientFinalizerResponseConnectionInterceptor responseConnectionInterceptor = new HttpClientFinalizerResponseConnectionInterceptor();
+ private final BiFunction super HttpClientRequest, ? super NettyOutbound, ? extends Publisher> originalSendBiFunction = (httpClientRequest, nettyOutbound) -> (Publisher) s -> {
+ };
+ private final BiFunction super HttpClientResponse, ? super Connection, ? extends Publisher> originalResponseConnectionBiFunction = (httpClientResponse, connection) -> (Publisher) s -> {
+ };
+ private final EnhancedInstance enhancedInstance = new EnhancedInstance() {
+ private EnhanceObjectCache enhanceObjectCache;
+
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return enhanceObjectCache;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+ this.enhanceObjectCache = (EnhanceObjectCache) value;
+ }
+ };
+ @Rule
+ public AgentServiceRule serviceRule = new AgentServiceRule();
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ private HttpClientResponse mockResponse;
+ private HttpClientRequest mockRequest;
+ @SegmentStoragePoint
+ private SegmentStorage segmentStorage;
+ private AbstractSpan entrySpan;
+
+ @Before
+ public void setUp() throws Exception {
+ final EnhanceObjectCache enhanceObjectCache = new EnhanceObjectCache();
+ enhanceObjectCache.setUrl(URI);
+ enhancedInstance.setSkyWalkingDynamicField(enhanceObjectCache);
+ entrySpan = ContextManager.createEntrySpan(ENTRY_OPERATION_NAME, null);
+ entrySpan.setLayer(SpanLayer.HTTP);
+ entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX);
+ mockRequest = new MockCliengRequest();
+ mockResponse = new MockClientResponse();
+ }
+
+ @Test
+ public void testWithDynamicFieldNull() throws Throwable {
+ enhancedInstance.setSkyWalkingDynamicField(null);
+ executeSendRequest();
+ final List traceSegments = segmentStorage.getTraceSegments();
+ assertEquals(traceSegments.size(), 0);
+ if (ContextManager.isActive()) {
+ ContextManager.stopSpan();
+ }
+ }
+
+ @Test
+ public void testWithEmptyUri() throws Throwable {
+ final EnhanceObjectCache objectCache = (EnhanceObjectCache) enhancedInstance.getSkyWalkingDynamicField();
+ objectCache.setUrl("");
+ executeSendRequest();
+ final List traceSegments = segmentStorage.getTraceSegments();
+ assertEquals(traceSegments.size(), 1);
+ final List spans = SegmentHelper.getSpans(traceSegments.get(0));
+ assertNotNull(spans);
+ assertEquals(spans.size(), 1);
+ assertUpstreamSpan(spans.get(0));
+ assertNotNull(objectCache.getSpan1());
+ assertEquals(objectCache.getSpan1().getSpanId(), entrySpan.getSpanId());
+ }
+
+ @Test
+ public void testWithUri() throws Throwable {
+ executeSendRequest();
+ final List traceSegments = segmentStorage.getTraceSegments();
+ assertEquals(traceSegments.size(), 1);
+ final EnhanceObjectCache objectCache = (EnhanceObjectCache) enhancedInstance
+ .getSkyWalkingDynamicField();
+ assertNotNull(objectCache.getSpan1());
+ assertNotNull(objectCache.getSpan());
+ assertEquals(objectCache.getSpan1().getSpanId(), entrySpan.getSpanId());
+ assertTrue(objectCache.getSpan().isExit());
+ assertEquals(objectCache.getSpan().getOperationName(), "SpringCloudGateway/sendRequest");
+ final List spans = SegmentHelper.getSpans(traceSegments.get(0));
+ assertNotNull(spans);
+ assertEquals(spans.size(), 2);
+ assertUpstreamSpan(spans.get(1));
+ assertDownstreamSpan(spans.get(0));
+ }
+
+ private void executeSendRequest() throws Throwable {
+ Object[] sendArguments = new Object[]{originalSendBiFunction};
+ sendInterceptor.beforeMethod(enhancedInstance, null, sendArguments, null, null);
+ sendInterceptor.afterMethod(enhancedInstance, null, new Object[0], null, enhancedInstance);
+ ((BiFunction super HttpClientRequest, ? super NettyOutbound, ? extends Publisher>) sendArguments[0])
+ .apply(mockRequest, null);
+ Object[] responseConnectionArguments = new Object[]{originalResponseConnectionBiFunction};
+ responseConnectionInterceptor
+ .beforeMethod(enhancedInstance, null, responseConnectionArguments, null, null);
+ Flux flux = Flux.just(0);
+
+ flux = (Flux) responseConnectionInterceptor.afterMethod(enhancedInstance, null, new Object[0], null, flux);
+
+ ((BiFunction super HttpClientResponse, ? super Connection, ? extends Publisher>) responseConnectionArguments[0])
+ .apply(mockResponse, null);
+ flux.blockFirst();
+ }
+
+ private void assertUpstreamSpan(AbstractSpan span) {
+ SpanAssert.assertLayer(span, SpanLayer.HTTP);
+ SpanAssert.assertComponent(span, ComponentsDefine.SPRING_WEBFLUX);
+ }
+
+ private void assertDownstreamSpan(AbstractSpan span) {
+ SpanAssert.assertLayer(span, SpanLayer.HTTP);
+ SpanAssert.assertComponent(span, ComponentsDefine.SPRING_CLOUD_GATEWAY);
+ SpanAssert.assertTagSize(span, 2);
+ SpanAssert.assertTag(span, 0, URI);
+ SpanAssert.assertTag(span, 1, String.valueOf(HttpResponseStatus.OK.code()));
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/MockCliengRequest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/MockCliengRequest.java
new file mode 100644
index 0000000000..9d65d3dc83
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/MockCliengRequest.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
+
+import io.netty.handler.codec.http.DefaultHttpHeaders;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.cookie.Cookie;
+import reactor.netty.http.client.HttpClientRequest;
+import reactor.util.context.Context;
+import reactor.util.context.ContextView;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.Set;
+
+public class MockCliengRequest implements HttpClientRequest {
+
+ @Override
+ public HttpClientRequest addCookie(Cookie cookie) {
+ return null;
+ }
+
+ @Override
+ public HttpClientRequest addHeader(CharSequence charSequence, CharSequence charSequence1) {
+ return null;
+ }
+
+ @Override
+ public HttpClientRequest header(CharSequence charSequence, CharSequence charSequence1) {
+ return null;
+ }
+
+ @Override
+ public HttpClientRequest headers(HttpHeaders httpHeaders) {
+ return null;
+ }
+
+ @Override
+ public boolean isFollowRedirect() {
+ return false;
+ }
+
+ @Override
+ public HttpClientRequest responseTimeout(Duration duration) {
+ return null;
+ }
+
+ @Override
+ public Context currentContext() {
+ return null;
+ }
+
+ @Override
+ public ContextView currentContextView() {
+ return null;
+ }
+
+ @Override
+ public String[] redirectedFrom() {
+ return new String[0];
+ }
+
+ @Override
+ public HttpHeaders requestHeaders() {
+ return new DefaultHttpHeaders();
+ }
+
+ @Override
+ public String resourceUrl() {
+ return null;
+ }
+
+ @Override
+ public Map> cookies() {
+ return null;
+ }
+
+ @Override
+ public boolean isKeepAlive() {
+ return false;
+ }
+
+ @Override
+ public boolean isWebsocket() {
+ return false;
+ }
+
+ @Override
+ public HttpMethod method() {
+ return null;
+ }
+
+ @Override
+ public String path() {
+ return HttpClientRequest.super.path();
+ }
+
+ @Override
+ public String fullPath() {
+ return null;
+ }
+
+ @Override
+ public String requestId() {
+ return null;
+ }
+
+ @Override
+ public String uri() {
+ return null;
+ }
+
+ @Override
+ public HttpVersion version() {
+ return null;
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/MockClientResponse.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/MockClientResponse.java
new file mode 100644
index 0000000000..0b075a1917
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/MockClientResponse.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
+
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.cookie.Cookie;
+import reactor.core.publisher.Mono;
+import reactor.netty.http.client.HttpClientResponse;
+import reactor.util.context.Context;
+import reactor.util.context.ContextView;
+
+import java.util.Map;
+import java.util.Set;
+
+public class MockClientResponse implements HttpClientResponse {
+
+ @Override
+ public HttpHeaders responseHeaders() {
+ return null;
+ }
+
+ @Override
+ public HttpResponseStatus status() {
+ return HttpResponseStatus.OK;
+ }
+
+ @Override
+ public Mono trailerHeaders() {
+ return null;
+ }
+
+ @Override
+ public Context currentContext() {
+ return null;
+ }
+
+ @Override
+ public ContextView currentContextView() {
+ return null;
+ }
+
+ @Override
+ public String[] redirectedFrom() {
+ return new String[0];
+ }
+
+ @Override
+ public HttpHeaders requestHeaders() {
+ return null;
+ }
+
+ @Override
+ public String resourceUrl() {
+ return null;
+ }
+
+ @Override
+ public Map> cookies() {
+ return null;
+ }
+
+ @Override
+ public boolean isKeepAlive() {
+ return false;
+ }
+
+ @Override
+ public boolean isWebsocket() {
+ return false;
+ }
+
+ @Override
+ public HttpMethod method() {
+ return null;
+ }
+
+ @Override
+ public String path() {
+ return HttpClientResponse.super.path();
+ }
+
+ @Override
+ public String fullPath() {
+ return null;
+ }
+
+ @Override
+ public String requestId() {
+ return null;
+ }
+
+ @Override
+ public String uri() {
+ return null;
+ }
+
+ @Override
+ public HttpVersion version() {
+ return null;
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/NettyRoutingFilterInterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/NettyRoutingFilterInterceptorTest.java
new file mode 100644
index 0000000000..25bbffb7cc
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/NettyRoutingFilterInterceptorTest.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
+
+import java.security.Principal;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.test.helper.SegmentHelper;
+import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStorage;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint;
+import org.apache.skywalking.apm.agent.test.tools.SpanAssert;
+import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.i18n.LocaleContext;
+import org.springframework.http.codec.multipart.Part;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebSession;
+import reactor.core.publisher.Mono;
+
+@RunWith(TracingSegmentRunner.class)
+public class NettyRoutingFilterInterceptorTest {
+ private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance {
+ private ContextSnapshot snapshot;
+ Map attributes = new HashMap<>();
+
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return snapshot;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+ this.snapshot = (ContextSnapshot) value;
+ }
+
+ @Override
+ public ServerHttpRequest getRequest() {
+ return null;
+ }
+
+ @Override
+ public ServerHttpResponse getResponse() {
+ return null;
+ }
+
+ @Override
+ public Map getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ public Mono getSession() {
+ return null;
+ }
+
+ @Override
+ public Mono getPrincipal() {
+ return null;
+ }
+
+ @Override
+ public Mono> getFormData() {
+ return null;
+ }
+
+ @Override
+ public Mono> getMultipartData() {
+ return null;
+ }
+
+ @Override
+ public LocaleContext getLocaleContext() {
+ return null;
+ }
+
+ @Override
+ public ApplicationContext getApplicationContext() {
+ return null;
+ }
+
+ @Override
+ public boolean isNotModified() {
+ return false;
+ }
+
+ @Override
+ public boolean checkNotModified(Instant instant) {
+ return false;
+ }
+
+ @Override
+ public boolean checkNotModified(String s) {
+ return false;
+ }
+
+ @Override
+ public boolean checkNotModified(String s, Instant instant) {
+ return false;
+ }
+
+ @Override
+ public String transformUrl(String s) {
+ return null;
+ }
+
+ @Override
+ public void addUrlTransformer(Function function) {
+
+ }
+
+ @Override
+ public String getLogPrefix() {
+ return null;
+ }
+ }
+
+ private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance();
+ private final NettyRoutingFilterInterceptor interceptor = new NettyRoutingFilterInterceptor();
+ @Rule
+ public AgentServiceRule serviceRule = new AgentServiceRule();
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ @SegmentStoragePoint
+ private SegmentStorage segmentStorage;
+
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ @Test
+ public void testWithNullDynamicField() throws Throwable {
+ interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
+ interceptor.afterMethod(null, null, null, null, null);
+ ContextManager.stopSpan();
+ final List traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertEquals(traceSegments.size(), 1);
+ final List spans = SegmentHelper.getSpans(traceSegments.get(0));
+ Assert.assertNotNull(spans);
+ Assert.assertEquals(spans.size(), 1);
+ SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
+ }
+
+ @Test
+ public void testWithContextSnapshot() throws Throwable {
+ final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null);
+ SpanLayer.asHttp(entrySpan);
+ entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX);
+ enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture());
+ interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
+ interceptor.afterMethod(null, null, null, null, null);
+ ContextManager.stopSpan();
+ ContextManager.stopSpan(entrySpan);
+ final List traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertEquals(traceSegments.size(), 1);
+ final List spans = SegmentHelper.getSpans(traceSegments.get(0));
+ Assert.assertNotNull(spans);
+ Assert.assertEquals(spans.size(), 2);
+ SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
+ SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX);
+ SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP);
+ }
+
+ private static final String NETTY_ROUTING_FILTER_TRACED_ATTR =
+ NettyRoutingFilterInterceptor.class.getName() + ".isTraced";
+
+ @Test
+ public void testIsTraced() throws Throwable {
+ interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
+ interceptor.afterMethod(null, null, null, null, null);
+ Assert.assertEquals(enhancedInstance.getAttributes().get(NETTY_ROUTING_FILTER_TRACED_ATTR), true);
+ Assert.assertNotNull(ContextManager.activeSpan());
+
+ ContextManager.stopSpan();
+
+ interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
+ interceptor.afterMethod(null, null, null, null, null);
+ Assert.assertEquals(enhancedInstance.getAttributes().get(NETTY_ROUTING_FILTER_TRACED_ATTR), true);
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000..1f0955d450
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/pom.xml
index 04f310997d..0741114c6b 100644
--- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/pom.xml
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/pom.xml
@@ -31,6 +31,7 @@
gateway-2.0.x-plugingateway-2.1.x-plugingateway-3.x-plugin
+ gateway-4.x-pluginpom
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/pom.xml
index ce3953a99d..1ae8f4983f 100644
--- a/apm-sniffer/optional-plugins/optional-spring-plugins/pom.xml
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/pom.xml
@@ -35,6 +35,7 @@
spring-webflux-5.x-pluginresttemplate-6.x-pluginmvc-annotation-6.x-plugin
+ spring-webflux-6.x-plugin
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/define/DispatcherHandlerInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/define/DispatcherHandlerInstrumentation.java
index d33eb8cf2f..1c3085c399 100644
--- a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/define/DispatcherHandlerInstrumentation.java
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/define/DispatcherHandlerInstrumentation.java
@@ -18,8 +18,11 @@
package org.apache.skywalking.apm.plugin.spring.webflux.v5.define;
+import java.util.Collections;
+import java.util.List;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
@@ -29,6 +32,9 @@
import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
public class DispatcherHandlerInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
+ private static final String WEBFLUX_CONTEXT_WRITE_CLASS = "reactor.core.publisher.Mono";
+ private static final String WEBFLUX_CONTEXT_WRITE_METHOD = "subscriberContext";
+
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return new ConstructorInterceptPoint[0];
@@ -60,4 +66,10 @@ public boolean isOverrideArgs() {
protected ClassMatch enhanceClass() {
return byName("org.springframework.web.reactive.DispatcherHandler");
}
+
+ @Override
+ protected List witnessMethods() {
+ return Collections.singletonList(
+ new WitnessMethod(WEBFLUX_CONTEXT_WRITE_CLASS, named(WEBFLUX_CONTEXT_WRITE_METHOD)));
+ }
}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/pom.xml b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/pom.xml
new file mode 100644
index 0000000000..b8e7c79207
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/pom.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+ optional-spring-plugins
+ org.apache.skywalking
+ 9.2.0-SNAPSHOT
+
+ 4.0.0
+
+ apm-spring-webflux-6.x-plugin
+
+ http://maven.apache.org
+
+
+
+ org.springframework
+ spring-webflux
+ 6.0.0
+ provided
+
+
+
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/DispatcherHandlerHandleMethodInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/DispatcherHandlerHandleMethodInterceptor.java
new file mode 100644
index 0000000000..0f2e2f296d
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/DispatcherHandlerHandleMethodInterceptor.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.webflux.v6;
+
+import org.apache.skywalking.apm.agent.core.context.CarrierItem;
+import org.apache.skywalking.apm.agent.core.context.ContextCarrier;
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags.HTTP;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.web.reactive.HandlerMapping;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.ServerWebExchangeDecorator;
+import org.springframework.web.server.adapter.DefaultServerWebExchange;
+import org.springframework.web.util.pattern.PathPattern;
+import reactor.core.publisher.Mono;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class DispatcherHandlerHandleMethodInterceptor implements InstanceMethodsAroundInterceptor {
+
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ MethodInterceptResult result) throws Throwable {
+ EnhancedInstance instance = getInstance(allArguments[0]);
+
+ ServerWebExchange exchange = (ServerWebExchange) allArguments[0];
+
+ ContextCarrier carrier = new ContextCarrier();
+ CarrierItem next = carrier.items();
+ HttpHeaders headers = exchange.getRequest().getHeaders();
+ while (next.hasNext()) {
+ next = next.next();
+ List header = headers.get(next.getHeadKey());
+ if (header != null && header.size() > 0) {
+ next.setHeadValue(header.get(0));
+ }
+ }
+
+ AbstractSpan span = ContextManager.createEntrySpan(exchange.getRequest().getURI().getPath(), carrier);
+
+ if (instance != null && instance.getSkyWalkingDynamicField() != null) {
+ ContextManager.continued((ContextSnapshot) instance.getSkyWalkingDynamicField());
+ }
+ span.setComponent(ComponentsDefine.SPRING_WEBFLUX);
+ SpanLayer.asHttp(span);
+ Tags.URL.set(span, exchange.getRequest().getURI().toString());
+ HTTP.METHOD.set(span, exchange.getRequest().getMethod().name());
+ instance.setSkyWalkingDynamicField(ContextManager.capture());
+ span.prepareForAsync();
+ ContextManager.stopSpan(span);
+
+ exchange.getAttributes().put("SKYWALKING_SPAN", span);
+ }
+
+ private void maybeSetPattern(AbstractSpan span, ServerWebExchange exchange) {
+ if (span != null) {
+ PathPattern pathPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
+ //check if the best matching pattern matches url in request
+ //to avoid operationName overwritten by fallback url
+ if (pathPattern != null && pathPattern.matches(exchange.getRequest().getPath().pathWithinApplication())) {
+ span.setOperationName(pathPattern.getPatternString());
+ }
+ }
+
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ Object ret) throws Throwable {
+
+ ServerWebExchange exchange = (ServerWebExchange) allArguments[0];
+
+ AbstractSpan span = (AbstractSpan) exchange.getAttributes().get("SKYWALKING_SPAN");
+ Mono monoReturn = (Mono) ret;
+
+ // add skywalking context snapshot to reactor context.
+ EnhancedInstance instance = getInstance(allArguments[0]);
+ if (instance != null && instance.getSkyWalkingDynamicField() != null) {
+ monoReturn = monoReturn.contextWrite(
+ c -> c.put("SKYWALKING_CONTEXT_SNAPSHOT", instance.getSkyWalkingDynamicField()));
+ }
+
+ return monoReturn.doFinally(s -> {
+
+ if (span != null) {
+ maybeSetPattern(span, exchange);
+ try {
+
+ HttpStatusCode httpStatus = exchange.getResponse().getStatusCode();
+ // fix webflux-2.0.0-2.1.0 version have bug. httpStatus is null. not support
+ if (httpStatus != null) {
+ Tags.HTTP_RESPONSE_STATUS_CODE.set(span, httpStatus.value());
+ if (httpStatus.isError()) {
+ span.errorOccurred();
+ }
+ }
+ } finally {
+ span.asyncFinish();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
+ }
+
+ public static EnhancedInstance getInstance(Object o) {
+ EnhancedInstance instance = null;
+ if (o instanceof DefaultServerWebExchange) {
+ instance = (EnhancedInstance) o;
+ } else if (o instanceof ServerWebExchangeDecorator) {
+ ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate();
+ return getInstance(delegate);
+ }
+ return instance;
+ }
+
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/ServerWebExchangeConstructorInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/ServerWebExchangeConstructorInterceptor.java
new file mode 100644
index 0000000000..cbd76e60a8
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/ServerWebExchangeConstructorInterceptor.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.webflux.v6;
+
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
+
+public class ServerWebExchangeConstructorInterceptor implements InstanceConstructorInterceptor {
+ @Override
+ public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/define/DispatcherHandlerInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/define/DispatcherHandlerInstrumentation.java
new file mode 100644
index 0000000000..14600779ef
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/define/DispatcherHandlerInstrumentation.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.webflux.v6.define;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+
+import java.util.Collections;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
+
+public class DispatcherHandlerInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
+ private static final String WEBFLUX_CONTEXT_WRITE_CLASS = "reactor.core.publisher.Mono";
+ private static final String WEBFLUX_CONTEXT_WRITE_METHOD = "contextWrite";
+ private static final String WEBFLUX_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.webflux.v6.DispatcherHandlerHandleMethodInterceptor";
+ private static final String WEBFLUX_ENHANCE_CLASS = "org.springframework.web.reactive.DispatcherHandler";
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[0];
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
+ return new InstanceMethodsInterceptPoint[] {
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named("handle");
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return WEBFLUX_METHOD_INTERCEPTOR;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ }
+ };
+ }
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return byName(WEBFLUX_ENHANCE_CLASS);
+ }
+
+ /**
+ * @since 6.0.0
+ */
+ @Override
+ protected List witnessMethods() {
+ return Collections.singletonList(
+ new WitnessMethod(WEBFLUX_CONTEXT_WRITE_CLASS, named(WEBFLUX_CONTEXT_WRITE_METHOD)));
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/define/ServerWebExchangeInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/define/ServerWebExchangeInstrumentation.java
new file mode 100644
index 0000000000..65f104e417
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/define/ServerWebExchangeInstrumentation.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.skywalking.apm.plugin.spring.webflux.v6.define;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
+
+public class ServerWebExchangeInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
+ private static final String WEBFLUX_CONSTRUCTOR_INTERCEPOR = "org.apache.skywalking.apm.plugin.spring.webflux.v6.ServerWebExchangeConstructorInterceptor";
+ private static final String WEBFLUX_ENHANCE_CLASS = "org.springframework.web.server.adapter.DefaultServerWebExchange";
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[] {
+ new ConstructorInterceptPoint() {
+ @Override
+ public ElementMatcher getConstructorMatcher() {
+ return any();
+ }
+
+ @Override
+ public String getConstructorInterceptor() {
+ return WEBFLUX_CONSTRUCTOR_INTERCEPOR;
+ }
+ }
+ };
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
+ return new InstanceMethodsInterceptPoint[0];
+ }
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return byName(WEBFLUX_ENHANCE_CLASS);
+ }
+}
diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/resources/skywalking-plugin.def
new file mode 100644
index 0000000000..1d55fd1af6
--- /dev/null
+++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/resources/skywalking-plugin.def
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+spring-webflux-6.x=org.apache.skywalking.apm.plugin.spring.webflux.v6.define.DispatcherHandlerInstrumentation
+spring-webflux-6.x=org.apache.skywalking.apm.plugin.spring.webflux.v6.define.ServerWebExchangeInstrumentation
diff --git a/docs/en/setup/service-agent/java-agent/Optional-plugins.md b/docs/en/setup/service-agent/java-agent/Optional-plugins.md
index a91ba05b5a..5c7a7f7976 100644
--- a/docs/en/setup/service-agent/java-agent/Optional-plugins.md
+++ b/docs/en/setup/service-agent/java-agent/Optional-plugins.md
@@ -9,7 +9,7 @@ Now, we have the following known optional plugins.
* Plugin of Gson serialization lib in optional plugin folder.
* Plugin of Zookeeper 3.4.x in optional plugin folder. The reason of being optional plugin is, many business irrelevant traces are generated, which cause extra payload to agents and backends. At the same time, those traces may be just heartbeat(s).
* [Customize enhance](Customize-enhance-trace.md) Trace methods based on description files, rather than write plugin or change source codes.
-* Plugin of Spring Cloud Gateway 2.x and 3.x in optional plugin folder. Please only activate this plugin when you install agent in Spring Gateway.
+* Plugin of Spring Cloud Gateway 2.x and 3.x and 4.x in optional plugin folder. Please only activate this plugin when you install agent in Spring Gateway.
* Plugin of Spring Transaction in optional plugin folder. The reason of being optional plugin is, many local span are generated, which also spend more CPU, memory and network.
* [Plugin of Kotlin coroutine](agent-optional-plugins/Kotlin-Coroutine-plugin.md) provides the tracing across coroutines automatically. As it will add local spans to all across routines scenarios, Please assess the performance impact.
* Plugin of quartz-scheduler-2.x in the optional plugin folder. The reason for being an optional plugin is, many task scheduling systems are based on quartz-scheduler, this will cause duplicate tracing and link different sub-tasks as they share the same quartz level trigger, such as ElasticJob.
diff --git a/docs/en/setup/service-agent/java-agent/Plugin-list.md b/docs/en/setup/service-agent/java-agent/Plugin-list.md
index fe5464ff79..9d6bbe77c0 100644
--- a/docs/en/setup/service-agent/java-agent/Plugin-list.md
+++ b/docs/en/setup/service-agent/java-agent/Plugin-list.md
@@ -172,3 +172,5 @@
- jetty-server-11.x
- jetty-client-11.x
- websphere-liberty-23.x
+- spring-cloud-gateway-4.x
+- spring-webflux-6.x
diff --git a/docs/en/setup/service-agent/java-agent/Supported-list.md b/docs/en/setup/service-agent/java-agent/Supported-list.md
index 6e502bbf01..232d264d85 100644
--- a/docs/en/setup/service-agent/java-agent/Supported-list.md
+++ b/docs/en/setup/service-agent/java-agent/Supported-list.md
@@ -15,7 +15,7 @@ metrics based on the tracing data.
* Resin 3 (Optional¹)
* Resin 4 (Optional¹)
* [Jetty Server](http://www.eclipse.org/jetty/) 9.x -> 11.x
- * [Spring WebFlux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) 5.x (Optional¹)
+ * [Spring WebFlux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) 5.x (Optional¹) -> 6.x (Optional¹)
* [Undertow](http://undertow.io/) 1.3.0.Final -> 2.0.27.Final
* [RESTEasy](https://resteasy.github.io/) 3.1.0.Final -> 6.2.4.Final
* [Play Framework](https://www.playframework.com/) 2.6.x -> 2.8.x
@@ -40,7 +40,7 @@ metrics based on the tracing data.
* [Hutool-http](https://www.hutool.cn/) client 5.x
* [Micronaut HTTP Client](https://github.com/micronaut-projects/micronaut-core) 3.2.x -> 3.6.x
* HTTP Gateway
- * [Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) 2.0.2.RELEASE -> 3.x (Optional²)
+ * [Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) 2.0.2.RELEASE -> 4.1.x (Optional²)
* [Apache ShenYu](https://shenyu.apache.org) (Rich protocol support: `HTTP`,`Spring Cloud`,`gRPC`,`Dubbo`,`SOFARPC`,`Motan`,`Tars`) 2.4.x (Optional²)
* JDBC
* Mysql Driver 5.x, 6.x, 8.x
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-4.x-scenario/config/expectedData.yaml
new file mode 100644
index 0000000000..70df54e93f
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/config/expectedData.yaml
@@ -0,0 +1,155 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+segmentItems:
+- serviceName: gateway-projectB-scenario
+ segmentSize: nq 0
+ segments:
+ - segmentId: not null
+ spans:
+ - operationName: /provider/timeout/error
+ parentSpanId: 0
+ spanId: 1
+ isError: true
+ spanType: Exit
+ tags:
+ - { key: url, value: not null }
+ - { key: http.method, value: GET }
+ - { key: http.status_code, value: '500' }
+ logs:
+ - logEvent:
+ - { key: event, value: error }
+ - { key: error.kind, value: not null }
+ - { key: message, value: not null }
+ - {key: stack, value: not null}
+ - logEvent:
+ - { key: event, value: error }
+ - { key: error.kind, value: not null }
+ - { key: message, value: not null }
+ - { key: stack, value: not null }
+ - operationName: GET:/provider/b/testcase
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 14
+ isError: false
+ spanType: Entry
+ peer: ''
+ tags:
+ - {key: url, value: not null}
+ - {key: http.method, value: GET}
+ - {key: http.status_code, value: '200'}
+ refs:
+ - {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070',
+ refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not
+ null, parentService: not null, traceId: not null}
+ skipAnalysis: 'false'
+- serviceName: gateway-projectA-scenario
+ segmentSize: nq 0
+ segments:
+ - segmentId: not null
+ spans:
+ - operationName: /provider/timeout/error
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ isError: true
+ spanType: Entry
+ tags:
+ - {key: url, value: 'http://localhost:8080/provider/timeout/error' }
+ - {key: http.method, value: GET}
+ - {key: http.status_code, value: '500'}
+ - segmentId: not null
+ spans:
+ - operationName: SpringCloudGateway/sendRequest
+ parentSpanId: 0
+ spanId: 1
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 61
+ isError: true
+ spanType: Exit
+ peer: 1.2.3.4:18070
+ skipAnalysis: false
+ tags:
+ - { key: url, value: not null }
+ logs:
+ - logEvent:
+ - { key: event, value: error }
+ - { key: error.kind, value: not null }
+ - { key: message, value: not null }
+ - { key: stack, value: not null}
+ - operationName: SpringCloudGateway/RoutingFilter
+ parentSpanId: -1
+ spanId: 0
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 61
+ spanType: Local
+ refs:
+ - { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread,
+ parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
+ null, parentService: not null, traceId: not null }
+ skipAnalysis: 'false'
+ - segmentId: not null
+ spans:
+ - operationName: SpringCloudGateway/sendRequest
+ parentSpanId: 0
+ spanId: 1
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 61
+ isError: false
+ spanType: Exit
+ peer: 'localhost:18070'
+ tags:
+ - {key: url, value: not null}
+ - {key: http.status_code, value: '200'}
+ skipAnalysis: 'false'
+ - operationName: SpringCloudGateway/RoutingFilter
+ parentSpanId: -1
+ spanId: 0
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 61
+ isError: false
+ spanType: Local
+ peer: ''
+ refs:
+ - {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread,
+ parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
+ null, parentService: not null, traceId: not null}
+ skipAnalysis: 'false'
+ - segmentId: not null
+ spans:
+ - operationName: /provider/b/testcase
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 67
+ isError: false
+ spanType: Entry
+ peer: not null
+ tags:
+ - { key: url, value: 'http://localhost:8080/provider/b/testcase' }
+ - { key: http.method, value: GET }
+ - { key: http.status_code, value: '200' }
+ skipAnalysis: 'false'
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/configuration.yml b/test/plugin/scenarios/gateway-4.x-scenario/configuration.yml
new file mode 100644
index 0000000000..a958b8d680
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/configuration.yml
@@ -0,0 +1,22 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+type: jvm
+entryService: http://localhost:8080/provider/b/testcase
+healthCheck: http://localhost:8080/provider/b/healthCheck
+startScript: ./bin/startup.sh
+runningMode: with_optional
+withPlugins: apm-spring-cloud-gateway-4.x-plugin-*.jar
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-dist/bin/startup.sh b/test/plugin/scenarios/gateway-4.x-scenario/gateway-dist/bin/startup.sh
new file mode 100644
index 0000000000..0d28675632
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-dist/bin/startup.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+home="$(cd "$(dirname $0)"; pwd)"
+
+java -jar ${agent_opts} "-Dskywalking.agent.service_name=gateway-projectA-scenario" ${home}/../libs/gateway-projectA-scenario.jar &
+sleep 1
+
+java -jar ${agent_opts} "-Dskywalking.agent.service_name=gateway-projectB-scenario" ${home}/../libs/gateway-projectB-scenario.jar &
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-dist/pom.xml b/test/plugin/scenarios/gateway-4.x-scenario/gateway-dist/pom.xml
new file mode 100644
index 0000000000..ac465f2fd5
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-dist/pom.xml
@@ -0,0 +1,54 @@
+
+
+
+
+ org.apache.skywalking
+ gateway-4.x-scenario
+ 5.0.0
+
+ 4.0.0
+
+ gateway-dist
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ assemble
+ package
+
+ single
+
+
+
+ src/main/assembly/assembly.xml
+
+ ../target/
+
+
+
+
+
+
+
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-dist/src/main/assembly/assembly.xml b/test/plugin/scenarios/gateway-4.x-scenario/gateway-dist/src/main/assembly/assembly.xml
new file mode 100644
index 0000000000..00d04e28e0
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-dist/src/main/assembly/assembly.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ zip
+
+
+
+
+ ./bin
+ 0775
+
+
+
+
+
+ ../gateway-projectA-scenario/target/gateway-projectA-scenario.jar
+ ./libs
+ 0775
+
+
+ ../gateway-projectB-scenario/target/gateway-projectB-scenario.jar
+ ./libs
+ 0775
+
+
+
\ No newline at end of file
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/pom.xml b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/pom.xml
new file mode 100644
index 0000000000..d49078511b
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/pom.xml
@@ -0,0 +1,56 @@
+
+
+
+
+ org.apache.skywalking
+ gateway-4.x-scenario
+ 5.0.0
+
+ 4.0.0
+
+ gateway-projectA-scenario
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+ ${test.framework.version}
+
+
+
+
+ gateway-projectA-scenario
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 3.0.0
+
+
+
+ repackage
+
+
+
+
+
+
+
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/ApiKeyResolver.java b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/ApiKeyResolver.java
new file mode 100644
index 0000000000..40517d1d3f
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/ApiKeyResolver.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA;
+
+import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+@Component
+public class ApiKeyResolver implements KeyResolver {
+
+ public Mono resolve(ServerWebExchange exchange) {
+ return Mono.just(exchange.getRequest().getPath().value());
+ }
+}
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java
new file mode 100644
index 0000000000..68eb95b0c6
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test1Filter.java b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test1Filter.java
new file mode 100644
index 0000000000..af891eae20
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test1Filter.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA;
+
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+public class Test1Filter implements GlobalFilter, Ordered {
+ @Override
+ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ ServerHttpRequest buildRequest = exchange.getRequest().mutate().build();
+ return chain.filter(exchange.mutate().request(buildRequest).build());
+ }
+
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+}
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test2Filter.java b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test2Filter.java
new file mode 100644
index 0000000000..d3a5846bd5
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Test2Filter.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA;
+
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+public class Test2Filter implements GlobalFilter, Ordered {
+
+ @Override
+ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ ServerHttpRequest buildRequest = exchange.getRequest().mutate().build();
+ return chain.filter(exchange.mutate().request(buildRequest).build());
+ }
+
+ @Override
+ public int getOrder() {
+ return 1;
+ }
+}
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/TestFilterConfig.java b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/TestFilterConfig.java
new file mode 100644
index 0000000000..29f9627bdf
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/TestFilterConfig.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class TestFilterConfig {
+
+ @Bean
+ public Test1Filter test1Filter() {
+ return new Test1Filter();
+ }
+
+ @Bean
+ public Test2Filter test2Filter() {
+ return new Test2Filter();
+ }
+}
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml
new file mode 100644
index 0000000000..7639e7ce8a
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml
@@ -0,0 +1,33 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+
+server:
+ port: 8080
+spring:
+ cloud:
+ gateway:
+ httpclient:
+ connect-timeout: 2000
+ routes:
+ - id: provider_route
+ uri: http://localhost:18070
+ predicates:
+ - Path=/provider/b/*
+ - id: provider_timeout
+ uri: http://1.2.3.4:18070
+ predicates:
+ - Path=/provider/timeout/*
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/pom.xml b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/pom.xml
new file mode 100644
index 0000000000..31ef696819
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/pom.xml
@@ -0,0 +1,57 @@
+
+
+
+
+ gateway-4.x-scenario
+ org.apache.skywalking
+ 5.0.0
+
+ 4.0.0
+
+ gateway-projectB-scenario
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ 2.1.0.RELEASE
+
+
+
+
+ gateway-projectB-scenario
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 1.5.9.RELEASE
+
+
+
+ repackage
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java
new file mode 100644
index 0000000000..51c94655c7
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 test.apache.skywalking.apm.testcase.sc.gateway.projectB;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@ComponentScan(value = {"test.apache.skywalking.apm.testcase.sc.gateway.projectB.controller"})
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java
new file mode 100644
index 0000000000..923d7fd7ff
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 test.apache.skywalking.apm.testcase.sc.gateway.projectB.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+
+@RestController
+public class TestController {
+
+ @RequestMapping("/provider/b/testcase")
+ public String testcase() {
+ try {
+ new RestTemplate().getForEntity("http://localhost:8080/provider/timeout/error", String.class);
+ } catch (Exception e) {
+ }
+ return "1";
+ }
+
+ @RequestMapping("/provider/b/healthCheck")
+ public String healthCheck() {
+ return "Success";
+ }
+}
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties
new file mode 100644
index 0000000000..cac2c4d5a1
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+server.port=18070
\ No newline at end of file
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/pom.xml b/test/plugin/scenarios/gateway-4.x-scenario/pom.xml
new file mode 100644
index 0000000000..2817fefa2c
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+ 4.0.0
+
+ org.apache.skywalking
+ gateway-4.x-scenario
+ pom
+ 5.0.0
+
+ gateway-projectA-scenario
+ gateway-projectB-scenario
+ gateway-dist
+
+
+ skywalking-gateway-4.x-scenario
+
+
+ UTF-8
+ 1.8
+ 3.8.1
+ 4.0.0
+ ${test.framework.version}
+
+
+
+ gateway-4.x-scenario
+
+
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+ ${compiler.version}
+ ${compiler.version}
+ ${project.build.sourceEncoding}
+
+
+
+
+
+
diff --git a/test/plugin/scenarios/gateway-4.x-scenario/support-version.list b/test/plugin/scenarios/gateway-4.x-scenario/support-version.list
new file mode 100644
index 0000000000..ff90e11c58
--- /dev/null
+++ b/test/plugin/scenarios/gateway-4.x-scenario/support-version.list
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+4.0.0
+4.0.8
+4.1.0
\ No newline at end of file