This is an automated email from the ASF dual-hosted git repository. wu-sheng pushed a commit to branch feature/spring6-mixed-classpath-scenario in repository https://gitbox.apache.org/repos/asf/skywalking-java.git
commit 24bfeea6837f6a31cbe635b0abd1f3496ab2ee43 Author: Wu Sheng <[email protected]> AuthorDate: Tue Jul 7 13:33:31 2026 +0800 Add mixed-classpath scenario and Tomcat http.headers collection Two related follow-ups to the #812 servlet-commons unification, both about HTTP request-tag collection on the Tomcat/servlet path. 1. spring-6.x-mixed-servlet-scenario — an end-to-end repro of the #13938 field condition: a Jakarta Spring 6 WAR that ALSO carries javax.servlet-api:4.0.1 at compile scope, so both javax.servlet.* (from WEB-INF/lib) and jakarta.servlet.* (from the Tomcat 10.1 container) are loadable in the deployed webapp at once. The scenario asserts http.headers and http.params are still collected on the jakarta entry span, proving servlet-commons' runtime instanceof dispatch resolves the live request under a mixed classpath — previously only covered by the HttpRequestWrappersTest unit test. One version by design (the resolution is version-independent; version compatibility is already covered by spring-6.x-scenario). 2. Tomcat http.headers — http.headers was emitted only by the Spring MVC plugin, so plugin.http.include_http_headers was a no-op for plain servlet apps served by the Tomcat plugin. Add INCLUDE_HTTP_HEADERS + HTTP_HEADERS_LENGTH_THRESHOLD to TomcatPluginConfig.Plugin.Http — the shared plugin.http.* namespace already used by the Spring MVC and HttpClient plugins, so no new config key and no agent.config change — and collect the configured request headers in TomcatInvokeInterceptor, guarded no-op when the config is empty. Proven on the dedicated Tomcat scenarios, where the Tomcat plugin owns the entry span: tomcat-9x (javax, Tomcat 9) and tomcat-10x (jakarta, Tomcat 10) now carry a mock_header header and a q1 query param on their internal self-call and assert http.headers + http.params, which also fills the previously-missing jakarta http.params coverage. --- .github/workflows/plugins-jdk17-test.1.yaml | 1 + CHANGES.md | 1 + .../apm/plugin/tomcat/TomcatInvokeInterceptor.java | 28 ++ .../apm/plugin/tomcat/TomcatPluginConfig.java | 14 + .../config/expectedData.yaml | 379 +++++++++++++++++++++ .../configuration.yml | 8 +- .../spring-6.x-mixed-servlet-scenario/pom.xml | 143 ++++++++ .../skywalking/apm/testcase/entity/User.java | 53 +++ .../testcase/implinterface/TestCaseController.java | 35 ++ .../testcase/implinterface/TestCaseInterface.java | 31 ++ .../apm/testcase/inherit/ChildController.java | 28 ++ .../apm/testcase/inherit/ParentController.java | 32 ++ .../apm/testcase/restapi/RestCaseController.java | 77 +++++ .../resttemplate/RestTemplateController.java | 85 +++++ .../apm/testcase/spring3/CaseController.java | 41 +++ .../spring3/component/TestComponentBean.java | 29 ++ .../testcase/spring3/dao/TestRepositoryBean.java | 29 ++ .../testcase/spring3/service/TestServiceBean.java | 38 +++ .../src/main/resources/log4j2.xml | 30 ++ .../src/main/webapp/WEB-INF/spring-mvc-servlet.xml | 30 ++ .../src/main/webapp/WEB-INF/web.xml | 35 ++ .../support-version.list | 5 + .../tomcat-10x-scenario/config/expectedData.yaml | 4 +- .../tomcat-10x-scenario/configuration.yml | 2 + .../apm/testcase/tomcat10x/CaseServlet.java | 5 +- .../tomcat-9x-scenario/config/expectedData.yaml | 4 +- .../scenarios/tomcat-9x-scenario/configuration.yml | 2 + .../apm/testcase/tomcat9x/CaseServlet.java | 5 +- 28 files changed, 1168 insertions(+), 6 deletions(-) diff --git a/.github/workflows/plugins-jdk17-test.1.yaml b/.github/workflows/plugins-jdk17-test.1.yaml index e4f7144dfd..7738b85f44 100644 --- a/.github/workflows/plugins-jdk17-test.1.yaml +++ b/.github/workflows/plugins-jdk17-test.1.yaml @@ -71,6 +71,7 @@ jobs: matrix: case: - spring-6.x-scenario + - spring-6.x-mixed-servlet-scenario - spring-boot-3.x-scenario - struts2.7-scenario - resteasy-6.x-scenario diff --git a/CHANGES.md b/CHANGES.md index 48310efdab..4daf294f74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Release Notes. ------------------ * Fix `plugin.http.include_http_headers` not working on Spring Boot 3.x / Jakarta EE (apache/skywalking#13938). +* Support `plugin.http.include_http_headers` in the Tomcat plugin, collecting the configured request headers as the `http.headers` tag on Tomcat entry spans (e.g. RESTEasy on Tomcat), consistent with the Spring MVC plugin. Honors the shared `plugin.http.http_headers_length_threshold`. * Unify the Tomcat plugins into a single `tomcat` plugin (Tomcat 7 - 10) and the Jetty server plugins into a single `jetty-server` plugin (Jetty 9 - 11), each supporting both `javax.servlet` and `jakarta.servlet`. Breaking change: plugin names `tomcat-7.x/8.x` and `tomcat-10.x` are replaced by `tomcat`, and `jetty-server-9.x` and `jetty-server-11.x` by `jetty-server`; update `plugin.exclude_plugins` if you reference the old names. * Add a Jetty 12 server plugin (`jetty-server-12.x`). Jetty 12 removed the `HttpChannel` handle target and moved request handling to the async `Server#handle(Request, Response, Callback)` core API, so it needs a separate plugin from the merged `jetty-server`. * Add a Struts 7 plugin (`struts2-7.x`) for Jakarta Struts, whose `DefaultActionInvocation` moved to `org.apache.struts2`. diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatInvokeInterceptor.java b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatInvokeInterceptor.java index 9cb009199a..8208a2a4c6 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatInvokeInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatInvokeInterceptor.java @@ -37,8 +37,11 @@ import org.apache.skywalking.apm.util.StringUtil; import org.apache.tomcat.util.http.Parameters; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.List; import java.util.Map; public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor { @@ -64,6 +67,10 @@ public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor if (TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS) { collectHttpParam(request, span); } + + if (!CollectionUtil.isEmpty(TomcatPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS)) { + collectHttpHeaders(request, span); + } } @Override @@ -115,4 +122,25 @@ public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor Tags.HTTP.PARAMS.set(span, tagValue); } } + + private void collectHttpHeaders(Request request, AbstractSpan span) { + final List<String> headersList = new ArrayList<>(TomcatPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS.size()); + TomcatPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS.stream() + .filter(headerName -> request.getHeaders(headerName) != null) + .forEach(headerName -> { + Enumeration<String> headerValues = request.getHeaders(headerName); + List<String> valueList = Collections.list(headerValues); + if (!CollectionUtil.isEmpty(valueList)) { + headersList.add(headerName + "=" + valueList); + } + }); + + if (!headersList.isEmpty()) { + String tagValue = String.join("\n", headersList); + tagValue = TomcatPluginConfig.Plugin.Http.HTTP_HEADERS_LENGTH_THRESHOLD > 0 ? + StringUtil.cut(tagValue, TomcatPluginConfig.Plugin.Http.HTTP_HEADERS_LENGTH_THRESHOLD) : + tagValue; + Tags.HTTP.HEADERS.set(span, tagValue); + } + } } diff --git a/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatPluginConfig.java b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatPluginConfig.java index 0a1d713cf9..8a99337863 100644 --- a/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatPluginConfig.java +++ b/apm-sniffer/apm-sdk-plugin/tomcat-plugin/src/main/java/org/apache/skywalking/apm/plugin/tomcat/TomcatPluginConfig.java @@ -18,6 +18,7 @@ package org.apache.skywalking.apm.plugin.tomcat; +import java.util.List; import org.apache.skywalking.apm.agent.core.boot.PluginConfig; public class TomcatPluginConfig { @@ -38,6 +39,19 @@ public class TomcatPluginConfig { * for the sake of performance */ public static int HTTP_PARAMS_LENGTH_THRESHOLD = 1024; + + /** + * When {@link Http#INCLUDE_HTTP_HEADERS} declares header names, this threshold controls the length + * limitation of all header values. use negative values to keep and send the complete headers. + * Note. this config item is added for the sake of performance. + */ + public static int HTTP_HEADERS_LENGTH_THRESHOLD = 2048; + + /** + * It controls what header data should be collected, this is for security purpose, values must be in lower + * case. Shares the {@code plugin.http.include_http_headers} config key with the other HTTP server plugins. + */ + public static List<String> INCLUDE_HTTP_HEADERS; } } } diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..992a34dafd --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/config/expectedData.yaml @@ -0,0 +1,379 @@ +# 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: spring-6.x-mixed-servlet-scenario + segmentSize: ge 10 + segments: + - segmentId: not null + spans: + - operationName: HEAD:/healthCheck + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/healthCheck'} + - {key: http.method, value: HEAD} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: test.apache.skywalking.apm.testcase.spring3.component.TestComponentBean.componentMethod + parentSpanId: 1 + spanId: 2 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: test.apache.skywalking.apm.testcase.spring3.dao.TestRepositoryBean.doSomeStuff + parentSpanId: 1 + spanId: 3 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: test.apache.skywalking.apm.testcase.spring3.service.TestServiceBean.doSomeBusiness + parentSpanId: 0 + spanId: 1 + spanLayer: Unknown + startTime: nq 0 + endTime: nq 0 + componentId: 93 + isError: false + spanType: Local + peer: '' + skipAnalysis: 'false' + - operationName: GET:/case/spring3 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/case/spring3'} + - {key: http.method, value: GET} + - {key: http.headers, value: 'mock_header=[mock_value]'} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'POST:/create/' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/create/'} + - {key: http.method, value: POST} + - {key: http.status_code, value: '201'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'GET:/get/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/get/1'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 3, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'PUT:/update/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/update/1'} + - {key: http.method, value: PUT} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 4, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'DELETE:/delete/{id}' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/delete/1'} + - {key: http.method, value: DELETE} + - {key: http.status_code, value: '204'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 5, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/inherit/child/test + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/inherit/child/test'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 6, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/impl/requestmapping + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/impl/requestmapping'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 7, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: 'GET:/impl/restmapping' + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/impl/restmapping'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + refs: + - {parentEndpoint: GET:/case/resttemplate, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 8, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: spring-6.x-mixed-servlet-scenario, traceId: not null} + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /spring-6.x-mixed-servlet-scenario/case/spring3 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/case/spring3'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/create/ + parentSpanId: 0 + spanId: 2 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/create/'} + - {key: http.method, value: POST} + - {key: http.status_code, value: '201'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/get/1 + parentSpanId: 0 + spanId: 3 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/get/1'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/update/1 + parentSpanId: 0 + spanId: 4 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/update/1'} + - {key: http.method, value: PUT} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/delete/1 + parentSpanId: 0 + spanId: 5 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 13 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/delete/1'} + - {key: http.method, value: DELETE} + - {key: http.status_code, value: '204'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/inherit/child/test + parentSpanId: 0 + spanId: 6 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/inherit/child/test'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/impl/requestmapping + parentSpanId: 0 + spanId: 7 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/impl/requestmapping'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: /spring-6.x-mixed-servlet-scenario/impl/restmapping + parentSpanId: 0 + spanId: 8 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 12 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - {key: http.method, value: GET} + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/impl/restmapping'} + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' + - operationName: GET:/case/resttemplate + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 14 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/spring-6.x-mixed-servlet-scenario/case/resttemplate'} + - {key: http.method, value: GET} + - key: http.params + value: |- + q1=[v1] + chinese=[中文] + - {key: http.status_code, value: '200'} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/tomcat-10x-scenario/configuration.yml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/configuration.yml similarity index 63% copy from test/plugin/scenarios/tomcat-10x-scenario/configuration.yml copy to test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/configuration.yml index fac58bdfd1..8ca8d8b42b 100644 --- a/test/plugin/scenarios/tomcat-10x-scenario/configuration.yml +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/configuration.yml @@ -15,5 +15,9 @@ # limitations under the License. type: tomcat -entryService: http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario -healthCheck: http://localhost:8080/tomcat-10x-scenario/case/healthCheck +entryService: '"http://localhost:8080/spring-6.x-mixed-servlet-scenario/case/resttemplate?q1=v1&chinese=%e4%b8%ad%e6%96%87"' +healthCheck: http://localhost:8080/spring-6.x-mixed-servlet-scenario/healthCheck +runningMode: with_optional +withPlugins: apm-spring-annotation-plugin-*.jar;apm-*-6.x-plugin-*.jar +environment: + - CATALINA_OPTS="-Dskywalking.plugin.http.include_http_headers=mock_header -Dskywalking.plugin.springmvc.collect_http_params=true" diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/pom.xml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/pom.xml new file mode 100644 index 0000000000..4cd3b19779 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/pom.xml @@ -0,0 +1,143 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + ~ + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.skywalking</groupId> + <artifactId>spring-6.x-mixed-servlet-scenario</artifactId> + <version>5.0.0</version> + + <packaging>war</packaging> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <compiler.version>17</compiler.version> + <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version> + <test.framework.version>6.0.0</test.framework.version> + <test.framework>spring</test.framework> + </properties> + + <name>skywalking-spring-6.x-mixed-servlet-scenario</name> + + <dependencies> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-context</artifactId> + <version>${test.framework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-aop</artifactId> + <version>${test.framework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-webmvc</artifactId> + <version>${test.framework.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-web</artifactId> + <version>${test.framework.version}</version> + </dependency> + + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>2.14.3</version> + </dependency> + + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <version>2.9.1</version> + </dependency> + + <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>okhttp</artifactId> + <version>4.10.0</version> + </dependency> + + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-api</artifactId> + <version>2.19.0</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + <version>2.19.0</version> + </dependency> + + <dependency> + <groupId>jakarta.servlet</groupId> + <artifactId>jakarta.servlet-api</artifactId> + <version>6.0.0</version> + </dependency> + <!-- + The point of this scenario (issue #13938): a Jakarta Spring 6 app that ALSO carries the + legacy javax.servlet-api transitively. At 'compile' scope this jar lands in WEB-INF/lib, + so both javax.servlet.* and jakarta.servlet.* are loadable in the deployed webapp at once + (jakarta from Tomcat 10.1's container libs, javax from WEB-INF/lib - Tomcat only skips the + jakarta copy, not javax). servlet-commons' runtime instanceof dispatch must still resolve + the live jakarta request and collect http.headers / http.params. Do NOT change the scope + to provided - that would remove the mixed classpath and defeat the test. + --> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <version>4.0.1</version> + </dependency> + </dependencies> + + <build> + <finalName>spring-6.x-mixed-servlet-scenario</finalName> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-war-plugin</artifactId> + <version>3.3.1</version> + </plugin> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <version>${maven-compiler-plugin.version}</version> + <configuration> + <parameters>true</parameters> + <source>${compiler.version}</source> + <target>${compiler.version}</target> + <encoding>${project.build.sourceEncoding}</encoding> + </configuration> + </plugin> + </plugins> + </build> + + <pluginRepositories> + <pluginRepository> + <id>spring-snapshots</id> + <url>https://repo.spring.io/snapshot</url> + </pluginRepository> + <pluginRepository> + <id>spring-milestones</id> + <url>https://repo.spring.io/milestone</url> + </pluginRepository> + </pluginRepositories> +</project> \ No newline at end of file diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java new file mode 100644 index 0000000000..e7fb1871e5 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java @@ -0,0 +1,53 @@ +/* + * 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.entity; + +public class User { + + private int id; + private String userName; + + public User(int id) { + this.id = id; + } + + public User(int id, String userName) { + this.id = id; + this.userName = userName; + } + + public User() { + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.java new file mode 100644 index 0000000000..7a95a9318a --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseController.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.implinterface; + +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestCaseController implements TestCaseInterface { + + @Override + public String implRequestMappingAnnotationTestCase() { + return "implRequestMappingAnnotationTestCase"; + } + + @Override + public String implRestAnnotationTestCase() { + return "implRestAnnotationTestCase"; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.java new file mode 100644 index 0000000000..4fea97b7b1 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/implinterface/TestCaseInterface.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.implinterface; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("/impl") +public interface TestCaseInterface { + @RequestMapping(path = "/requestmapping") + String implRequestMappingAnnotationTestCase(); + + @GetMapping("/restmapping") + String implRestAnnotationTestCase(); +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.java new file mode 100644 index 0000000000..27ddf54c76 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ChildController.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 test.apache.skywalking.apm.testcase.inherit; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/inherit/child") +public class ChildController extends ParentController { + +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java new file mode 100644 index 0000000000..16b928100d --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/inherit/ParentController.java @@ -0,0 +1,32 @@ +/* + * 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.inherit; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/inherit/parent") +public class ParentController { + + @RequestMapping("test") + public String test(Integer param) { + return "parent-a" + param; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java new file mode 100644 index 0000000000..6792ecb712 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/restapi/RestCaseController.java @@ -0,0 +1,77 @@ +/* + * 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.restapi; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; +import test.apache.skywalking.apm.testcase.entity.User; + +@RestController +public class RestCaseController { + + private static final Map<Integer, User> USERS = new ConcurrentHashMap<Integer, User>(); + + @GetMapping(value = "/get/{id}") + @ResponseBody + private ResponseEntity<User> getUser(@PathVariable("id") int id) throws InterruptedException { + User currentUser = new User(id, "a"); + return ResponseEntity.ok(currentUser); + } + + @PostMapping(value = "/create/") + @ResponseBody + public ResponseEntity<Void> createUser(@RequestBody User user, + UriComponentsBuilder ucBuilder) throws InterruptedException { + USERS.put(user.getId(), user); + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(ucBuilder.path("/get/{id}").buildAndExpand(user.getId()).toUri()); + return new ResponseEntity<Void>(headers, HttpStatus.CREATED); + } + + @PutMapping(value = "/update/{id}") + @ResponseBody + public ResponseEntity<User> updateUser(@PathVariable("id") int id, + @RequestBody User user) throws InterruptedException { + User currentUser = new User(id, user.getUserName()); + return ResponseEntity.ok(currentUser); + } + + @DeleteMapping(value = "/delete/{id}") + @ResponseBody + public ResponseEntity<Void> deleteUser(@PathVariable("id") int id) throws InterruptedException { + User currentUser = USERS.get(id); + if (currentUser == null) { + return ResponseEntity.noContent().build(); + } + USERS.remove(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java new file mode 100644 index 0000000000..62b93ac01a --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/resttemplate/RestTemplateController.java @@ -0,0 +1,85 @@ +/* + * 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.resttemplate; + +import java.io.IOException; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.http.HttpEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import test.apache.skywalking.apm.testcase.entity.User; + +@RestController +public class RestTemplateController { + + private static final String SUCCESS = "Success"; + + private static final Logger LOGGER = LogManager.getLogger(RestTemplateController.class); + + private static final String URL = "http://localhost:8080/spring-6.x-mixed-servlet-scenario"; + + @RequestMapping("/case/resttemplate") + @ResponseBody + public String restTemplate() throws IOException { + Request request = new Request.Builder().header("mock_header", "mock_value").url(URL + "/case/spring3").build(); + Response response = new OkHttpClient().newCall(request).execute(); + LOGGER.info(response.toString()); + + // Create user + HttpEntity<User> userEntity = new HttpEntity<>(new User(1, "a")); + new RestTemplate().postForEntity(URL + "/create/", userEntity, Void.class); + + // Find User + new RestTemplate().getForEntity(URL + "/get/{id}", User.class, 1); + + //Modify user + HttpEntity<User> updateUserEntity = new HttpEntity<>(new User(1, "b")); + new RestTemplate().put(URL + "/update/{id}", updateUserEntity, userEntity.getBody().getId(), 1); + + //Delete user + new RestTemplate().delete(URL + "/delete/{id}", 1); + + Request inheritRequest = new Request.Builder().url(URL + "/inherit/child/test").build(); + response = new OkHttpClient().newCall(inheritRequest).execute(); + LOGGER.info(response.toString()); + + Request implRequestMappingRequest = new Request.Builder().url(URL + "/impl/requestmapping").build(); + response = new OkHttpClient().newCall(implRequestMappingRequest).execute(); + LOGGER.info(response.toString()); + + Request implRestMappingRequest = new Request.Builder().url(URL + "/impl/restmapping").build(); + response = new OkHttpClient().newCall(implRestMappingRequest).execute(); + LOGGER.info(response.toString()); + + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() { + return SUCCESS; + } + +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java new file mode 100644 index 0000000000..501c691579 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/CaseController.java @@ -0,0 +1,41 @@ +/* + * 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.spring3; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import test.apache.skywalking.apm.testcase.spring3.service.TestServiceBean; + +@Controller +public class CaseController { + + private static final String SUCCESS = "Success"; + + @Autowired + private TestServiceBean testServiceBean; + + @RequestMapping(value = "/case/spring3") + @ResponseBody + public String updateUser() { + testServiceBean.doSomeBusiness("test"); + return SUCCESS; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.java new file mode 100644 index 0000000000..274aaf490e --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/component/TestComponentBean.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.spring3.component; + +import org.springframework.stereotype.Component; + +@Component +public class TestComponentBean { + + public String componentMethod(String name) { + return name + "-" + "dealWith-component"; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.java new file mode 100644 index 0000000000..84f871ef55 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/dao/TestRepositoryBean.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.spring3.dao; + +import org.springframework.stereotype.Repository; + +@Repository +public class TestRepositoryBean { + + public String doSomeStuff(String name) { + return name + "-dealWithRepository"; + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.java new file mode 100644 index 0000000000..811fe11648 --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/java/test/apache/skywalking/apm/testcase/spring3/service/TestServiceBean.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.spring3.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import test.apache.skywalking.apm.testcase.spring3.dao.TestRepositoryBean; +import test.apache.skywalking.apm.testcase.spring3.component.TestComponentBean; + +@Service +public class TestServiceBean { + @Autowired + private TestComponentBean componentBean; + + @Autowired + private TestRepositoryBean repositoryBean; + + public void doSomeBusiness(String name) { + componentBean.componentMethod(name); + repositoryBean.doSomeStuff(name); + } +} diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..b5cda5ae8a --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + ~ + --> +<Configuration status="WARN"> + <Appenders> + <Console name="Console" target="SYSTEM_ERR"> + <PatternLayout charset="UTF-8" pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] - %l - %m%n"/> + </Console> + </Appenders> + <Loggers> + <Root level="WARN"> + <AppenderRef ref="Console"/> + </Root> + </Loggers> +</Configuration> diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml new file mode 100644 index 0000000000..b8a2885d3e --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/spring-mvc-servlet.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + ~ + --> + +<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.0.xsd + http://www.springframework.org/schema/mvc + http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> + <context:component-scan base-package="test.apache.skywalking.apm.testcase"/> + <mvc:annotation-driven/> +</beans> \ No newline at end of file diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/web.xml b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..e64a37771f --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/src/main/webapp/WEB-INF/web.xml @@ -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. + ~ + --> + +<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee + http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" + version="3.1"> + <display-name>skywalking-spring-6.x-mixed-servlet-scenario</display-name> + + <servlet> + <servlet-name>spring-mvc</servlet-name> + <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> + <load-on-startup>1</load-on-startup> + </servlet> + <servlet-mapping> + <servlet-name>spring-mvc</servlet-name> + <url-pattern>/</url-pattern> + </servlet-mapping> +</web-app> diff --git a/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/support-version.list b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/support-version.list new file mode 100644 index 0000000000..fed709bd7c --- /dev/null +++ b/test/plugin/scenarios/spring-6.x-mixed-servlet-scenario/support-version.list @@ -0,0 +1,5 @@ +# Single version by design: this scenario tests the mixed javax+jakarta CLASSPATH +# condition (issue #13938), which is resolved by servlet-commons' runtime instanceof +# dispatch and is independent of the Spring version. Version compatibility across +# 6.0/6.1/6.2 is already covered by spring-6.x-scenario. Do NOT expand this list. +6.2.19 diff --git a/test/plugin/scenarios/tomcat-10x-scenario/config/expectedData.yaml b/test/plugin/scenarios/tomcat-10x-scenario/config/expectedData.yaml index dc03590cdd..030fc841ae 100644 --- a/test/plugin/scenarios/tomcat-10x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/tomcat-10x-scenario/config/expectedData.yaml @@ -34,6 +34,8 @@ segmentItems: tags: - {key: url, value: 'http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario'} - {key: http.method, value: POST} + - {key: http.params, value: 'q1=[v1]'} + - {key: http.headers, value: 'mock_header=[mock_value]'} - {key: http.status_code, value: "200"} refs: - {parentEndpoint: 'GET:/tomcat-10x-scenario/case/tomcat-10x-scenario', networkAddress: 'localhost:8080', @@ -55,7 +57,7 @@ segmentItems: peer: localhost:8080 skipAnalysis: false tags: - - {key: url, value: 'http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario'} + - {key: url, value: 'http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario?q1=v1'} - {key: http.method, value: POST} - {key: http.status_code, value: "200"} - operationName: GET:/tomcat-10x-scenario/case/tomcat-10x-scenario diff --git a/test/plugin/scenarios/tomcat-10x-scenario/configuration.yml b/test/plugin/scenarios/tomcat-10x-scenario/configuration.yml index fac58bdfd1..f28e57d9e7 100644 --- a/test/plugin/scenarios/tomcat-10x-scenario/configuration.yml +++ b/test/plugin/scenarios/tomcat-10x-scenario/configuration.yml @@ -17,3 +17,5 @@ type: tomcat entryService: http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario healthCheck: http://localhost:8080/tomcat-10x-scenario/case/healthCheck +environment: + - CATALINA_OPTS="-Dskywalking.plugin.http.include_http_headers=mock_header -Dskywalking.plugin.tomcat.collect_http_params=true" diff --git a/test/plugin/scenarios/tomcat-10x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat10x/CaseServlet.java b/test/plugin/scenarios/tomcat-10x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat10x/CaseServlet.java index 441f4e0e0b..78da65ec8a 100644 --- a/test/plugin/scenarios/tomcat-10x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat10x/CaseServlet.java +++ b/test/plugin/scenarios/tomcat-10x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat10x/CaseServlet.java @@ -37,7 +37,10 @@ public class CaseServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost httpPost = new HttpPost("http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario"); + // Carry a query param and a header so the receiving Tomcat entry span (doPost) exercises + // the tomcat-plugin's http.params + http.headers collection (see configuration.yml). + HttpPost httpPost = new HttpPost("http://localhost:8080/tomcat-10x-scenario/case/tomcat-10x-scenario?q1=v1"); + httpPost.setHeader("mock_header", "mock_value"); ResponseHandler<String> responseHandler = response -> { HttpEntity entity = response.getEntity(); return entity != null ? EntityUtils.toString(entity) : null; diff --git a/test/plugin/scenarios/tomcat-9x-scenario/config/expectedData.yaml b/test/plugin/scenarios/tomcat-9x-scenario/config/expectedData.yaml index 10bd8bee30..2737cc9ca5 100644 --- a/test/plugin/scenarios/tomcat-9x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/tomcat-9x-scenario/config/expectedData.yaml @@ -34,6 +34,8 @@ segmentItems: tags: - {key: url, value: 'http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario'} - {key: http.method, value: POST} + - {key: http.params, value: 'q1=[v1]'} + - {key: http.headers, value: 'mock_header=[mock_value]'} - {key: http.status_code, value: "200"} refs: - {parentEndpoint: 'GET:/tomcat-9x-scenario/case/tomcat-9x-scenario', networkAddress: 'localhost:8080', @@ -55,7 +57,7 @@ segmentItems: peer: localhost:8080 skipAnalysis: false tags: - - {key: url, value: 'http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario'} + - {key: url, value: 'http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario?q1=v1'} - {key: http.method, value: POST} - {key: http.status_code, value: "200"} - operationName: GET:/tomcat-9x-scenario/case/tomcat-9x-scenario diff --git a/test/plugin/scenarios/tomcat-9x-scenario/configuration.yml b/test/plugin/scenarios/tomcat-9x-scenario/configuration.yml index 523a817486..b345aba8f7 100644 --- a/test/plugin/scenarios/tomcat-9x-scenario/configuration.yml +++ b/test/plugin/scenarios/tomcat-9x-scenario/configuration.yml @@ -17,3 +17,5 @@ type: tomcat entryService: http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario healthCheck: http://localhost:8080/tomcat-9x-scenario/case/healthCheck +environment: + - CATALINA_OPTS="-Dskywalking.plugin.http.include_http_headers=mock_header -Dskywalking.plugin.tomcat.collect_http_params=true" diff --git a/test/plugin/scenarios/tomcat-9x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat9x/CaseServlet.java b/test/plugin/scenarios/tomcat-9x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat9x/CaseServlet.java index 9b47be8639..e9dc5969c9 100644 --- a/test/plugin/scenarios/tomcat-9x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat9x/CaseServlet.java +++ b/test/plugin/scenarios/tomcat-9x-scenario/src/main/java/org/apache/skywalking/apm/testcase/tomcat9x/CaseServlet.java @@ -37,7 +37,10 @@ public class CaseServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost httpPost = new HttpPost("http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario"); + // Carry a query param and a header so the receiving Tomcat entry span (doPost) exercises + // the tomcat-plugin's http.params + http.headers collection (see configuration.yml). + HttpPost httpPost = new HttpPost("http://localhost:8080/tomcat-9x-scenario/case/tomcat-9x-scenario?q1=v1"); + httpPost.setHeader("mock_header", "mock_value"); ResponseHandler<String> responseHandler = response -> { HttpEntity entity = response.getEntity(); return entity != null ? EntityUtils.toString(entity) : null;
