From 7fdfcdb3952d4fbc2d9c6bfcfd47ef8b0cbe7d03 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Tue, 14 Feb 2023 15:11:41 -0800 Subject: [PATCH 01/37] JavaScript snippet injection --- .../javaagent-unit-tests/build.gradle.kts | 9 + .../servlet/v3_0/snippet/InjectionTest.java | 184 +++++++++++++++++ .../SnippetInjectingResponseWrapperTest.java | 189 ++++++++++++++++++ .../servlet/v3_0/snippet/TestUtil.java | 30 +++ .../test/resources/htmlWithoutHeadTag.html | 7 + .../src/test/resources/staticHtmlAfter.html | 11 + .../resources/staticHtmlChineseAfter.html | 11 + .../resources/staticHtmlChineseOrigin.html | 10 + .../src/test/resources/staticHtmlOrigin.html | 10 + .../servlet-3.0/javaagent/build.gradle.kts | 2 + .../servlet/v3_0/Servlet3Advice.java | 7 + .../v3_0/Servlet3InstrumentationModule.java | 6 + .../Servlet3OutputStreamWriteBytesAdvice.java | 32 +++ ...OutputStreamWriteBytesAndOffsetAdvice.java | 33 +++ .../Servlet3OutputStreamWriteIntAdvice.java | 31 +++ .../servlet/v3_0/snippet/Injection.java | 34 ++++ .../servlet/v3_0/snippet/InjectionState.java | 70 +++++++ .../ServletOutputStreamInjectionHelper.java | 90 +++++++++ .../snippet/SnippetInjectingPrintWriter.java | 52 +++++ .../SnippetInjectingResponseWrapper.java | 178 +++++++++++++++++ .../test/groovy/AbstractServlet3Test.groovy | 53 +++++ .../src/test/groovy/JettyServlet3Test.groovy | 12 +- .../src/test/groovy/TestServlet3.groovy | 40 ++++ .../src/test/groovy/TomcatServlet3Test.groovy | 10 + .../test/java/RequestDispatcherServlet.java | 10 + .../servlet/ExperimentalSnippetHolder.java | 27 +++ .../ServletOutputStreamInstrumentation.java | 65 ++++++ settings.gradle.kts | 1 + .../testing/junit/http/ServerEndpoint.java | 28 ++- 29 files changed, 1239 insertions(+), 3 deletions(-) create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/htmlWithoutHeadTag.html create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlAfter.html create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseAfter.html create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseOrigin.html create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlOrigin.html create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Injection.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java create mode 100644 instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java create mode 100644 instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts new file mode 100644 index 000000000000..1046d2b6fece --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { + testImplementation("javax.servlet:javax.servlet-api:3.0.1") + testImplementation(project(":instrumentation:servlet:servlet-common:bootstrap")) + testImplementation(project(":instrumentation:servlet:servlet-3.0:javaagent")) +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java new file mode 100644 index 000000000000..2472deee295d --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java @@ -0,0 +1,184 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFile; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import javax.servlet.ServletOutputStream; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class InjectionTest { + + @Test + void testInjectionForStringContainHeadTag() throws IOException { + String testSnippet = "\n "; + ExperimentalSnippetHolder.setSnippet(testSnippet); + // read the originalFile + String original = readFile("staticHtmlOrigin.html"); + // read the correct answer + String correct = readFile("staticHtmlAfter.html"); + byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); + SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); + when(response.isCommitted()).thenReturn(false); + when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); + InjectionState obj = new InjectionState(response); + + StringWriter writer = new StringWriter(); + + ServletOutputStream sp = + new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + writer.write(b); + } + }; + boolean injected = + ServletOutputStreamInjectionHelper.handleWrite( + originalBytes, 0, originalBytes.length, obj, sp); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); + assertThat(injected).isEqualTo(true); + writer.flush(); + + String result = writer.toString(); + writer.close(); + assertThat(result).isEqualTo(correct); + } + + @Test + @Disabled + void testInjectionForChinese() throws IOException { + String testSnippet = "\n "; + ExperimentalSnippetHolder.setSnippet(testSnippet); + // read the originalFile + String original = readFile("staticHtmlChineseOrigin.html"); + // read the correct answer + String correct = readFile("staticHtmlChineseAfter.html"); + byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); + SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); + when(response.isCommitted()).thenReturn(false); + when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); + InjectionState obj = new InjectionState(response); + + StringWriter writer = new StringWriter(); + + ServletOutputStream sp = + new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + writer.write(b); + } + }; + boolean injected = + ServletOutputStreamInjectionHelper.handleWrite( + originalBytes, 0, originalBytes.length, obj, sp); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); + assertThat(injected).isEqualTo(true); + writer.flush(); + + String result = writer.toString(); + writer.close(); + assertThat(result).isEqualTo(correct); + } + + @Test + void testInjectionForStringWithoutHeadTag() throws IOException { + String testSnippet = "\n "; + ExperimentalSnippetHolder.setSnippet(testSnippet); + // read the originalFile + String original = readFile("htmlWithoutHeadTag.html"); + + byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); + SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); + when(response.isCommitted()).thenReturn(false); + when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); + InjectionState obj = new InjectionState(response); + StringWriter writer = new StringWriter(); + + ServletOutputStream sp = + new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + writer.write(b); + } + }; + boolean injected = + ServletOutputStreamInjectionHelper.handleWrite( + originalBytes, 0, originalBytes.length, obj, sp); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(0); + assertThat(injected).isEqualTo(false); + writer.flush(); + String result = writer.toString(); + writer.close(); + assertThat(result).isEqualTo(""); + } + + @Test + void testHalfHeadTag() throws IOException { + String testSnippet = "\n "; + ExperimentalSnippetHolder.setSnippet(testSnippet); + // read the original string + String originalFirstPart = "\n" + "\n" + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + byte[] originalSecondPartBytes = originalSecondPart.getBytes(StandardCharsets.UTF_8); + injected = + ServletOutputStreamInjectionHelper.handleWrite( + originalSecondPartBytes, 0, originalSecondPartBytes.length, obj, sp); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); + assertThat(injected).isEqualTo(true); + String correctSecondPart = + "ad>\n" + + " \n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + writer.flush(); + result = writer.toString(); + assertThat(result).isEqualTo(correctSecondPart); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java new file mode 100644 index 000000000000..4b692622bf10 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java @@ -0,0 +1,189 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFile; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.Charset; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class SnippetInjectingResponseWrapperTest { + + @Test + void testInjectToTextHtml() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlOrigin.html"); + String correct = readFile("staticHtmlAfter.html"); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("text/html"); + when(response.getStatus()).thenReturn(200); + when(response.containsHeader("content-type")).thenReturn(true); + StringWriter writer = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + ExperimentalSnippetHolder.setSnippet("\n "); + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + responseWrapper.getWriter().write(original); + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(correct); + } + + @Test + @Disabled + void testInjectToChineseTextHtml() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlChineseOrigin.html"); + String correct = readFile("staticHtmlChineseAfter.html"); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("text/html"); + + StringWriter writer = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + ExperimentalSnippetHolder.setSnippet("\n "); + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + responseWrapper.getWriter().write(original); + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(correct); + } + + @Test + void shouldNotInjectToTextHtml() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlOrigin.html"); + + StringWriter writer = new StringWriter(); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("not/text"); + when(response.getStatus()).thenReturn(200); + when(response.containsHeader("content-type")).thenReturn(true); + + when(response.getWriter()).thenReturn(new PrintWriter(writer, true)); + ExperimentalSnippetHolder.setSnippet("\n "); + + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + responseWrapper.getWriter().write(original); + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(original); + } + + @Test + void testWriteInt() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlOrigin.html"); + String correct = readFile("staticHtmlAfter.html"); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("text/html"); + + StringWriter writer = new StringWriter(); + // StringWriter correctWriter = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + ExperimentalSnippetHolder.setSnippet("\n "); + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + byte[] originalBytes = original.getBytes(Charset.defaultCharset().name()); + // byte[] correctBytes = correct.getBytes(UTF_8); + // PrintWriter correctPw = new PrintWriter(correctWriter); + for (int i = 0; i < originalBytes.length; i++) { + responseWrapper.getWriter().write(originalBytes[i]); + } + + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(correct); + } + + @Test + void testWriteCharArray() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlChineseOrigin.html"); + String correct = readFile("staticHtmlChineseAfter.html"); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("text/html"); + when(response.getStatus()).thenReturn(200); + when(response.containsHeader("content-type")).thenReturn(true); + + StringWriter writer = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + ExperimentalSnippetHolder.setSnippet("\n "); + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + char[] originalChars = original.toCharArray(); + responseWrapper.getWriter().write(originalChars, 0, originalChars.length); + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(correct); + } + + @Test + void testWriteWithOffset() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlChineseOrigin.html"); + String correct = readFile("staticHtmlChineseAfter.html"); + String extraBuffer = "this buffer should not be print out"; + original = extraBuffer + original; + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("text/html"); + when(response.getStatus()).thenReturn(200); + when(response.containsHeader("content-type")).thenReturn(true); + + StringWriter writer = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + ExperimentalSnippetHolder.setSnippet("\n "); + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + + responseWrapper + .getWriter() + .write(original, extraBuffer.length(), original.length() - extraBuffer.length()); + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(correct); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java new file mode 100644 index 000000000000..fb5fec9fcb6e --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class TestUtil { + + public static String readFile(String resourceName) throws IOException { + InputStream in = + SnippetInjectingResponseWrapperTest.class + .getClassLoader() + .getResourceAsStream(resourceName); + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = in.read(buffer)) != -1) { + result.write(buffer, 0, length); + } + return result.toString(StandardCharsets.UTF_8.name()); + } + + private TestUtil() {} +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/htmlWithoutHeadTag.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/htmlWithoutHeadTag.html new file mode 100644 index 000000000000..e0a5f5d9d61c --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/htmlWithoutHeadTag.html @@ -0,0 +1,7 @@ + + + + +

without head tag

+ + \ No newline at end of file diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlAfter.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlAfter.html new file mode 100644 index 000000000000..ba5ff4ad3ac1 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlAfter.html @@ -0,0 +1,11 @@ + + + + + + Title + + + + + \ No newline at end of file diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseAfter.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseAfter.html new file mode 100644 index 000000000000..81166f54dd69 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseAfter.html @@ -0,0 +1,11 @@ + + + + + + Title + + +

欢迎光临

+ + \ No newline at end of file diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseOrigin.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseOrigin.html new file mode 100644 index 000000000000..5594942811a7 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseOrigin.html @@ -0,0 +1,10 @@ + + + + + Title + + +

欢迎光临

+ + \ No newline at end of file diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlOrigin.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlOrigin.html new file mode 100644 index 000000000000..de155ea51d7d --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlOrigin.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts b/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts index 689d229e20fd..e9d9f0a6d093 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts @@ -23,9 +23,11 @@ dependencies { compileOnly("javax.servlet:javax.servlet-api:3.0.1") testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent")) + testImplementation(project(":instrumentation:servlet:servlet-common:bootstrap")) testLibrary("org.eclipse.jetty:jetty-server:8.0.0.v20110901") testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901") + // TODO (trask?) test against tomcat 7 (servlet 3.0) testLibrary("org.apache.tomcat.embed:tomcat-embed-core:8.0.41") testLibrary("org.apache.tomcat.embed:tomcat-embed-jasper:8.0.41") diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java index 9da2aa307eda..d625789f5595 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java @@ -6,14 +6,17 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.SnippetInjectingResponseWrapper.FAKE_SNIPPET_HEADER; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.bootstrap.CallDepth; import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge; +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver; import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.SnippetInjectingResponseWrapper; import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -40,6 +43,10 @@ public static void onEnter( } HttpServletRequest httpServletRequest = (HttpServletRequest) request; + if (!ExperimentalSnippetHolder.getSnippet().isEmpty() + && !((HttpServletResponse) response).containsHeader(FAKE_SNIPPET_HEADER)) { + response = new SnippetInjectingResponseWrapper((HttpServletResponse) response); + } callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey()); callDepth.getAndIncrement(); diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InstrumentationModule.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InstrumentationModule.java index 299efcdd1f5c..94935ade7b7a 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InstrumentationModule.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InstrumentationModule.java @@ -16,6 +16,7 @@ import io.opentelemetry.javaagent.instrumentation.servlet.common.async.AsyncStartInstrumentation; import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseInstrumentation; import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterInstrumentation; +import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletOutputStreamInstrumentation; import java.util.List; import net.bytebuddy.matcher.ElementMatcher; @@ -44,6 +45,11 @@ BASE_PACKAGE, adviceClassName(".Servlet3AsyncContextStartAdvice")), adviceClassName(".Servlet3Advice"), adviceClassName(".Servlet3InitAdvice"), adviceClassName(".Servlet3FilterInitAdvice")), + new ServletOutputStreamInstrumentation( + BASE_PACKAGE, + adviceClassName(".Servlet3OutputStreamWriteBytesAndOffsetAdvice"), + adviceClassName(".Servlet3OutputStreamWriteBytesAdvice"), + adviceClassName(".Servlet3OutputStreamWriteIntAdvice")), new HttpServletResponseInstrumentation( BASE_PACKAGE, adviceClassName(".Servlet3ResponseSendAdvice"))); } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java new file mode 100644 index 000000000000..c2258978b0cf --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper; +import java.io.IOException; +import javax.servlet.ServletOutputStream; +import net.bytebuddy.asm.Advice; + +public class Servlet3OutputStreamWriteBytesAdvice { + + @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, suppress = Throwable.class) + public static boolean methodEnter( + @Advice.This ServletOutputStream servletOutputStream, @Advice.Argument(0) byte[] write) + throws IOException { + InjectionState state = getInjectionState(servletOutputStream); + if (state == null) { + return true; + } + // if handleWrite return true, then it means the injection has happened and the 'write' + // manipulate is done. the function would return false then, meaning skip the original write + // function + return !ServletOutputStreamInjectionHelper.handleWrite( + write, 0, write.length, state, servletOutputStream); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java new file mode 100644 index 000000000000..69effc3bc789 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper.handleWrite; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; +import java.io.IOException; +import javax.servlet.ServletOutputStream; +import net.bytebuddy.asm.Advice; + +public class Servlet3OutputStreamWriteBytesAndOffsetAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, suppress = Throwable.class) + public static boolean methodEnter( + @Advice.This ServletOutputStream servletOutputStream, + @Advice.Argument(value = 0) byte[] write, + @Advice.Argument(value = 1) int off, + @Advice.Argument(value = 2) int len) + throws IOException { + InjectionState state = getInjectionState(servletOutputStream); + if (state == null) { + return true; + } + // if handleWrite return true, then it means the injection has happened and the 'write' + // manipulate is done. the function would return false then, meaning skip the original write + // function + return !handleWrite(write, off, len, state, servletOutputStream); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java new file mode 100644 index 000000000000..1d59a2ad2a80 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper.handleWrite; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; +import java.io.IOException; +import javax.servlet.ServletOutputStream; +import net.bytebuddy.asm.Advice; + +public class Servlet3OutputStreamWriteIntAdvice { + + @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, suppress = Throwable.class) + public static boolean methodEnter( + @Advice.This ServletOutputStream servletOutputStream, @Advice.Argument(0) int write) + throws IOException { + InjectionState state = getInjectionState(servletOutputStream); + if (state == null) { + return true; + } + // if handleWrite return true, then it means the injection has happened and the 'write' + // manipulate is done. the function would return false then, meaning skip the original write + // function + return !handleWrite(state, servletOutputStream, write); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Injection.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Injection.java new file mode 100644 index 000000000000..2ee7578f2600 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Injection.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import io.opentelemetry.instrumentation.api.util.VirtualField; +import javax.servlet.ServletOutputStream; + +public class Injection { + + private static final VirtualField virtualField = + VirtualField.find(ServletOutputStream.class, InjectionState.class); + + public static void initializeInjectionStateIfNeeded( + ServletOutputStream servletOutputStream, SnippetInjectingResponseWrapper wrapper) { + InjectionState state = virtualField.get(servletOutputStream); + if (!wrapper.isContentTypeTextHtml()) { + virtualField.set(servletOutputStream, null); + return; + } + if (state == null || state.getWrapper() != wrapper) { + state = new InjectionState(wrapper); + virtualField.set(servletOutputStream, state); + } + } + + public static InjectionState getInjectionState(ServletOutputStream servletOutputStream) { + return virtualField.get(servletOutputStream); + } + + private Injection() {} +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java new file mode 100644 index 000000000000..d5dbe07830ab --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +public class InjectionState { + private static final int HEAD_TAG_WRITTEN_FAKE_VALUE = -1; + private static final int HEAD_TAG_LENGTH = "".length(); + private final SnippetInjectingResponseWrapper wrapper; + private int headTagBytesSeen = 0; + + public InjectionState(SnippetInjectingResponseWrapper wrapper) { + this.wrapper = wrapper; + } + + public int getHeadTagBytesSeen() { + return headTagBytesSeen; + } + + public String getCharacterEncoding() { + return wrapper.getCharacterEncoding(); + } + + public void setHeadTagWritten() { + headTagBytesSeen = HEAD_TAG_WRITTEN_FAKE_VALUE; + } + + public boolean isHeadTagWritten() { + return headTagBytesSeen == HEAD_TAG_WRITTEN_FAKE_VALUE; + } + + /** + * Returns true when the byte is the last character of "" and now is the right time to + * inject. Otherwise, returns false. + */ + public boolean processByte(int b) { + if (isHeadTagWritten()) { + return false; + } + if (inHeadTag(b)) { + headTagBytesSeen++; + } else { + headTagBytesSeen = 0; + } + return headTagBytesSeen == HEAD_TAG_LENGTH; + } + + private boolean inHeadTag(int b) { + if (headTagBytesSeen == 0 && b == '<') { + return true; + } else if (headTagBytesSeen == 1 && b == 'h') { + return true; + } else if (headTagBytesSeen == 2 && b == 'e') { + return true; + } else if (headTagBytesSeen == 3 && b == 'a') { + return true; + } else if (headTagBytesSeen == 4 && b == 'd') { + return true; + } else if (headTagBytesSeen == 5 && b == '>') { + return true; + } + return false; + } + + public SnippetInjectingResponseWrapper getWrapper() { + return wrapper; + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java new file mode 100644 index 000000000000..235c5fdc2188 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java @@ -0,0 +1,90 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import static java.util.logging.Level.FINE; + +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.logging.Logger; +import javax.servlet.ServletOutputStream; + +public class ServletOutputStreamInjectionHelper { + + private static final Logger logger = + Logger.getLogger(ServletOutputStreamInjectionHelper.class.getName()); + + /** + * return true means this method performed the injection, return false means it didn't inject + * anything Servlet3OutputStreamWriteAdvice would skip the write method when the return value is + * true, and would write the original bytes when the return value is false. + */ + public static boolean handleWrite( + byte[] original, int off, int length, InjectionState state, ServletOutputStream out) + throws IOException { + if (state.isHeadTagWritten()) { + return false; + } + int i; + boolean endOfHeadTagFound = false; + for (i = off; i < length && i - off < length; i++) { + if (state.processByte(original[i])) { + endOfHeadTagFound = true; + break; + } + } + if (!endOfHeadTagFound) { + return false; + } + state.setHeadTagWritten(); // set before write to avoid recursive loop + if (state.getWrapper().isNotSafeToInject()) { + return false; + } + byte[] snippetBytes; + try { + snippetBytes = ExperimentalSnippetHolder.getSnippetBytes(state.getCharacterEncoding()); + } catch (UnsupportedEncodingException e) { + logger.log(FINE, "UnsupportedEncodingException", e); + return false; + } + // updating Content-Length before any further writing in case that writing triggers a flush + state.getWrapper().updateContentLengthIfPreviouslySet(); + out.write(original, off, i + 1); + out.write(snippetBytes); + out.write(original, i + 1, length - i - 1); + return true; + } + + public static boolean handleWrite(InjectionState state, ServletOutputStream out, int b) + throws IOException { + if (state.isHeadTagWritten()) { + return false; + } + if (!state.processByte(b)) { + return false; + } + state.setHeadTagWritten(); // set before write to avoid recursive loop + + if (state.getWrapper().isNotSafeToInject()) { + return false; + } + byte[] snippetBytes; + try { + snippetBytes = ExperimentalSnippetHolder.getSnippetBytes(state.getCharacterEncoding()); + } catch (UnsupportedEncodingException e) { + logger.log(FINE, "UnsupportedEncodingException", e); + return false; + } + state.getWrapper().updateContentLengthIfPreviouslySet(); + out.write(b); + + out.write(snippetBytes); + return true; + } + + private ServletOutputStreamInjectionHelper() {} +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java new file mode 100644 index 000000000000..88e3a02a21b0 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import java.io.PrintWriter; + +public class SnippetInjectingPrintWriter extends PrintWriter { + private final String snippet; + private final InjectionState state; + + public SnippetInjectingPrintWriter( + PrintWriter writer, String snippet, SnippetInjectingResponseWrapper wrapper) { + super(writer); + state = new InjectionState(wrapper); + this.snippet = snippet; + } + + @Override + public void write(String s, int off, int len) { + for (int i = off; i < s.length() && i - off < len; i++) { + write(s.charAt(i)); + } + } + + @Override + public void write(int b) { + super.write(b); + if (state.isHeadTagWritten()) { + return; + } + boolean endOfHeadTagFound = state.processByte(b); + if (!endOfHeadTagFound) { + return; + } + state.setHeadTagWritten(); // set before write to avoid recursive loop + if (state.getWrapper().isNotSafeToInject()) { + return; + } + state.getWrapper().updateContentLengthIfPreviouslySet(); + super.write(snippet); + } + + @Override + public void write(char[] buf, int off, int len) { + for (int i = off; i < buf.length && i - off < len; i++) { + write(buf[i]); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java new file mode 100644 index 000000000000..85e5a40c80a1 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -0,0 +1,178 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.initializeInjectionStateIfNeeded; +import static java.util.logging.Level.FINE; + +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.logging.Logger; +import javax.annotation.Nullable; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +/** + * Notes on Content-Length: the snippet length is only added to the content length when injection + * occurs and the content length was set previously. + * + *

If the Content-Length is set after snippet injection occurs (either for the first time or is + * set again for some reason),we intentionally do not add the snippet length, because the + * application server may be making that call at the end of a request when it sees the request has + * not been submitted, in which case it is likely using the real length of content that has been + * written, including the snippet length. + */ +public class SnippetInjectingResponseWrapper extends HttpServletResponseWrapper { + private static final Logger logger = Logger.getLogger(HttpServletResponseWrapper.class.getName()); + public static final String FAKE_SNIPPET_HEADER = "FAKE_SNIPPET_HEADER"; + private static final String SNIPPET = ExperimentalSnippetHolder.getSnippet(); + private static final int SNIPPET_LENGTH = SNIPPET.length(); + + private static final int UNSET = -1; + @Nullable private static final MethodHandle setContentLengthLongHandler = getMethodHandle(); + + private long contentLength = UNSET; + + private SnippetInjectingPrintWriter snippetInjectingPrintWriter = null; + + public SnippetInjectingResponseWrapper(HttpServletResponse response) { + super(response); + } + + @Override + public boolean containsHeader(String name) { + // override this function in order to make sure the response is wrapped + // but not wrapped twice + // we didn't use the traditional method req.setAttribute + // because async would set the original request attribute but didn't pass down the wrapped + // response + // then the response would never be wrapped again + // see also https://docs.oracle.com/javaee/7/api/javax/servlet/AsyncContext.html + if (name.equals(FAKE_SNIPPET_HEADER)) { + return true; + } + return super.containsHeader(name); + } + + @Override + public void setHeader(String name, String value) { + // checking content-type is just an optimization to avoid unnecessary parsing + if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { + try { + contentLength = Long.valueOf(value); + } catch (NumberFormatException ex) { + logger.log(FINE, "NumberFormatException", ex); + } + } + super.setHeader(name, value); + } + + @Override + public void addHeader(String name, String value) { + // checking content-type is just an optimization to avoid unnecessary parsing + if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { + try { + contentLength = Long.valueOf(value); + } catch (NumberFormatException ex) { + logger.log(FINE, "NumberFormatException", ex); + } + } + super.addHeader(name, value); + } + + @Override + public void setIntHeader(String name, int value) { + // checking content-type is just an optimization to avoid unnecessary parsing + if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { + contentLength = value; + } + super.setIntHeader(name, value); + } + + @Override + public void addIntHeader(String name, int value) { + // checking content-type is just an optimization to avoid unnecessary parsing + if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { + contentLength = value; + } + super.addIntHeader(name, value); + } + + @Override + public void setContentLength(int len) { + contentLength = len; + super.setContentLength(len); + } + + @Nullable + private static MethodHandle getMethodHandle() { + try { + return MethodHandles.lookup() + .findSpecial( + HttpServletResponseWrapper.class, + "setContentLengthLong", + MethodType.methodType(void.class), + SnippetInjectingResponseWrapper.class); + } catch (NoSuchMethodException | IllegalAccessException e) { + logger.log(FINE, "SnippetInjectingResponseWrapper setContentLengthLong", e); + return null; + } + } + + public void setContentLengthLong(long length) throws Throwable { + contentLength = length; + if (setContentLengthLongHandler == null) { + super.setContentLength((int) length); + } else { + setContentLengthLongHandler.invokeWithArguments(this, length); + } + } + + public boolean isContentTypeTextHtml() { + String contentType = super.getContentType(); + if (contentType == null) { + contentType = super.getHeader("content-type"); + } + return contentType != null && contentType.startsWith("text/html"); + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + ServletOutputStream output = super.getOutputStream(); + initializeInjectionStateIfNeeded(output, this); + return output; + } + + @Override + public PrintWriter getWriter() throws IOException { + if (!isContentTypeTextHtml()) { + return super.getWriter(); + } + if (snippetInjectingPrintWriter == null) { + snippetInjectingPrintWriter = + new SnippetInjectingPrintWriter(super.getWriter(), SNIPPET, this); + } + return snippetInjectingPrintWriter; + } + + public void updateContentLengthIfPreviouslySet() { + if (contentLength != UNSET) { + setContentLength((int) contentLength + SNIPPET_LENGTH); + } + } + + public boolean isNotSafeToInject() { + // if content-length was set and response was already committed (headers sent to the client), + // then not safe to inject because the content-length header cannot be updated to account for + // the snippet length + return isCommitted() && (contentLength != UNSET); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy index 6dbdb60eb1b7..0d48faf71c59 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy @@ -7,6 +7,7 @@ import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest import javax.servlet.Servlet @@ -16,6 +17,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM @@ -50,6 +53,8 @@ abstract class AbstractServlet3Test extends HttpServerTest extends HttpServerTest Test ") + def request = request(HTML2, "GET") + def response = client.execute(request).aggregate().join() + + expect: + response.status().code() == HTML2.status + // check response content-length header + String result = "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + "" + response.contentUtf8() == result + response.headers().contentLength() == result.length() + } + + def "snippet injection with PrintWriter"() { + setup: + ExperimentalSnippetHolder.setSnippet("\n ") + def request = request(HTML, "GET") + def response = client.execute(request).aggregate().join() + + expect: + response.status().code() == HTML.status + String result = "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + "" + + response.contentUtf8() == result + response.headers().contentLength() == result.length() + } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy index 8d1549ecf086..ecb656bec4be 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy @@ -18,6 +18,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT @@ -170,6 +172,8 @@ class JettyServlet3TestForward extends JettyDispatchTest { super.setupServlets(context) addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Forward) @@ -207,6 +211,8 @@ class JettyServlet3TestInclude extends JettyDispatchTest { super.setupServlets(context) addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Include) @@ -232,7 +238,8 @@ class JettyServlet3TestDispatchImmediate extends JettyDispatchTest { @Override protected void setupServlets(ServletContextHandler context) { super.setupServlets(context) - + addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchImmediate) @@ -260,7 +267,8 @@ class JettyServlet3TestDispatchAsync extends JettyDispatchTest { @Override protected void setupServlets(ServletContextHandler context) { super.setupServlets(context) - + addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchAsync) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy index 2014402497da..83e1873f3c20 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy @@ -5,6 +5,7 @@ import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint + import javax.servlet.RequestDispatcher import javax.servlet.ServletException import javax.servlet.annotation.WebServlet @@ -17,6 +18,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT @@ -70,6 +73,19 @@ class TestServlet3 { break case EXCEPTION: throw new ServletException(endpoint.body) + case HTML: + resp.contentType = "text/html" + resp.status = endpoint.status + resp.setContentLengthLong(endpoint.body.length()) + resp.writer.print(endpoint.body) + break + case HTML2: + resp.contentType = "text/html" + resp.status = endpoint.status + resp.setContentLength(endpoint.body.length()) + byte[] body = endpoint.body.getBytes() + resp.getOutputStream().write(body, 0, body.length) + break } } } @@ -141,6 +157,20 @@ class TestServlet3 { writer.close() } throw new ServletException(endpoint.body) + break + case HTML: + resp.contentType = "text/html" + resp.status = endpoint.status + resp.setContentLength(endpoint.body.length()) + resp.writer.print(endpoint.body) + context.complete() + break + case HTML2: + resp.contentType = "text/html" + resp.status = endpoint.status + resp.getOutputStream().print(endpoint.body) + context.complete() + break } } } finally { @@ -197,6 +227,16 @@ class TestServlet3 { resp.status = endpoint.status resp.writer.print(endpoint.body) throw new ServletException(endpoint.body) + case HTML: + resp.status = endpoint.status + resp.contentType = "text/html" + resp.writer.print(endpoint.body) + break + case HTML2: + resp.contentType = "text/html" + resp.status = endpoint.status + resp.getOutputStream().print(endpoint.body) + break } } } finally { diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy index 93cfeacd8e6b..ba5dd0280bb8 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy @@ -30,6 +30,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM @@ -343,6 +345,8 @@ class TomcatServlet3TestForward extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Forward) } } @@ -384,6 +388,8 @@ class TomcatServlet3TestInclude extends TomcatDispatchTest { addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Include) } } @@ -411,6 +417,8 @@ class TomcatServlet3TestDispatchImmediate extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) } } @@ -434,6 +442,8 @@ class TomcatServlet3TestDispatchAsync extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java index 25e2e68d379a..5d94f95e3c41 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java @@ -3,6 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2; + +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; @@ -37,6 +41,12 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) String target = req.getServletPath().replace("/dispatch", ""); ServletContext context = getServletContext(); RequestDispatcher dispatcher = context.getRequestDispatcher(target); + // for HTML test case, set content type before calling include as + // "include" reject modify on resp later + // check https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html + if (ServerEndpoint.forPath(target) == HTML || ServerEndpoint.forPath(target) == HTML2) { + resp.setContentType("text/html"); + } dispatcher.include(req, resp); } } diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java new file mode 100644 index 000000000000..25d90cd27c0c --- /dev/null +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.bootstrap.servlet; + +import java.io.UnsupportedEncodingException; + +public class ExperimentalSnippetHolder { + + private static String snippet = ""; + + public static void setSnippet(String snippet) { + ExperimentalSnippetHolder.snippet = snippet; + } + + public static String getSnippet() { + return snippet; + } + + public static byte[] getSnippetBytes(String encoding) throws UnsupportedEncodingException { + return snippet.getBytes(encoding); + } + + private ExperimentalSnippetHolder() {} +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java new file mode 100644 index 000000000000..df80666d71d1 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.common.service; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class ServletOutputStreamInstrumentation implements TypeInstrumentation { + private final String basePackageName; + private final String writeBytesAndOffsetClassName; + private final String writeBytesClassName; + private final String writeIntAdviceClassName; + + public ServletOutputStreamInstrumentation( + String basePackageName, + String writeBytesAndOffsetClassName, + String writeBytesClassName, + String writeIntAdviceClassName) { + this.basePackageName = basePackageName; + this.writeBytesAndOffsetClassName = writeBytesAndOffsetClassName; + this.writeBytesClassName = writeBytesClassName; + this.writeIntAdviceClassName = writeIntAdviceClassName; + } + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed(basePackageName + ".ServletOutputStream"); + } + + @Override + public ElementMatcher typeMatcher() { + return hasSuperType(namedOneOf(basePackageName + ".ServletOutputStream")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("write") + .and(takesArguments(3)) + .and(takesArgument(0, byte[].class)) + .and(takesArgument(1, int.class)) + .and(takesArgument(2, int.class)) + .and(isPublic()), + writeBytesAndOffsetClassName); + transformer.applyAdviceToMethod( + named("write").and(takesArguments(1)).and(takesArgument(0, byte[].class)).and(isPublic()), + writeBytesClassName); + transformer.applyAdviceToMethod( + named("write").and(takesArguments(1)).and(takesArgument(0, int.class)).and(isPublic()), + writeIntAdviceClassName); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 82aa607457fc..cde794dcd79a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -439,6 +439,7 @@ hideFromDependabot(":instrumentation:servlet:servlet-common:javaagent") hideFromDependabot(":instrumentation:servlet:servlet-javax-common:javaagent") hideFromDependabot(":instrumentation:servlet:servlet-2.2:javaagent") hideFromDependabot(":instrumentation:servlet:servlet-3.0:javaagent") +hideFromDependabot(":instrumentation:servlet:servlet-3.0:javaagent-unit-tests") hideFromDependabot(":instrumentation:servlet:servlet-5.0:javaagent") hideFromDependabot(":instrumentation:spark-2.3:javaagent") hideFromDependabot(":instrumentation:spring:spring-batch-3.0:javaagent") diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java index bccb9b2e323f..1a5f340554dd 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java @@ -31,7 +31,33 @@ public enum ServerEndpoint { AUTH_REQUIRED("authRequired", 200, null), LOGIN("login", 302, null), AUTH_ERROR("basicsecured/endpoint", 401, null), - INDEXED_CHILD("child", 200, ""); + INDEXED_CHILD("child", 200, ""), + HTML( + "html", + 200, + "\n" + + "\n" + + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""), + HTML2( + "html2", + 200, + "\n" + + "\n" + + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""); public static final String ID_ATTRIBUTE_NAME = "test.request.id"; public static final String ID_PARAMETER_NAME = "id"; From 25c2af1d418719550b0f2137cdfd6006fa4ee412 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 14 Feb 2023 15:38:44 -0800 Subject: [PATCH 02/37] Avoid dependency on globals for unit tests --- .../servlet/v3_0/snippet/InjectionTest.java | 25 +++++------- .../SnippetInjectingResponseWrapperTest.java | 38 ++++++++++--------- .../servlet/v3_0/Servlet3Advice.java | 5 ++- .../Servlet3OutputStreamWriteBytesAdvice.java | 6 +-- ...OutputStreamWriteBytesAndOffsetAdvice.java | 12 +++--- .../Servlet3OutputStreamWriteIntAdvice.java | 4 +- .../servlet/v3_0/Servlet3Singletons.java | 9 +++++ ...> OutputStreamSnippetInjectionHelper.java} | 26 +++++++------ .../SnippetInjectingResponseWrapper.java | 23 +++++++---- .../servlet/ExperimentalSnippetHolder.java | 6 --- 10 files changed, 82 insertions(+), 72 deletions(-) rename instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/{ServletOutputStreamInjectionHelper.java => OutputStreamSnippetInjectionHelper.java} (77%) diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java index 2472deee295d..8b170ab9c34b 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java @@ -23,7 +23,6 @@ class InjectionTest { @Test void testInjectionForStringContainHeadTag() throws IOException { String testSnippet = "\n "; - ExperimentalSnippetHolder.setSnippet(testSnippet); // read the originalFile String original = readFile("staticHtmlOrigin.html"); // read the correct answer @@ -43,9 +42,8 @@ public void write(int b) throws IOException { writer.write(b); } }; - boolean injected = - ServletOutputStreamInjectionHelper.handleWrite( - originalBytes, 0, originalBytes.length, obj, sp); + OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); + boolean injected = helper.handleWrite(originalBytes, 0, originalBytes.length, obj, sp); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(injected).isEqualTo(true); writer.flush(); @@ -59,7 +57,6 @@ public void write(int b) throws IOException { @Disabled void testInjectionForChinese() throws IOException { String testSnippet = "\n "; - ExperimentalSnippetHolder.setSnippet(testSnippet); // read the originalFile String original = readFile("staticHtmlChineseOrigin.html"); // read the correct answer @@ -79,9 +76,8 @@ public void write(int b) throws IOException { writer.write(b); } }; - boolean injected = - ServletOutputStreamInjectionHelper.handleWrite( - originalBytes, 0, originalBytes.length, obj, sp); + OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); + boolean injected = helper.handleWrite(originalBytes, 0, originalBytes.length, obj, sp); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(injected).isEqualTo(true); writer.flush(); @@ -112,9 +108,8 @@ public void write(int b) throws IOException { writer.write(b); } }; - boolean injected = - ServletOutputStreamInjectionHelper.handleWrite( - originalBytes, 0, originalBytes.length, obj, sp); + OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); + boolean injected = helper.handleWrite(originalBytes, 0, originalBytes.length, obj, sp); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(0); assertThat(injected).isEqualTo(false); writer.flush(); @@ -126,7 +121,6 @@ public void write(int b) throws IOException { @Test void testHalfHeadTag() throws IOException { String testSnippet = "\n "; - ExperimentalSnippetHolder.setSnippet(testSnippet); // read the original string String originalFirstPart = "\n" + "\n" + ""; byte[] originalSecondPartBytes = originalSecondPart.getBytes(StandardCharsets.UTF_8); injected = - ServletOutputStreamInjectionHelper.handleWrite( - originalSecondPartBytes, 0, originalSecondPartBytes.length, obj, sp); + helper.handleWrite(originalSecondPartBytes, 0, originalSecondPartBytes.length, obj, sp); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(injected).isEqualTo(true); String correctSecondPart = diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java index 4b692622bf10..56c38194959e 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java @@ -10,7 +10,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; @@ -23,7 +22,6 @@ class SnippetInjectingResponseWrapperTest { @Test void testInjectToTextHtml() throws IOException { - // read the originalFile String original = readFile("staticHtmlOrigin.html"); String correct = readFile("staticHtmlAfter.html"); @@ -33,8 +31,9 @@ void testInjectToTextHtml() throws IOException { when(response.containsHeader("content-type")).thenReturn(true); StringWriter writer = new StringWriter(); when(response.getWriter()).thenReturn(new PrintWriter(writer)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); responseWrapper.getWriter().write(original); responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); @@ -58,8 +57,9 @@ void testInjectToChineseTextHtml() throws IOException { StringWriter writer = new StringWriter(); when(response.getWriter()).thenReturn(new PrintWriter(writer)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); responseWrapper.getWriter().write(original); responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); @@ -84,9 +84,10 @@ void shouldNotInjectToTextHtml() throws IOException { when(response.containsHeader("content-type")).thenReturn(true); when(response.getWriter()).thenReturn(new PrintWriter(writer, true)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); responseWrapper.getWriter().write(original); responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); @@ -110,13 +111,14 @@ void testWriteInt() throws IOException { StringWriter writer = new StringWriter(); // StringWriter correctWriter = new StringWriter(); when(response.getWriter()).thenReturn(new PrintWriter(writer)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); - byte[] originalBytes = original.getBytes(Charset.defaultCharset().name()); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); + byte[] originalBytes = original.getBytes(Charset.defaultCharset()); // byte[] correctBytes = correct.getBytes(UTF_8); // PrintWriter correctPw = new PrintWriter(correctWriter); - for (int i = 0; i < originalBytes.length; i++) { - responseWrapper.getWriter().write(originalBytes[i]); + for (byte originalByte : originalBytes) { + responseWrapper.getWriter().write(originalByte); } responseWrapper.getWriter().flush(); @@ -142,8 +144,9 @@ void testWriteCharArray() throws IOException { StringWriter writer = new StringWriter(); when(response.getWriter()).thenReturn(new PrintWriter(writer)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); char[] originalChars = original.toCharArray(); responseWrapper.getWriter().write(originalChars, 0, originalChars.length); responseWrapper.getWriter().flush(); @@ -171,8 +174,9 @@ void testWriteWithOffset() throws IOException { StringWriter writer = new StringWriter(); when(response.getWriter()).thenReturn(new PrintWriter(writer)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); responseWrapper .getWriter() diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java index d625789f5595..a28e7b58d169 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java @@ -43,9 +43,10 @@ public static void onEnter( } HttpServletRequest httpServletRequest = (HttpServletRequest) request; - if (!ExperimentalSnippetHolder.getSnippet().isEmpty() + String snippet = ExperimentalSnippetHolder.getSnippet(); + if (!snippet.isEmpty() && !((HttpServletResponse) response).containsHeader(FAKE_SNIPPET_HEADER)) { - response = new SnippetInjectingResponseWrapper((HttpServletResponse) response); + response = new SnippetInjectingResponseWrapper((HttpServletResponse) response, snippet); } callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey()); callDepth.getAndIncrement(); diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java index c2258978b0cf..9aa550e9fdce 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java @@ -5,10 +5,10 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper; import java.io.IOException; import javax.servlet.ServletOutputStream; import net.bytebuddy.asm.Advice; @@ -26,7 +26,7 @@ public static boolean methodEnter( // if handleWrite return true, then it means the injection has happened and the 'write' // manipulate is done. the function would return false then, meaning skip the original write // function - return !ServletOutputStreamInjectionHelper.handleWrite( - write, 0, write.length, state, servletOutputStream); + return !getSnippetInjectionHelper() + .handleWrite(write, 0, write.length, state, servletOutputStream); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java index 69effc3bc789..ca566ad924ca 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java @@ -5,13 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper.handleWrite; - import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; -import java.io.IOException; -import javax.servlet.ServletOutputStream; import net.bytebuddy.asm.Advice; +import javax.servlet.ServletOutputStream; +import java.io.IOException; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; public class Servlet3OutputStreamWriteBytesAndOffsetAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, suppress = Throwable.class) @@ -28,6 +28,6 @@ public static boolean methodEnter( // if handleWrite return true, then it means the injection has happened and the 'write' // manipulate is done. the function would return false then, meaning skip the original write // function - return !handleWrite(write, off, len, state, servletOutputStream); + return !getSnippetInjectionHelper().handleWrite(write, off, len, state, servletOutputStream); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java index 1d59a2ad2a80..565c82b3171f 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java @@ -5,8 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper.handleWrite; import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; import java.io.IOException; @@ -26,6 +26,6 @@ public static boolean methodEnter( // if handleWrite return true, then it means the injection has happened and the 'write' // manipulate is done. the function would return false then, meaning skip the original write // function - return !handleWrite(state, servletOutputStream, write); + return !getSnippetInjectionHelper().handleWrite(state, servletOutputStream, write); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java index fe4d8a3ce055..8d06e31addc0 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java @@ -8,12 +8,14 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.util.ClassAndMethod; import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver; import io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper; import io.opentelemetry.javaagent.instrumentation.servlet.ServletInstrumenterBuilder; import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext; import io.opentelemetry.javaagent.instrumentation.servlet.common.response.ResponseInstrumenterFactory; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.OutputStreamSnippetInjectionHelper; import javax.servlet.Filter; import javax.servlet.Servlet; import javax.servlet.http.HttpServletRequest; @@ -39,6 +41,9 @@ public final class Servlet3Singletons { private static final Instrumenter RESPONSE_INSTRUMENTER = ResponseInstrumenterFactory.createInstrumenter(INSTRUMENTATION_NAME); + private static final OutputStreamSnippetInjectionHelper SNIPPET_INJECTION_HELPER = + new OutputStreamSnippetInjectionHelper(ExperimentalSnippetHolder.getSnippet()); + public static ServletHelper helper() { return HELPER; } @@ -55,6 +60,10 @@ public static MappingResolver getMappingResolver(Object servletOrFilter) { return null; } + static OutputStreamSnippetInjectionHelper getSnippetInjectionHelper() { + return SNIPPET_INJECTION_HELPER; + } + private static MappingResolver.Factory getMappingResolverFactory(Object servletOrFilter) { boolean servlet = servletOrFilter instanceof Servlet; if (servlet) { diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java similarity index 77% rename from instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java rename to instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java index 235c5fdc2188..6e87ce65fb2d 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java @@ -7,24 +7,29 @@ import static java.util.logging.Level.FINE; -import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import java.io.IOException; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.logging.Logger; -import javax.servlet.ServletOutputStream; -public class ServletOutputStreamInjectionHelper { +public class OutputStreamSnippetInjectionHelper { private static final Logger logger = - Logger.getLogger(ServletOutputStreamInjectionHelper.class.getName()); + Logger.getLogger(OutputStreamSnippetInjectionHelper.class.getName()); + + private final String snippet; + + public OutputStreamSnippetInjectionHelper(String snippet) { + this.snippet = snippet; + } /** * return true means this method performed the injection, return false means it didn't inject * anything Servlet3OutputStreamWriteAdvice would skip the write method when the return value is * true, and would write the original bytes when the return value is false. */ - public static boolean handleWrite( - byte[] original, int off, int length, InjectionState state, ServletOutputStream out) + public boolean handleWrite( + byte[] original, int off, int length, InjectionState state, OutputStream out) throws IOException { if (state.isHeadTagWritten()) { return false; @@ -46,7 +51,7 @@ public static boolean handleWrite( } byte[] snippetBytes; try { - snippetBytes = ExperimentalSnippetHolder.getSnippetBytes(state.getCharacterEncoding()); + snippetBytes = snippet.getBytes(state.getCharacterEncoding()); } catch (UnsupportedEncodingException e) { logger.log(FINE, "UnsupportedEncodingException", e); return false; @@ -59,8 +64,7 @@ public static boolean handleWrite( return true; } - public static boolean handleWrite(InjectionState state, ServletOutputStream out, int b) - throws IOException { + public boolean handleWrite(InjectionState state, OutputStream out, int b) throws IOException { if (state.isHeadTagWritten()) { return false; } @@ -74,7 +78,7 @@ public static boolean handleWrite(InjectionState state, ServletOutputStream out, } byte[] snippetBytes; try { - snippetBytes = ExperimentalSnippetHolder.getSnippetBytes(state.getCharacterEncoding()); + snippetBytes = snippet.getBytes(state.getCharacterEncoding()); } catch (UnsupportedEncodingException e) { logger.log(FINE, "UnsupportedEncodingException", e); return false; @@ -85,6 +89,4 @@ public static boolean handleWrite(InjectionState state, ServletOutputStream out, out.write(snippetBytes); return true; } - - private ServletOutputStreamInjectionHelper() {} } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 85e5a40c80a1..5882119eb9e2 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -8,7 +8,6 @@ import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.initializeInjectionStateIfNeeded; import static java.util.logging.Level.FINE; -import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import java.io.IOException; import java.io.PrintWriter; import java.lang.invoke.MethodHandle; @@ -31,20 +30,27 @@ * written, including the snippet length. */ public class SnippetInjectingResponseWrapper extends HttpServletResponseWrapper { + private static final Logger logger = Logger.getLogger(HttpServletResponseWrapper.class.getName()); + public static final String FAKE_SNIPPET_HEADER = "FAKE_SNIPPET_HEADER"; - private static final String SNIPPET = ExperimentalSnippetHolder.getSnippet(); - private static final int SNIPPET_LENGTH = SNIPPET.length(); private static final int UNSET = -1; + + // this is for Servlet 3.1 support @Nullable private static final MethodHandle setContentLengthLongHandler = getMethodHandle(); + private final String snippet; + private final int snippetLength; + private long contentLength = UNSET; private SnippetInjectingPrintWriter snippetInjectingPrintWriter = null; - public SnippetInjectingResponseWrapper(HttpServletResponse response) { + public SnippetInjectingResponseWrapper(HttpServletResponse response, String snippet) { super(response); + this.snippet = snippet; + snippetLength = snippet.length(); } @Override @@ -67,7 +73,7 @@ public void setHeader(String name, String value) { // checking content-type is just an optimization to avoid unnecessary parsing if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { try { - contentLength = Long.valueOf(value); + contentLength = Long.parseLong(value); } catch (NumberFormatException ex) { logger.log(FINE, "NumberFormatException", ex); } @@ -80,7 +86,7 @@ public void addHeader(String name, String value) { // checking content-type is just an optimization to avoid unnecessary parsing if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { try { - contentLength = Long.valueOf(value); + contentLength = Long.parseLong(value); } catch (NumberFormatException ex) { logger.log(FINE, "NumberFormatException", ex); } @@ -127,6 +133,7 @@ private static MethodHandle getMethodHandle() { } } + // this is for Servlet 3.1 support public void setContentLengthLong(long length) throws Throwable { contentLength = length; if (setContentLengthLongHandler == null) { @@ -158,14 +165,14 @@ public PrintWriter getWriter() throws IOException { } if (snippetInjectingPrintWriter == null) { snippetInjectingPrintWriter = - new SnippetInjectingPrintWriter(super.getWriter(), SNIPPET, this); + new SnippetInjectingPrintWriter(super.getWriter(), snippet, this); } return snippetInjectingPrintWriter; } public void updateContentLengthIfPreviouslySet() { if (contentLength != UNSET) { - setContentLength((int) contentLength + SNIPPET_LENGTH); + setContentLength((int) contentLength + snippetLength); } } diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java index 25d90cd27c0c..dcf787748f6f 100644 --- a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -5,8 +5,6 @@ package io.opentelemetry.javaagent.bootstrap.servlet; -import java.io.UnsupportedEncodingException; - public class ExperimentalSnippetHolder { private static String snippet = ""; @@ -19,9 +17,5 @@ public static String getSnippet() { return snippet; } - public static byte[] getSnippetBytes(String encoding) throws UnsupportedEncodingException { - return snippet.getBytes(encoding); - } - private ExperimentalSnippetHolder() {} } From 8f9813d0868c93aade961ebea39aee71b20f5783 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Wed, 15 Feb 2023 21:17:12 -0800 Subject: [PATCH 03/37] Update Servlet3OutputStreamWriteBytesAndOffsetAdvice.java --- .../Servlet3OutputStreamWriteBytesAndOffsetAdvice.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java index ca566ad924ca..5540a0a4ff91 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java @@ -5,14 +5,14 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; -import net.bytebuddy.asm.Advice; -import javax.servlet.ServletOutputStream; -import java.io.IOException; - import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; +import java.io.IOException; +import javax.servlet.ServletOutputStream; +import net.bytebuddy.asm.Advice; + public class Servlet3OutputStreamWriteBytesAndOffsetAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, suppress = Throwable.class) public static boolean methodEnter( From d2af9caa21228ea0d5efd3217153d98afb1d44b3 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Fri, 17 Feb 2023 18:36:29 -0800 Subject: [PATCH 04/37] update comments --- .../javaagent-unit-tests/build.gradle.kts | 1 - .../servlet/v3_0/snippet/InjectionTest.java | 13 +++-------- .../SnippetInjectingResponseWrapperTest.java | 22 +------------------ .../Servlet3OutputStreamWriteBytesAdvice.java | 8 ++++--- ...OutputStreamWriteBytesAndOffsetAdvice.java | 8 ++++--- .../Servlet3OutputStreamWriteIntAdvice.java | 8 ++++--- .../SnippetInjectingResponseWrapper.java | 2 +- .../test/groovy/AbstractServlet3Test.groovy | 16 +++++++------- .../src/test/groovy/JettyServlet3Test.groovy | 20 ++++++++--------- .../src/test/groovy/TestServlet3.groovy | 18 ++++++++------- .../src/test/groovy/TomcatServlet3Test.groovy | 20 ++++++++--------- .../test/java/RequestDispatcherServlet.java | 11 +++++----- .../testing/junit/http/ServerEndpoint.java | 8 +++---- 13 files changed, 68 insertions(+), 87 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts index 1046d2b6fece..c6f646595e21 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts @@ -4,6 +4,5 @@ plugins { dependencies { testImplementation("javax.servlet:javax.servlet-api:3.0.1") - testImplementation(project(":instrumentation:servlet:servlet-common:bootstrap")) testImplementation(project(":instrumentation:servlet:servlet-3.0:javaagent")) } diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java index 8b170ab9c34b..0b8f3bdc501d 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java @@ -10,7 +10,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import java.io.IOException; import java.io.StringWriter; import java.nio.charset.StandardCharsets; @@ -23,9 +22,7 @@ class InjectionTest { @Test void testInjectionForStringContainHeadTag() throws IOException { String testSnippet = "\n "; - // read the originalFile String original = readFile("staticHtmlOrigin.html"); - // read the correct answer String correct = readFile("staticHtmlAfter.html"); byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); @@ -57,11 +54,10 @@ public void write(int b) throws IOException { @Disabled void testInjectionForChinese() throws IOException { String testSnippet = "\n "; - // read the originalFile String original = readFile("staticHtmlChineseOrigin.html"); - // read the correct answer String correct = readFile("staticHtmlChineseAfter.html"); byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); + SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); when(response.isCommitted()).thenReturn(false); when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); @@ -90,11 +86,9 @@ public void write(int b) throws IOException { @Test void testInjectionForStringWithoutHeadTag() throws IOException { String testSnippet = "\n "; - ExperimentalSnippetHolder.setSnippet(testSnippet); - // read the originalFile String original = readFile("htmlWithoutHeadTag.html"); - byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); + SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); when(response.isCommitted()).thenReturn(false); when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); @@ -119,9 +113,8 @@ public void write(int b) throws IOException { } @Test - void testHalfHeadTag() throws IOException { + void testHeadTagSplitAcrossTwoWrites() throws IOException { String testSnippet = "\n "; - // read the original string String originalFirstPart = "\n" + "\n" + " Test "; SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response, testSnippet); byte[] originalBytes = original.getBytes(Charset.defaultCharset()); - // byte[] correctBytes = correct.getBytes(UTF_8); - // PrintWriter correctPw = new PrintWriter(correctWriter); for (byte originalByte : originalBytes) { responseWrapper.getWriter().write(originalByte); } @@ -124,17 +112,14 @@ void testWriteInt() throws IOException { responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); - // read file get result String result = writer.toString(); writer.close(); - // check whether new response == correct answer assertThat(result).isEqualTo(correct); } @Test void testWriteCharArray() throws IOException { - // read the originalFile String original = readFile("staticHtmlChineseOrigin.html"); String correct = readFile("staticHtmlChineseAfter.html"); HttpServletResponse response = mock(HttpServletResponse.class); @@ -152,17 +137,14 @@ void testWriteCharArray() throws IOException { responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); - // read file get result String result = writer.toString(); writer.close(); - // check whether new response == correct answer assertThat(result).isEqualTo(correct); } @Test void testWriteWithOffset() throws IOException { - // read the originalFile String original = readFile("staticHtmlChineseOrigin.html"); String correct = readFile("staticHtmlChineseAfter.html"); String extraBuffer = "this buffer should not be print out"; @@ -184,10 +166,8 @@ void testWriteWithOffset() throws IOException { responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); - // read file get result String result = writer.toString(); writer.close(); - // check whether new response == correct answer assertThat(result).isEqualTo(correct); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java index 9aa550e9fdce..b07fc3b14bd4 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java @@ -23,9 +23,11 @@ public static boolean methodEnter( if (state == null) { return true; } - // if handleWrite return true, then it means the injection has happened and the 'write' - // manipulate is done. the function would return false then, meaning skip the original write - // function + // if handleWrite returns true, then it means the original bytes + the snippet were written + // to the servletOutputStream, and so we no longer need to execute the original method + // call (see skipOn above) + // if it returns false, then it means nothing was written to the servletOutputStream and the + // original method call should be executed return !getSnippetInjectionHelper() .handleWrite(write, 0, write.length, state, servletOutputStream); } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java index 5540a0a4ff91..a9969cfe7eef 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java @@ -25,9 +25,11 @@ public static boolean methodEnter( if (state == null) { return true; } - // if handleWrite return true, then it means the injection has happened and the 'write' - // manipulate is done. the function would return false then, meaning skip the original write - // function + // if handleWrite returns true, then it means the original bytes + the snippet were written + // to the servletOutputStream, and so we no longer need to execute the original method + // call (see skipOn above) + // if it returns false, then it means nothing was written to the servletOutputStream and the + // original method call should be executed return !getSnippetInjectionHelper().handleWrite(write, off, len, state, servletOutputStream); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java index 565c82b3171f..d78696517077 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java @@ -23,9 +23,11 @@ public static boolean methodEnter( if (state == null) { return true; } - // if handleWrite return true, then it means the injection has happened and the 'write' - // manipulate is done. the function would return false then, meaning skip the original write - // function + // if handleWrite returns true, then it means the original bytes + the snippet were written + // to the servletOutputStream, and so we no longer need to execute the original method + // call (see skipOn above) + // if it returns false, then it means nothing was written to the servletOutputStream and the + // original method call should be executed return !getSnippetInjectionHelper().handleWrite(state, servletOutputStream, write); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 5882119eb9e2..494d03bb583b 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -24,7 +24,7 @@ * occurs and the content length was set previously. * *

If the Content-Length is set after snippet injection occurs (either for the first time or is - * set again for some reason),we intentionally do not add the snippet length, because the + * set again for some reason), we intentionally do not add the snippet length, because the * application server may be making that call at the end of a request when it sees the request has * not been submitted, in which case it is likely using the real length of content that has been * written, including the snippet length. diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy index 0d48faf71c59..a79fe7a8c1d8 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy @@ -17,8 +17,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM @@ -53,8 +53,8 @@ abstract class AbstractServlet3Test extends HttpServerTest extends HttpServerTest Test ") - def request = request(HTML2, "GET") + def request = request(HTML_SERVLET_OUTPUT_STREAM, "GET") def response = client.execute(request).aggregate().join() expect: - response.status().code() == HTML2.status + response.status().code() == HTML_SERVLET_OUTPUT_STREAM.status // check response content-length header String result = "\n" + "\n" + @@ -128,11 +128,11 @@ abstract class AbstractServlet3Test extends HttpServerTest Test ") - def request = request(HTML, "GET") + def request = request(HTML_PRINT_WRITER, "GET") def response = client.execute(request).aggregate().join() expect: - response.status().code() == HTML.status + response.status().code() == HTML_PRINT_WRITER.status String result = "\n" + "\n" + "\n" + diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy index ecb656bec4be..a3acb146125e 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy @@ -18,8 +18,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT @@ -172,8 +172,8 @@ class JettyServlet3TestForward extends JettyDispatchTest { super.setupServlets(context) addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Forward) @@ -211,8 +211,8 @@ class JettyServlet3TestInclude extends JettyDispatchTest { super.setupServlets(context) addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Include) @@ -238,8 +238,8 @@ class JettyServlet3TestDispatchImmediate extends JettyDispatchTest { @Override protected void setupServlets(ServletContextHandler context) { super.setupServlets(context) - addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchImmediate) @@ -267,8 +267,8 @@ class JettyServlet3TestDispatchAsync extends JettyDispatchTest { @Override protected void setupServlets(ServletContextHandler context) { super.setupServlets(context) - addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchAsync) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy index 83e1873f3c20..2002be3a88f1 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy @@ -18,8 +18,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT @@ -73,13 +73,13 @@ class TestServlet3 { break case EXCEPTION: throw new ServletException(endpoint.body) - case HTML: + case HTML_PRINT_WRITER: resp.contentType = "text/html" resp.status = endpoint.status resp.setContentLengthLong(endpoint.body.length()) resp.writer.print(endpoint.body) break - case HTML2: + case HTML_SERVLET_OUTPUT_STREAM: resp.contentType = "text/html" resp.status = endpoint.status resp.setContentLength(endpoint.body.length()) @@ -158,14 +158,14 @@ class TestServlet3 { } throw new ServletException(endpoint.body) break - case HTML: + case HTML_PRINT_WRITER: resp.contentType = "text/html" resp.status = endpoint.status resp.setContentLength(endpoint.body.length()) resp.writer.print(endpoint.body) context.complete() break - case HTML2: + case HTML_SERVLET_OUTPUT_STREAM: resp.contentType = "text/html" resp.status = endpoint.status resp.getOutputStream().print(endpoint.body) @@ -227,12 +227,14 @@ class TestServlet3 { resp.status = endpoint.status resp.writer.print(endpoint.body) throw new ServletException(endpoint.body) - case HTML: + case HTML_PRINT_WRITER: + // intentionally testing HTML_PRINT_WRITER and HTML_SERVLET_OUTPUT_STREAM with different order of setting status and contentType resp.status = endpoint.status resp.contentType = "text/html" resp.writer.print(endpoint.body) break - case HTML2: + case HTML_SERVLET_OUTPUT_STREAM: + // intentionally testing HTML_PRINT_WRITER and HTML_SERVLET_OUTPUT_STREAM with different order of setting status and contentType resp.contentType = "text/html" resp.status = endpoint.status resp.getOutputStream().print(endpoint.body) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy index ba5dd0280bb8..60f58741412b 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy @@ -30,8 +30,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM @@ -345,8 +345,8 @@ class TomcatServlet3TestForward extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Forward) } } @@ -388,8 +388,8 @@ class TomcatServlet3TestInclude extends TomcatDispatchTest { addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Include) } } @@ -417,8 +417,8 @@ class TomcatServlet3TestDispatchImmediate extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) } } @@ -442,8 +442,8 @@ class TomcatServlet3TestDispatchAsync extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java index 5d94f95e3c41..9abce0892ac1 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML; -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import java.io.IOException; @@ -41,10 +41,11 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) String target = req.getServletPath().replace("/dispatch", ""); ServletContext context = getServletContext(); RequestDispatcher dispatcher = context.getRequestDispatcher(target); - // for HTML test case, set content type before calling include as - // "include" reject modify on resp later + // for HTML test case, set the content type before calling include because + // setContentType will be rejected if called inside of include // check https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html - if (ServerEndpoint.forPath(target) == HTML || ServerEndpoint.forPath(target) == HTML2) { + if (ServerEndpoint.forPath(target) == HTML_PRINT_WRITER + || ServerEndpoint.forPath(target) == HTML_SERVLET_OUTPUT_STREAM) { resp.setContentType("text/html"); } dispatcher.include(req, resp); diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java index 1a5f340554dd..53895d00f1f8 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java @@ -32,8 +32,8 @@ public enum ServerEndpoint { LOGIN("login", 302, null), AUTH_ERROR("basicsecured/endpoint", 401, null), INDEXED_CHILD("child", 200, ""), - HTML( - "html", + HTML_PRINT_WRITER( + "HTML_PRINT_WRITER", 200, "\n" + "\n" @@ -45,8 +45,8 @@ public enum ServerEndpoint { + "

test works

\n" + "\n" + ""), - HTML2( - "html2", + HTML_SERVLET_OUTPUT_STREAM( + "HTML_SERVLET_OUTPUT_STREAM", 200, "\n" + "\n" From 95a671407d38841968f504b152e161336cadf5fe Mon Sep 17 00:00:00 2001 From: siyuniu-ms <123212536+siyuniu-ms@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:40:32 -0800 Subject: [PATCH 05/37] Update instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java Co-authored-by: Trask Stalnaker --- .../servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 494d03bb583b..c6fd569e2844 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -55,7 +55,7 @@ public SnippetInjectingResponseWrapper(HttpServletResponse response, String snip @Override public boolean containsHeader(String name) { - // override this function in order to make sure the response is wrapped + // this function is overridden in order to make sure the response is wrapped // but not wrapped twice // we didn't use the traditional method req.setAttribute // because async would set the original request attribute but didn't pass down the wrapped From 6a5329ad84b7aabadeee45aa62b01b6d1288f6fa Mon Sep 17 00:00:00 2001 From: siyuniu-ms <123212536+siyuniu-ms@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:40:46 -0800 Subject: [PATCH 06/37] Update instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java Co-authored-by: Trask Stalnaker --- .../servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index c6fd569e2844..6aad8c0287a6 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -57,7 +57,7 @@ public SnippetInjectingResponseWrapper(HttpServletResponse response, String snip public boolean containsHeader(String name) { // this function is overridden in order to make sure the response is wrapped // but not wrapped twice - // we didn't use the traditional method req.setAttribute + // we don't use req.setAttribute // because async would set the original request attribute but didn't pass down the wrapped // response // then the response would never be wrapped again From 85c43e19a6fcfe6e10deca66b328acdcdcb6036c Mon Sep 17 00:00:00 2001 From: siyuniu-ms <123212536+siyuniu-ms@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:41:00 -0800 Subject: [PATCH 07/37] Update instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java Co-authored-by: Trask Stalnaker --- .../servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 6aad8c0287a6..8c5b95f4fd1c 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -178,7 +178,7 @@ public void updateContentLengthIfPreviouslySet() { public boolean isNotSafeToInject() { // if content-length was set and response was already committed (headers sent to the client), - // then not safe to inject because the content-length header cannot be updated to account for + // then it is not safe to inject because the content-length header cannot be updated to account for // the snippet length return isCommitted() && (contentLength != UNSET); } From 6d422bfe796cf8b68e67c1a3b4a36b91deae6340 Mon Sep 17 00:00:00 2001 From: siyuniu-ms <123212536+siyuniu-ms@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:41:18 -0800 Subject: [PATCH 08/37] Update instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java Co-authored-by: Trask Stalnaker --- .../v3_0/snippet/SnippetInjectingResponseWrapper.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 8c5b95f4fd1c..06bcad41a7c6 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -58,9 +58,9 @@ public boolean containsHeader(String name) { // this function is overridden in order to make sure the response is wrapped // but not wrapped twice // we don't use req.setAttribute - // because async would set the original request attribute but didn't pass down the wrapped - // response - // then the response would never be wrapped again + // because async requests pass down their attributes, but don't pass down our wrapped response + // and so we would see the presence of the attribute and think the response was already wrapped + // when it really is not // see also https://docs.oracle.com/javaee/7/api/javax/servlet/AsyncContext.html if (name.equals(FAKE_SNIPPET_HEADER)) { return true; From f597144bc10086866ad6bfa85ae3e0c58af85845 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Tue, 21 Feb 2023 11:11:20 -0800 Subject: [PATCH 09/37] Update SnippetInjectingResponseWrapper.java --- .../servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 06bcad41a7c6..860c81c0141d 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -178,8 +178,8 @@ public void updateContentLengthIfPreviouslySet() { public boolean isNotSafeToInject() { // if content-length was set and response was already committed (headers sent to the client), - // then it is not safe to inject because the content-length header cannot be updated to account for - // the snippet length + // then it is not safe to inject because the content-length header cannot be updated to account + // for the snippet length return isCommitted() && (contentLength != UNSET); } } From 9fc8899b1a4b6cafe7dd86ad660dcdd233828189 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Tue, 21 Feb 2023 16:17:06 -0800 Subject: [PATCH 10/37] Update Servlet3Singletons.java --- .../instrumentation/servlet/v3_0/Servlet3Singletons.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java index 8d06e31addc0..6c59213a4619 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java @@ -60,7 +60,7 @@ public static MappingResolver getMappingResolver(Object servletOrFilter) { return null; } - static OutputStreamSnippetInjectionHelper getSnippetInjectionHelper() { + public static OutputStreamSnippetInjectionHelper getSnippetInjectionHelper() { return SNIPPET_INJECTION_HELPER; } From 1919eb015190773b6e671d478652c03e6ea6f61d Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Tue, 14 Feb 2023 15:11:41 -0800 Subject: [PATCH 11/37] JavaScript snippet injection --- .../javaagent-unit-tests/build.gradle.kts | 9 + .../servlet/v3_0/snippet/InjectionTest.java | 184 +++++++++++++++++ .../SnippetInjectingResponseWrapperTest.java | 189 ++++++++++++++++++ .../servlet/v3_0/snippet/TestUtil.java | 30 +++ .../test/resources/htmlWithoutHeadTag.html | 7 + .../src/test/resources/staticHtmlAfter.html | 11 + .../resources/staticHtmlChineseAfter.html | 11 + .../resources/staticHtmlChineseOrigin.html | 10 + .../src/test/resources/staticHtmlOrigin.html | 10 + .../servlet-3.0/javaagent/build.gradle.kts | 2 + .../servlet/v3_0/Servlet3Advice.java | 7 + .../v3_0/Servlet3InstrumentationModule.java | 6 + .../Servlet3OutputStreamWriteBytesAdvice.java | 32 +++ ...OutputStreamWriteBytesAndOffsetAdvice.java | 33 +++ .../Servlet3OutputStreamWriteIntAdvice.java | 31 +++ .../servlet/v3_0/snippet/Injection.java | 34 ++++ .../servlet/v3_0/snippet/InjectionState.java | 70 +++++++ .../ServletOutputStreamInjectionHelper.java | 90 +++++++++ .../snippet/SnippetInjectingPrintWriter.java | 52 +++++ .../SnippetInjectingResponseWrapper.java | 178 +++++++++++++++++ .../test/groovy/AbstractServlet3Test.groovy | 53 +++++ .../src/test/groovy/JettyServlet3Test.groovy | 12 +- .../src/test/groovy/TestServlet3.groovy | 40 ++++ .../src/test/groovy/TomcatServlet3Test.groovy | 10 + .../test/java/RequestDispatcherServlet.java | 10 + .../servlet/ExperimentalSnippetHolder.java | 27 +++ .../ServletOutputStreamInstrumentation.java | 65 ++++++ settings.gradle.kts | 1 + .../testing/junit/http/ServerEndpoint.java | 28 ++- 29 files changed, 1239 insertions(+), 3 deletions(-) create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/htmlWithoutHeadTag.html create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlAfter.html create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseAfter.html create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseOrigin.html create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlOrigin.html create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Injection.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java create mode 100644 instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java create mode 100644 instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts new file mode 100644 index 000000000000..1046d2b6fece --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { + testImplementation("javax.servlet:javax.servlet-api:3.0.1") + testImplementation(project(":instrumentation:servlet:servlet-common:bootstrap")) + testImplementation(project(":instrumentation:servlet:servlet-3.0:javaagent")) +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java new file mode 100644 index 000000000000..2472deee295d --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java @@ -0,0 +1,184 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFile; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import javax.servlet.ServletOutputStream; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class InjectionTest { + + @Test + void testInjectionForStringContainHeadTag() throws IOException { + String testSnippet = "\n "; + ExperimentalSnippetHolder.setSnippet(testSnippet); + // read the originalFile + String original = readFile("staticHtmlOrigin.html"); + // read the correct answer + String correct = readFile("staticHtmlAfter.html"); + byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); + SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); + when(response.isCommitted()).thenReturn(false); + when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); + InjectionState obj = new InjectionState(response); + + StringWriter writer = new StringWriter(); + + ServletOutputStream sp = + new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + writer.write(b); + } + }; + boolean injected = + ServletOutputStreamInjectionHelper.handleWrite( + originalBytes, 0, originalBytes.length, obj, sp); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); + assertThat(injected).isEqualTo(true); + writer.flush(); + + String result = writer.toString(); + writer.close(); + assertThat(result).isEqualTo(correct); + } + + @Test + @Disabled + void testInjectionForChinese() throws IOException { + String testSnippet = "\n "; + ExperimentalSnippetHolder.setSnippet(testSnippet); + // read the originalFile + String original = readFile("staticHtmlChineseOrigin.html"); + // read the correct answer + String correct = readFile("staticHtmlChineseAfter.html"); + byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); + SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); + when(response.isCommitted()).thenReturn(false); + when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); + InjectionState obj = new InjectionState(response); + + StringWriter writer = new StringWriter(); + + ServletOutputStream sp = + new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + writer.write(b); + } + }; + boolean injected = + ServletOutputStreamInjectionHelper.handleWrite( + originalBytes, 0, originalBytes.length, obj, sp); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); + assertThat(injected).isEqualTo(true); + writer.flush(); + + String result = writer.toString(); + writer.close(); + assertThat(result).isEqualTo(correct); + } + + @Test + void testInjectionForStringWithoutHeadTag() throws IOException { + String testSnippet = "\n "; + ExperimentalSnippetHolder.setSnippet(testSnippet); + // read the originalFile + String original = readFile("htmlWithoutHeadTag.html"); + + byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); + SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); + when(response.isCommitted()).thenReturn(false); + when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); + InjectionState obj = new InjectionState(response); + StringWriter writer = new StringWriter(); + + ServletOutputStream sp = + new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + writer.write(b); + } + }; + boolean injected = + ServletOutputStreamInjectionHelper.handleWrite( + originalBytes, 0, originalBytes.length, obj, sp); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(0); + assertThat(injected).isEqualTo(false); + writer.flush(); + String result = writer.toString(); + writer.close(); + assertThat(result).isEqualTo(""); + } + + @Test + void testHalfHeadTag() throws IOException { + String testSnippet = "\n "; + ExperimentalSnippetHolder.setSnippet(testSnippet); + // read the original string + String originalFirstPart = "\n" + "\n" + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + byte[] originalSecondPartBytes = originalSecondPart.getBytes(StandardCharsets.UTF_8); + injected = + ServletOutputStreamInjectionHelper.handleWrite( + originalSecondPartBytes, 0, originalSecondPartBytes.length, obj, sp); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); + assertThat(injected).isEqualTo(true); + String correctSecondPart = + "ad>\n" + + " \n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + writer.flush(); + result = writer.toString(); + assertThat(result).isEqualTo(correctSecondPart); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java new file mode 100644 index 000000000000..4b692622bf10 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java @@ -0,0 +1,189 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFile; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.Charset; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class SnippetInjectingResponseWrapperTest { + + @Test + void testInjectToTextHtml() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlOrigin.html"); + String correct = readFile("staticHtmlAfter.html"); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("text/html"); + when(response.getStatus()).thenReturn(200); + when(response.containsHeader("content-type")).thenReturn(true); + StringWriter writer = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + ExperimentalSnippetHolder.setSnippet("\n "); + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + responseWrapper.getWriter().write(original); + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(correct); + } + + @Test + @Disabled + void testInjectToChineseTextHtml() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlChineseOrigin.html"); + String correct = readFile("staticHtmlChineseAfter.html"); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("text/html"); + + StringWriter writer = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + ExperimentalSnippetHolder.setSnippet("\n "); + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + responseWrapper.getWriter().write(original); + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(correct); + } + + @Test + void shouldNotInjectToTextHtml() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlOrigin.html"); + + StringWriter writer = new StringWriter(); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("not/text"); + when(response.getStatus()).thenReturn(200); + when(response.containsHeader("content-type")).thenReturn(true); + + when(response.getWriter()).thenReturn(new PrintWriter(writer, true)); + ExperimentalSnippetHolder.setSnippet("\n "); + + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + responseWrapper.getWriter().write(original); + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(original); + } + + @Test + void testWriteInt() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlOrigin.html"); + String correct = readFile("staticHtmlAfter.html"); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("text/html"); + + StringWriter writer = new StringWriter(); + // StringWriter correctWriter = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + ExperimentalSnippetHolder.setSnippet("\n "); + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + byte[] originalBytes = original.getBytes(Charset.defaultCharset().name()); + // byte[] correctBytes = correct.getBytes(UTF_8); + // PrintWriter correctPw = new PrintWriter(correctWriter); + for (int i = 0; i < originalBytes.length; i++) { + responseWrapper.getWriter().write(originalBytes[i]); + } + + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(correct); + } + + @Test + void testWriteCharArray() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlChineseOrigin.html"); + String correct = readFile("staticHtmlChineseAfter.html"); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("text/html"); + when(response.getStatus()).thenReturn(200); + when(response.containsHeader("content-type")).thenReturn(true); + + StringWriter writer = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + ExperimentalSnippetHolder.setSnippet("\n "); + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + char[] originalChars = original.toCharArray(); + responseWrapper.getWriter().write(originalChars, 0, originalChars.length); + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(correct); + } + + @Test + void testWriteWithOffset() throws IOException { + + // read the originalFile + String original = readFile("staticHtmlChineseOrigin.html"); + String correct = readFile("staticHtmlChineseAfter.html"); + String extraBuffer = "this buffer should not be print out"; + original = extraBuffer + original; + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn("text/html"); + when(response.getStatus()).thenReturn(200); + when(response.containsHeader("content-type")).thenReturn(true); + + StringWriter writer = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + ExperimentalSnippetHolder.setSnippet("\n "); + SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + + responseWrapper + .getWriter() + .write(original, extraBuffer.length(), original.length() - extraBuffer.length()); + responseWrapper.getWriter().flush(); + responseWrapper.getWriter().close(); + + // read file get result + String result = writer.toString(); + writer.close(); + // check whether new response == correct answer + assertThat(result).isEqualTo(correct); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java new file mode 100644 index 000000000000..fb5fec9fcb6e --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class TestUtil { + + public static String readFile(String resourceName) throws IOException { + InputStream in = + SnippetInjectingResponseWrapperTest.class + .getClassLoader() + .getResourceAsStream(resourceName); + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = in.read(buffer)) != -1) { + result.write(buffer, 0, length); + } + return result.toString(StandardCharsets.UTF_8.name()); + } + + private TestUtil() {} +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/htmlWithoutHeadTag.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/htmlWithoutHeadTag.html new file mode 100644 index 000000000000..e0a5f5d9d61c --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/htmlWithoutHeadTag.html @@ -0,0 +1,7 @@ + + + + +

without head tag

+ + \ No newline at end of file diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlAfter.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlAfter.html new file mode 100644 index 000000000000..ba5ff4ad3ac1 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlAfter.html @@ -0,0 +1,11 @@ + + + + + + Title + + + + + \ No newline at end of file diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseAfter.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseAfter.html new file mode 100644 index 000000000000..81166f54dd69 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseAfter.html @@ -0,0 +1,11 @@ + + + + + + Title + + +

欢迎光临

+ + \ No newline at end of file diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseOrigin.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseOrigin.html new file mode 100644 index 000000000000..5594942811a7 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseOrigin.html @@ -0,0 +1,10 @@ + + + + + Title + + +

欢迎光临

+ + \ No newline at end of file diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlOrigin.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlOrigin.html new file mode 100644 index 000000000000..de155ea51d7d --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlOrigin.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts b/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts index 689d229e20fd..e9d9f0a6d093 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts @@ -23,9 +23,11 @@ dependencies { compileOnly("javax.servlet:javax.servlet-api:3.0.1") testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent")) + testImplementation(project(":instrumentation:servlet:servlet-common:bootstrap")) testLibrary("org.eclipse.jetty:jetty-server:8.0.0.v20110901") testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901") + // TODO (trask?) test against tomcat 7 (servlet 3.0) testLibrary("org.apache.tomcat.embed:tomcat-embed-core:8.0.41") testLibrary("org.apache.tomcat.embed:tomcat-embed-jasper:8.0.41") diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java index 9da2aa307eda..d625789f5595 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java @@ -6,14 +6,17 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.SnippetInjectingResponseWrapper.FAKE_SNIPPET_HEADER; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.bootstrap.CallDepth; import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge; +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver; import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.SnippetInjectingResponseWrapper; import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -40,6 +43,10 @@ public static void onEnter( } HttpServletRequest httpServletRequest = (HttpServletRequest) request; + if (!ExperimentalSnippetHolder.getSnippet().isEmpty() + && !((HttpServletResponse) response).containsHeader(FAKE_SNIPPET_HEADER)) { + response = new SnippetInjectingResponseWrapper((HttpServletResponse) response); + } callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey()); callDepth.getAndIncrement(); diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InstrumentationModule.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InstrumentationModule.java index 299efcdd1f5c..94935ade7b7a 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InstrumentationModule.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InstrumentationModule.java @@ -16,6 +16,7 @@ import io.opentelemetry.javaagent.instrumentation.servlet.common.async.AsyncStartInstrumentation; import io.opentelemetry.javaagent.instrumentation.servlet.common.response.HttpServletResponseInstrumentation; import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletAndFilterInstrumentation; +import io.opentelemetry.javaagent.instrumentation.servlet.common.service.ServletOutputStreamInstrumentation; import java.util.List; import net.bytebuddy.matcher.ElementMatcher; @@ -44,6 +45,11 @@ BASE_PACKAGE, adviceClassName(".Servlet3AsyncContextStartAdvice")), adviceClassName(".Servlet3Advice"), adviceClassName(".Servlet3InitAdvice"), adviceClassName(".Servlet3FilterInitAdvice")), + new ServletOutputStreamInstrumentation( + BASE_PACKAGE, + adviceClassName(".Servlet3OutputStreamWriteBytesAndOffsetAdvice"), + adviceClassName(".Servlet3OutputStreamWriteBytesAdvice"), + adviceClassName(".Servlet3OutputStreamWriteIntAdvice")), new HttpServletResponseInstrumentation( BASE_PACKAGE, adviceClassName(".Servlet3ResponseSendAdvice"))); } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java new file mode 100644 index 000000000000..c2258978b0cf --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper; +import java.io.IOException; +import javax.servlet.ServletOutputStream; +import net.bytebuddy.asm.Advice; + +public class Servlet3OutputStreamWriteBytesAdvice { + + @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, suppress = Throwable.class) + public static boolean methodEnter( + @Advice.This ServletOutputStream servletOutputStream, @Advice.Argument(0) byte[] write) + throws IOException { + InjectionState state = getInjectionState(servletOutputStream); + if (state == null) { + return true; + } + // if handleWrite return true, then it means the injection has happened and the 'write' + // manipulate is done. the function would return false then, meaning skip the original write + // function + return !ServletOutputStreamInjectionHelper.handleWrite( + write, 0, write.length, state, servletOutputStream); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java new file mode 100644 index 000000000000..69effc3bc789 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper.handleWrite; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; +import java.io.IOException; +import javax.servlet.ServletOutputStream; +import net.bytebuddy.asm.Advice; + +public class Servlet3OutputStreamWriteBytesAndOffsetAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, suppress = Throwable.class) + public static boolean methodEnter( + @Advice.This ServletOutputStream servletOutputStream, + @Advice.Argument(value = 0) byte[] write, + @Advice.Argument(value = 1) int off, + @Advice.Argument(value = 2) int len) + throws IOException { + InjectionState state = getInjectionState(servletOutputStream); + if (state == null) { + return true; + } + // if handleWrite return true, then it means the injection has happened and the 'write' + // manipulate is done. the function would return false then, meaning skip the original write + // function + return !handleWrite(write, off, len, state, servletOutputStream); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java new file mode 100644 index 000000000000..1d59a2ad2a80 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper.handleWrite; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; +import java.io.IOException; +import javax.servlet.ServletOutputStream; +import net.bytebuddy.asm.Advice; + +public class Servlet3OutputStreamWriteIntAdvice { + + @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, suppress = Throwable.class) + public static boolean methodEnter( + @Advice.This ServletOutputStream servletOutputStream, @Advice.Argument(0) int write) + throws IOException { + InjectionState state = getInjectionState(servletOutputStream); + if (state == null) { + return true; + } + // if handleWrite return true, then it means the injection has happened and the 'write' + // manipulate is done. the function would return false then, meaning skip the original write + // function + return !handleWrite(state, servletOutputStream, write); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Injection.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Injection.java new file mode 100644 index 000000000000..2ee7578f2600 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Injection.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import io.opentelemetry.instrumentation.api.util.VirtualField; +import javax.servlet.ServletOutputStream; + +public class Injection { + + private static final VirtualField virtualField = + VirtualField.find(ServletOutputStream.class, InjectionState.class); + + public static void initializeInjectionStateIfNeeded( + ServletOutputStream servletOutputStream, SnippetInjectingResponseWrapper wrapper) { + InjectionState state = virtualField.get(servletOutputStream); + if (!wrapper.isContentTypeTextHtml()) { + virtualField.set(servletOutputStream, null); + return; + } + if (state == null || state.getWrapper() != wrapper) { + state = new InjectionState(wrapper); + virtualField.set(servletOutputStream, state); + } + } + + public static InjectionState getInjectionState(ServletOutputStream servletOutputStream) { + return virtualField.get(servletOutputStream); + } + + private Injection() {} +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java new file mode 100644 index 000000000000..d5dbe07830ab --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +public class InjectionState { + private static final int HEAD_TAG_WRITTEN_FAKE_VALUE = -1; + private static final int HEAD_TAG_LENGTH = "".length(); + private final SnippetInjectingResponseWrapper wrapper; + private int headTagBytesSeen = 0; + + public InjectionState(SnippetInjectingResponseWrapper wrapper) { + this.wrapper = wrapper; + } + + public int getHeadTagBytesSeen() { + return headTagBytesSeen; + } + + public String getCharacterEncoding() { + return wrapper.getCharacterEncoding(); + } + + public void setHeadTagWritten() { + headTagBytesSeen = HEAD_TAG_WRITTEN_FAKE_VALUE; + } + + public boolean isHeadTagWritten() { + return headTagBytesSeen == HEAD_TAG_WRITTEN_FAKE_VALUE; + } + + /** + * Returns true when the byte is the last character of "" and now is the right time to + * inject. Otherwise, returns false. + */ + public boolean processByte(int b) { + if (isHeadTagWritten()) { + return false; + } + if (inHeadTag(b)) { + headTagBytesSeen++; + } else { + headTagBytesSeen = 0; + } + return headTagBytesSeen == HEAD_TAG_LENGTH; + } + + private boolean inHeadTag(int b) { + if (headTagBytesSeen == 0 && b == '<') { + return true; + } else if (headTagBytesSeen == 1 && b == 'h') { + return true; + } else if (headTagBytesSeen == 2 && b == 'e') { + return true; + } else if (headTagBytesSeen == 3 && b == 'a') { + return true; + } else if (headTagBytesSeen == 4 && b == 'd') { + return true; + } else if (headTagBytesSeen == 5 && b == '>') { + return true; + } + return false; + } + + public SnippetInjectingResponseWrapper getWrapper() { + return wrapper; + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java new file mode 100644 index 000000000000..235c5fdc2188 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java @@ -0,0 +1,90 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import static java.util.logging.Level.FINE; + +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.logging.Logger; +import javax.servlet.ServletOutputStream; + +public class ServletOutputStreamInjectionHelper { + + private static final Logger logger = + Logger.getLogger(ServletOutputStreamInjectionHelper.class.getName()); + + /** + * return true means this method performed the injection, return false means it didn't inject + * anything Servlet3OutputStreamWriteAdvice would skip the write method when the return value is + * true, and would write the original bytes when the return value is false. + */ + public static boolean handleWrite( + byte[] original, int off, int length, InjectionState state, ServletOutputStream out) + throws IOException { + if (state.isHeadTagWritten()) { + return false; + } + int i; + boolean endOfHeadTagFound = false; + for (i = off; i < length && i - off < length; i++) { + if (state.processByte(original[i])) { + endOfHeadTagFound = true; + break; + } + } + if (!endOfHeadTagFound) { + return false; + } + state.setHeadTagWritten(); // set before write to avoid recursive loop + if (state.getWrapper().isNotSafeToInject()) { + return false; + } + byte[] snippetBytes; + try { + snippetBytes = ExperimentalSnippetHolder.getSnippetBytes(state.getCharacterEncoding()); + } catch (UnsupportedEncodingException e) { + logger.log(FINE, "UnsupportedEncodingException", e); + return false; + } + // updating Content-Length before any further writing in case that writing triggers a flush + state.getWrapper().updateContentLengthIfPreviouslySet(); + out.write(original, off, i + 1); + out.write(snippetBytes); + out.write(original, i + 1, length - i - 1); + return true; + } + + public static boolean handleWrite(InjectionState state, ServletOutputStream out, int b) + throws IOException { + if (state.isHeadTagWritten()) { + return false; + } + if (!state.processByte(b)) { + return false; + } + state.setHeadTagWritten(); // set before write to avoid recursive loop + + if (state.getWrapper().isNotSafeToInject()) { + return false; + } + byte[] snippetBytes; + try { + snippetBytes = ExperimentalSnippetHolder.getSnippetBytes(state.getCharacterEncoding()); + } catch (UnsupportedEncodingException e) { + logger.log(FINE, "UnsupportedEncodingException", e); + return false; + } + state.getWrapper().updateContentLengthIfPreviouslySet(); + out.write(b); + + out.write(snippetBytes); + return true; + } + + private ServletOutputStreamInjectionHelper() {} +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java new file mode 100644 index 000000000000..88e3a02a21b0 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import java.io.PrintWriter; + +public class SnippetInjectingPrintWriter extends PrintWriter { + private final String snippet; + private final InjectionState state; + + public SnippetInjectingPrintWriter( + PrintWriter writer, String snippet, SnippetInjectingResponseWrapper wrapper) { + super(writer); + state = new InjectionState(wrapper); + this.snippet = snippet; + } + + @Override + public void write(String s, int off, int len) { + for (int i = off; i < s.length() && i - off < len; i++) { + write(s.charAt(i)); + } + } + + @Override + public void write(int b) { + super.write(b); + if (state.isHeadTagWritten()) { + return; + } + boolean endOfHeadTagFound = state.processByte(b); + if (!endOfHeadTagFound) { + return; + } + state.setHeadTagWritten(); // set before write to avoid recursive loop + if (state.getWrapper().isNotSafeToInject()) { + return; + } + state.getWrapper().updateContentLengthIfPreviouslySet(); + super.write(snippet); + } + + @Override + public void write(char[] buf, int off, int len) { + for (int i = off; i < buf.length && i - off < len; i++) { + write(buf[i]); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java new file mode 100644 index 000000000000..85e5a40c80a1 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -0,0 +1,178 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.initializeInjectionStateIfNeeded; +import static java.util.logging.Level.FINE; + +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.logging.Logger; +import javax.annotation.Nullable; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +/** + * Notes on Content-Length: the snippet length is only added to the content length when injection + * occurs and the content length was set previously. + * + *

If the Content-Length is set after snippet injection occurs (either for the first time or is + * set again for some reason),we intentionally do not add the snippet length, because the + * application server may be making that call at the end of a request when it sees the request has + * not been submitted, in which case it is likely using the real length of content that has been + * written, including the snippet length. + */ +public class SnippetInjectingResponseWrapper extends HttpServletResponseWrapper { + private static final Logger logger = Logger.getLogger(HttpServletResponseWrapper.class.getName()); + public static final String FAKE_SNIPPET_HEADER = "FAKE_SNIPPET_HEADER"; + private static final String SNIPPET = ExperimentalSnippetHolder.getSnippet(); + private static final int SNIPPET_LENGTH = SNIPPET.length(); + + private static final int UNSET = -1; + @Nullable private static final MethodHandle setContentLengthLongHandler = getMethodHandle(); + + private long contentLength = UNSET; + + private SnippetInjectingPrintWriter snippetInjectingPrintWriter = null; + + public SnippetInjectingResponseWrapper(HttpServletResponse response) { + super(response); + } + + @Override + public boolean containsHeader(String name) { + // override this function in order to make sure the response is wrapped + // but not wrapped twice + // we didn't use the traditional method req.setAttribute + // because async would set the original request attribute but didn't pass down the wrapped + // response + // then the response would never be wrapped again + // see also https://docs.oracle.com/javaee/7/api/javax/servlet/AsyncContext.html + if (name.equals(FAKE_SNIPPET_HEADER)) { + return true; + } + return super.containsHeader(name); + } + + @Override + public void setHeader(String name, String value) { + // checking content-type is just an optimization to avoid unnecessary parsing + if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { + try { + contentLength = Long.valueOf(value); + } catch (NumberFormatException ex) { + logger.log(FINE, "NumberFormatException", ex); + } + } + super.setHeader(name, value); + } + + @Override + public void addHeader(String name, String value) { + // checking content-type is just an optimization to avoid unnecessary parsing + if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { + try { + contentLength = Long.valueOf(value); + } catch (NumberFormatException ex) { + logger.log(FINE, "NumberFormatException", ex); + } + } + super.addHeader(name, value); + } + + @Override + public void setIntHeader(String name, int value) { + // checking content-type is just an optimization to avoid unnecessary parsing + if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { + contentLength = value; + } + super.setIntHeader(name, value); + } + + @Override + public void addIntHeader(String name, int value) { + // checking content-type is just an optimization to avoid unnecessary parsing + if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { + contentLength = value; + } + super.addIntHeader(name, value); + } + + @Override + public void setContentLength(int len) { + contentLength = len; + super.setContentLength(len); + } + + @Nullable + private static MethodHandle getMethodHandle() { + try { + return MethodHandles.lookup() + .findSpecial( + HttpServletResponseWrapper.class, + "setContentLengthLong", + MethodType.methodType(void.class), + SnippetInjectingResponseWrapper.class); + } catch (NoSuchMethodException | IllegalAccessException e) { + logger.log(FINE, "SnippetInjectingResponseWrapper setContentLengthLong", e); + return null; + } + } + + public void setContentLengthLong(long length) throws Throwable { + contentLength = length; + if (setContentLengthLongHandler == null) { + super.setContentLength((int) length); + } else { + setContentLengthLongHandler.invokeWithArguments(this, length); + } + } + + public boolean isContentTypeTextHtml() { + String contentType = super.getContentType(); + if (contentType == null) { + contentType = super.getHeader("content-type"); + } + return contentType != null && contentType.startsWith("text/html"); + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + ServletOutputStream output = super.getOutputStream(); + initializeInjectionStateIfNeeded(output, this); + return output; + } + + @Override + public PrintWriter getWriter() throws IOException { + if (!isContentTypeTextHtml()) { + return super.getWriter(); + } + if (snippetInjectingPrintWriter == null) { + snippetInjectingPrintWriter = + new SnippetInjectingPrintWriter(super.getWriter(), SNIPPET, this); + } + return snippetInjectingPrintWriter; + } + + public void updateContentLengthIfPreviouslySet() { + if (contentLength != UNSET) { + setContentLength((int) contentLength + SNIPPET_LENGTH); + } + } + + public boolean isNotSafeToInject() { + // if content-length was set and response was already committed (headers sent to the client), + // then not safe to inject because the content-length header cannot be updated to account for + // the snippet length + return isCommitted() && (contentLength != UNSET); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy index 6dbdb60eb1b7..0d48faf71c59 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy @@ -7,6 +7,7 @@ import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest import javax.servlet.Servlet @@ -16,6 +17,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM @@ -50,6 +53,8 @@ abstract class AbstractServlet3Test extends HttpServerTest extends HttpServerTest Test ") + def request = request(HTML2, "GET") + def response = client.execute(request).aggregate().join() + + expect: + response.status().code() == HTML2.status + // check response content-length header + String result = "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + "" + response.contentUtf8() == result + response.headers().contentLength() == result.length() + } + + def "snippet injection with PrintWriter"() { + setup: + ExperimentalSnippetHolder.setSnippet("\n ") + def request = request(HTML, "GET") + def response = client.execute(request).aggregate().join() + + expect: + response.status().code() == HTML.status + String result = "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + "" + + response.contentUtf8() == result + response.headers().contentLength() == result.length() + } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy index 8d1549ecf086..ecb656bec4be 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy @@ -18,6 +18,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT @@ -170,6 +172,8 @@ class JettyServlet3TestForward extends JettyDispatchTest { super.setupServlets(context) addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Forward) @@ -207,6 +211,8 @@ class JettyServlet3TestInclude extends JettyDispatchTest { super.setupServlets(context) addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Include) @@ -232,7 +238,8 @@ class JettyServlet3TestDispatchImmediate extends JettyDispatchTest { @Override protected void setupServlets(ServletContextHandler context) { super.setupServlets(context) - + addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchImmediate) @@ -260,7 +267,8 @@ class JettyServlet3TestDispatchAsync extends JettyDispatchTest { @Override protected void setupServlets(ServletContextHandler context) { super.setupServlets(context) - + addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchAsync) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy index 2014402497da..83e1873f3c20 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy @@ -5,6 +5,7 @@ import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint + import javax.servlet.RequestDispatcher import javax.servlet.ServletException import javax.servlet.annotation.WebServlet @@ -17,6 +18,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT @@ -70,6 +73,19 @@ class TestServlet3 { break case EXCEPTION: throw new ServletException(endpoint.body) + case HTML: + resp.contentType = "text/html" + resp.status = endpoint.status + resp.setContentLengthLong(endpoint.body.length()) + resp.writer.print(endpoint.body) + break + case HTML2: + resp.contentType = "text/html" + resp.status = endpoint.status + resp.setContentLength(endpoint.body.length()) + byte[] body = endpoint.body.getBytes() + resp.getOutputStream().write(body, 0, body.length) + break } } } @@ -141,6 +157,20 @@ class TestServlet3 { writer.close() } throw new ServletException(endpoint.body) + break + case HTML: + resp.contentType = "text/html" + resp.status = endpoint.status + resp.setContentLength(endpoint.body.length()) + resp.writer.print(endpoint.body) + context.complete() + break + case HTML2: + resp.contentType = "text/html" + resp.status = endpoint.status + resp.getOutputStream().print(endpoint.body) + context.complete() + break } } } finally { @@ -197,6 +227,16 @@ class TestServlet3 { resp.status = endpoint.status resp.writer.print(endpoint.body) throw new ServletException(endpoint.body) + case HTML: + resp.status = endpoint.status + resp.contentType = "text/html" + resp.writer.print(endpoint.body) + break + case HTML2: + resp.contentType = "text/html" + resp.status = endpoint.status + resp.getOutputStream().print(endpoint.body) + break } } } finally { diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy index 93cfeacd8e6b..ba5dd0280bb8 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy @@ -30,6 +30,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM @@ -343,6 +345,8 @@ class TomcatServlet3TestForward extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Forward) } } @@ -384,6 +388,8 @@ class TomcatServlet3TestInclude extends TomcatDispatchTest { addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Include) } } @@ -411,6 +417,8 @@ class TomcatServlet3TestDispatchImmediate extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) } } @@ -434,6 +442,8 @@ class TomcatServlet3TestDispatchAsync extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java index 25e2e68d379a..5d94f95e3c41 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java @@ -3,6 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2; + +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; @@ -37,6 +41,12 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) String target = req.getServletPath().replace("/dispatch", ""); ServletContext context = getServletContext(); RequestDispatcher dispatcher = context.getRequestDispatcher(target); + // for HTML test case, set content type before calling include as + // "include" reject modify on resp later + // check https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html + if (ServerEndpoint.forPath(target) == HTML || ServerEndpoint.forPath(target) == HTML2) { + resp.setContentType("text/html"); + } dispatcher.include(req, resp); } } diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java new file mode 100644 index 000000000000..25d90cd27c0c --- /dev/null +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.bootstrap.servlet; + +import java.io.UnsupportedEncodingException; + +public class ExperimentalSnippetHolder { + + private static String snippet = ""; + + public static void setSnippet(String snippet) { + ExperimentalSnippetHolder.snippet = snippet; + } + + public static String getSnippet() { + return snippet; + } + + public static byte[] getSnippetBytes(String encoding) throws UnsupportedEncodingException { + return snippet.getBytes(encoding); + } + + private ExperimentalSnippetHolder() {} +} diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java new file mode 100644 index 000000000000..df80666d71d1 --- /dev/null +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.common.service; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class ServletOutputStreamInstrumentation implements TypeInstrumentation { + private final String basePackageName; + private final String writeBytesAndOffsetClassName; + private final String writeBytesClassName; + private final String writeIntAdviceClassName; + + public ServletOutputStreamInstrumentation( + String basePackageName, + String writeBytesAndOffsetClassName, + String writeBytesClassName, + String writeIntAdviceClassName) { + this.basePackageName = basePackageName; + this.writeBytesAndOffsetClassName = writeBytesAndOffsetClassName; + this.writeBytesClassName = writeBytesClassName; + this.writeIntAdviceClassName = writeIntAdviceClassName; + } + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed(basePackageName + ".ServletOutputStream"); + } + + @Override + public ElementMatcher typeMatcher() { + return hasSuperType(namedOneOf(basePackageName + ".ServletOutputStream")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("write") + .and(takesArguments(3)) + .and(takesArgument(0, byte[].class)) + .and(takesArgument(1, int.class)) + .and(takesArgument(2, int.class)) + .and(isPublic()), + writeBytesAndOffsetClassName); + transformer.applyAdviceToMethod( + named("write").and(takesArguments(1)).and(takesArgument(0, byte[].class)).and(isPublic()), + writeBytesClassName); + transformer.applyAdviceToMethod( + named("write").and(takesArguments(1)).and(takesArgument(0, int.class)).and(isPublic()), + writeIntAdviceClassName); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index f55e9b339f4b..cf587dafa567 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -439,6 +439,7 @@ hideFromDependabot(":instrumentation:servlet:servlet-common:javaagent") hideFromDependabot(":instrumentation:servlet:servlet-javax-common:javaagent") hideFromDependabot(":instrumentation:servlet:servlet-2.2:javaagent") hideFromDependabot(":instrumentation:servlet:servlet-3.0:javaagent") +hideFromDependabot(":instrumentation:servlet:servlet-3.0:javaagent-unit-tests") hideFromDependabot(":instrumentation:servlet:servlet-5.0:javaagent") hideFromDependabot(":instrumentation:spark-2.3:javaagent") hideFromDependabot(":instrumentation:spring:spring-batch-3.0:javaagent") diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java index bccb9b2e323f..1a5f340554dd 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java @@ -31,7 +31,33 @@ public enum ServerEndpoint { AUTH_REQUIRED("authRequired", 200, null), LOGIN("login", 302, null), AUTH_ERROR("basicsecured/endpoint", 401, null), - INDEXED_CHILD("child", 200, ""); + INDEXED_CHILD("child", 200, ""), + HTML( + "html", + 200, + "\n" + + "\n" + + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""), + HTML2( + "html2", + 200, + "\n" + + "\n" + + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""); public static final String ID_ATTRIBUTE_NAME = "test.request.id"; public static final String ID_PARAMETER_NAME = "id"; From 26b55c5e6ce9f0fefb47f7d5bf1539c26da493b0 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 14 Feb 2023 15:38:44 -0800 Subject: [PATCH 12/37] Avoid dependency on globals for unit tests --- .../servlet/v3_0/snippet/InjectionTest.java | 25 +++++------- .../SnippetInjectingResponseWrapperTest.java | 38 ++++++++++--------- .../servlet/v3_0/Servlet3Advice.java | 5 ++- .../Servlet3OutputStreamWriteBytesAdvice.java | 6 +-- ...OutputStreamWriteBytesAndOffsetAdvice.java | 12 +++--- .../Servlet3OutputStreamWriteIntAdvice.java | 4 +- .../servlet/v3_0/Servlet3Singletons.java | 9 +++++ ...> OutputStreamSnippetInjectionHelper.java} | 26 +++++++------ .../SnippetInjectingResponseWrapper.java | 23 +++++++---- .../servlet/ExperimentalSnippetHolder.java | 6 --- 10 files changed, 82 insertions(+), 72 deletions(-) rename instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/{ServletOutputStreamInjectionHelper.java => OutputStreamSnippetInjectionHelper.java} (77%) diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java index 2472deee295d..8b170ab9c34b 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java @@ -23,7 +23,6 @@ class InjectionTest { @Test void testInjectionForStringContainHeadTag() throws IOException { String testSnippet = "\n "; - ExperimentalSnippetHolder.setSnippet(testSnippet); // read the originalFile String original = readFile("staticHtmlOrigin.html"); // read the correct answer @@ -43,9 +42,8 @@ public void write(int b) throws IOException { writer.write(b); } }; - boolean injected = - ServletOutputStreamInjectionHelper.handleWrite( - originalBytes, 0, originalBytes.length, obj, sp); + OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); + boolean injected = helper.handleWrite(originalBytes, 0, originalBytes.length, obj, sp); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(injected).isEqualTo(true); writer.flush(); @@ -59,7 +57,6 @@ public void write(int b) throws IOException { @Disabled void testInjectionForChinese() throws IOException { String testSnippet = "\n "; - ExperimentalSnippetHolder.setSnippet(testSnippet); // read the originalFile String original = readFile("staticHtmlChineseOrigin.html"); // read the correct answer @@ -79,9 +76,8 @@ public void write(int b) throws IOException { writer.write(b); } }; - boolean injected = - ServletOutputStreamInjectionHelper.handleWrite( - originalBytes, 0, originalBytes.length, obj, sp); + OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); + boolean injected = helper.handleWrite(originalBytes, 0, originalBytes.length, obj, sp); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(injected).isEqualTo(true); writer.flush(); @@ -112,9 +108,8 @@ public void write(int b) throws IOException { writer.write(b); } }; - boolean injected = - ServletOutputStreamInjectionHelper.handleWrite( - originalBytes, 0, originalBytes.length, obj, sp); + OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); + boolean injected = helper.handleWrite(originalBytes, 0, originalBytes.length, obj, sp); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(0); assertThat(injected).isEqualTo(false); writer.flush(); @@ -126,7 +121,6 @@ public void write(int b) throws IOException { @Test void testHalfHeadTag() throws IOException { String testSnippet = "\n "; - ExperimentalSnippetHolder.setSnippet(testSnippet); // read the original string String originalFirstPart = "\n" + "\n" + ""; byte[] originalSecondPartBytes = originalSecondPart.getBytes(StandardCharsets.UTF_8); injected = - ServletOutputStreamInjectionHelper.handleWrite( - originalSecondPartBytes, 0, originalSecondPartBytes.length, obj, sp); + helper.handleWrite(originalSecondPartBytes, 0, originalSecondPartBytes.length, obj, sp); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(injected).isEqualTo(true); String correctSecondPart = diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java index 4b692622bf10..56c38194959e 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java @@ -10,7 +10,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; @@ -23,7 +22,6 @@ class SnippetInjectingResponseWrapperTest { @Test void testInjectToTextHtml() throws IOException { - // read the originalFile String original = readFile("staticHtmlOrigin.html"); String correct = readFile("staticHtmlAfter.html"); @@ -33,8 +31,9 @@ void testInjectToTextHtml() throws IOException { when(response.containsHeader("content-type")).thenReturn(true); StringWriter writer = new StringWriter(); when(response.getWriter()).thenReturn(new PrintWriter(writer)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); responseWrapper.getWriter().write(original); responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); @@ -58,8 +57,9 @@ void testInjectToChineseTextHtml() throws IOException { StringWriter writer = new StringWriter(); when(response.getWriter()).thenReturn(new PrintWriter(writer)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); responseWrapper.getWriter().write(original); responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); @@ -84,9 +84,10 @@ void shouldNotInjectToTextHtml() throws IOException { when(response.containsHeader("content-type")).thenReturn(true); when(response.getWriter()).thenReturn(new PrintWriter(writer, true)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); responseWrapper.getWriter().write(original); responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); @@ -110,13 +111,14 @@ void testWriteInt() throws IOException { StringWriter writer = new StringWriter(); // StringWriter correctWriter = new StringWriter(); when(response.getWriter()).thenReturn(new PrintWriter(writer)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); - byte[] originalBytes = original.getBytes(Charset.defaultCharset().name()); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); + byte[] originalBytes = original.getBytes(Charset.defaultCharset()); // byte[] correctBytes = correct.getBytes(UTF_8); // PrintWriter correctPw = new PrintWriter(correctWriter); - for (int i = 0; i < originalBytes.length; i++) { - responseWrapper.getWriter().write(originalBytes[i]); + for (byte originalByte : originalBytes) { + responseWrapper.getWriter().write(originalByte); } responseWrapper.getWriter().flush(); @@ -142,8 +144,9 @@ void testWriteCharArray() throws IOException { StringWriter writer = new StringWriter(); when(response.getWriter()).thenReturn(new PrintWriter(writer)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); char[] originalChars = original.toCharArray(); responseWrapper.getWriter().write(originalChars, 0, originalChars.length); responseWrapper.getWriter().flush(); @@ -171,8 +174,9 @@ void testWriteWithOffset() throws IOException { StringWriter writer = new StringWriter(); when(response.getWriter()).thenReturn(new PrintWriter(writer)); - ExperimentalSnippetHolder.setSnippet("\n "); - SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response); + String testSnippet = "\n "; + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, testSnippet); responseWrapper .getWriter() diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java index d625789f5595..a28e7b58d169 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java @@ -43,9 +43,10 @@ public static void onEnter( } HttpServletRequest httpServletRequest = (HttpServletRequest) request; - if (!ExperimentalSnippetHolder.getSnippet().isEmpty() + String snippet = ExperimentalSnippetHolder.getSnippet(); + if (!snippet.isEmpty() && !((HttpServletResponse) response).containsHeader(FAKE_SNIPPET_HEADER)) { - response = new SnippetInjectingResponseWrapper((HttpServletResponse) response); + response = new SnippetInjectingResponseWrapper((HttpServletResponse) response, snippet); } callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey()); callDepth.getAndIncrement(); diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java index c2258978b0cf..9aa550e9fdce 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java @@ -5,10 +5,10 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper; import java.io.IOException; import javax.servlet.ServletOutputStream; import net.bytebuddy.asm.Advice; @@ -26,7 +26,7 @@ public static boolean methodEnter( // if handleWrite return true, then it means the injection has happened and the 'write' // manipulate is done. the function would return false then, meaning skip the original write // function - return !ServletOutputStreamInjectionHelper.handleWrite( - write, 0, write.length, state, servletOutputStream); + return !getSnippetInjectionHelper() + .handleWrite(write, 0, write.length, state, servletOutputStream); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java index 69effc3bc789..ca566ad924ca 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java @@ -5,13 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper.handleWrite; - import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; -import java.io.IOException; -import javax.servlet.ServletOutputStream; import net.bytebuddy.asm.Advice; +import javax.servlet.ServletOutputStream; +import java.io.IOException; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; public class Servlet3OutputStreamWriteBytesAndOffsetAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, suppress = Throwable.class) @@ -28,6 +28,6 @@ public static boolean methodEnter( // if handleWrite return true, then it means the injection has happened and the 'write' // manipulate is done. the function would return false then, meaning skip the original write // function - return !handleWrite(write, off, len, state, servletOutputStream); + return !getSnippetInjectionHelper().handleWrite(write, off, len, state, servletOutputStream); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java index 1d59a2ad2a80..565c82b3171f 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java @@ -5,8 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionHelper.handleWrite; import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; import java.io.IOException; @@ -26,6 +26,6 @@ public static boolean methodEnter( // if handleWrite return true, then it means the injection has happened and the 'write' // manipulate is done. the function would return false then, meaning skip the original write // function - return !handleWrite(state, servletOutputStream, write); + return !getSnippetInjectionHelper().handleWrite(state, servletOutputStream, write); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java index fe4d8a3ce055..8d06e31addc0 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java @@ -8,12 +8,14 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.util.ClassAndMethod; import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import io.opentelemetry.javaagent.bootstrap.servlet.MappingResolver; import io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper; import io.opentelemetry.javaagent.instrumentation.servlet.ServletInstrumenterBuilder; import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import io.opentelemetry.javaagent.instrumentation.servlet.ServletResponseContext; import io.opentelemetry.javaagent.instrumentation.servlet.common.response.ResponseInstrumenterFactory; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.OutputStreamSnippetInjectionHelper; import javax.servlet.Filter; import javax.servlet.Servlet; import javax.servlet.http.HttpServletRequest; @@ -39,6 +41,9 @@ public final class Servlet3Singletons { private static final Instrumenter RESPONSE_INSTRUMENTER = ResponseInstrumenterFactory.createInstrumenter(INSTRUMENTATION_NAME); + private static final OutputStreamSnippetInjectionHelper SNIPPET_INJECTION_HELPER = + new OutputStreamSnippetInjectionHelper(ExperimentalSnippetHolder.getSnippet()); + public static ServletHelper helper() { return HELPER; } @@ -55,6 +60,10 @@ public static MappingResolver getMappingResolver(Object servletOrFilter) { return null; } + static OutputStreamSnippetInjectionHelper getSnippetInjectionHelper() { + return SNIPPET_INJECTION_HELPER; + } + private static MappingResolver.Factory getMappingResolverFactory(Object servletOrFilter) { boolean servlet = servletOrFilter instanceof Servlet; if (servlet) { diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java similarity index 77% rename from instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java rename to instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java index 235c5fdc2188..6e87ce65fb2d 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionHelper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java @@ -7,24 +7,29 @@ import static java.util.logging.Level.FINE; -import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import java.io.IOException; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.logging.Logger; -import javax.servlet.ServletOutputStream; -public class ServletOutputStreamInjectionHelper { +public class OutputStreamSnippetInjectionHelper { private static final Logger logger = - Logger.getLogger(ServletOutputStreamInjectionHelper.class.getName()); + Logger.getLogger(OutputStreamSnippetInjectionHelper.class.getName()); + + private final String snippet; + + public OutputStreamSnippetInjectionHelper(String snippet) { + this.snippet = snippet; + } /** * return true means this method performed the injection, return false means it didn't inject * anything Servlet3OutputStreamWriteAdvice would skip the write method when the return value is * true, and would write the original bytes when the return value is false. */ - public static boolean handleWrite( - byte[] original, int off, int length, InjectionState state, ServletOutputStream out) + public boolean handleWrite( + byte[] original, int off, int length, InjectionState state, OutputStream out) throws IOException { if (state.isHeadTagWritten()) { return false; @@ -46,7 +51,7 @@ public static boolean handleWrite( } byte[] snippetBytes; try { - snippetBytes = ExperimentalSnippetHolder.getSnippetBytes(state.getCharacterEncoding()); + snippetBytes = snippet.getBytes(state.getCharacterEncoding()); } catch (UnsupportedEncodingException e) { logger.log(FINE, "UnsupportedEncodingException", e); return false; @@ -59,8 +64,7 @@ public static boolean handleWrite( return true; } - public static boolean handleWrite(InjectionState state, ServletOutputStream out, int b) - throws IOException { + public boolean handleWrite(InjectionState state, OutputStream out, int b) throws IOException { if (state.isHeadTagWritten()) { return false; } @@ -74,7 +78,7 @@ public static boolean handleWrite(InjectionState state, ServletOutputStream out, } byte[] snippetBytes; try { - snippetBytes = ExperimentalSnippetHolder.getSnippetBytes(state.getCharacterEncoding()); + snippetBytes = snippet.getBytes(state.getCharacterEncoding()); } catch (UnsupportedEncodingException e) { logger.log(FINE, "UnsupportedEncodingException", e); return false; @@ -85,6 +89,4 @@ public static boolean handleWrite(InjectionState state, ServletOutputStream out, out.write(snippetBytes); return true; } - - private ServletOutputStreamInjectionHelper() {} } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 85e5a40c80a1..5882119eb9e2 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -8,7 +8,6 @@ import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.initializeInjectionStateIfNeeded; import static java.util.logging.Level.FINE; -import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import java.io.IOException; import java.io.PrintWriter; import java.lang.invoke.MethodHandle; @@ -31,20 +30,27 @@ * written, including the snippet length. */ public class SnippetInjectingResponseWrapper extends HttpServletResponseWrapper { + private static final Logger logger = Logger.getLogger(HttpServletResponseWrapper.class.getName()); + public static final String FAKE_SNIPPET_HEADER = "FAKE_SNIPPET_HEADER"; - private static final String SNIPPET = ExperimentalSnippetHolder.getSnippet(); - private static final int SNIPPET_LENGTH = SNIPPET.length(); private static final int UNSET = -1; + + // this is for Servlet 3.1 support @Nullable private static final MethodHandle setContentLengthLongHandler = getMethodHandle(); + private final String snippet; + private final int snippetLength; + private long contentLength = UNSET; private SnippetInjectingPrintWriter snippetInjectingPrintWriter = null; - public SnippetInjectingResponseWrapper(HttpServletResponse response) { + public SnippetInjectingResponseWrapper(HttpServletResponse response, String snippet) { super(response); + this.snippet = snippet; + snippetLength = snippet.length(); } @Override @@ -67,7 +73,7 @@ public void setHeader(String name, String value) { // checking content-type is just an optimization to avoid unnecessary parsing if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { try { - contentLength = Long.valueOf(value); + contentLength = Long.parseLong(value); } catch (NumberFormatException ex) { logger.log(FINE, "NumberFormatException", ex); } @@ -80,7 +86,7 @@ public void addHeader(String name, String value) { // checking content-type is just an optimization to avoid unnecessary parsing if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { try { - contentLength = Long.valueOf(value); + contentLength = Long.parseLong(value); } catch (NumberFormatException ex) { logger.log(FINE, "NumberFormatException", ex); } @@ -127,6 +133,7 @@ private static MethodHandle getMethodHandle() { } } + // this is for Servlet 3.1 support public void setContentLengthLong(long length) throws Throwable { contentLength = length; if (setContentLengthLongHandler == null) { @@ -158,14 +165,14 @@ public PrintWriter getWriter() throws IOException { } if (snippetInjectingPrintWriter == null) { snippetInjectingPrintWriter = - new SnippetInjectingPrintWriter(super.getWriter(), SNIPPET, this); + new SnippetInjectingPrintWriter(super.getWriter(), snippet, this); } return snippetInjectingPrintWriter; } public void updateContentLengthIfPreviouslySet() { if (contentLength != UNSET) { - setContentLength((int) contentLength + SNIPPET_LENGTH); + setContentLength((int) contentLength + snippetLength); } } diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java index 25d90cd27c0c..dcf787748f6f 100644 --- a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -5,8 +5,6 @@ package io.opentelemetry.javaagent.bootstrap.servlet; -import java.io.UnsupportedEncodingException; - public class ExperimentalSnippetHolder { private static String snippet = ""; @@ -19,9 +17,5 @@ public static String getSnippet() { return snippet; } - public static byte[] getSnippetBytes(String encoding) throws UnsupportedEncodingException { - return snippet.getBytes(encoding); - } - private ExperimentalSnippetHolder() {} } From 72563d2c9f359e1196f809d69caa5d19d8293e58 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Wed, 15 Feb 2023 21:17:12 -0800 Subject: [PATCH 13/37] Update Servlet3OutputStreamWriteBytesAndOffsetAdvice.java --- .../Servlet3OutputStreamWriteBytesAndOffsetAdvice.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java index ca566ad924ca..5540a0a4ff91 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java @@ -5,14 +5,14 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; -import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; -import net.bytebuddy.asm.Advice; -import javax.servlet.ServletOutputStream; -import java.io.IOException; - import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; +import java.io.IOException; +import javax.servlet.ServletOutputStream; +import net.bytebuddy.asm.Advice; + public class Servlet3OutputStreamWriteBytesAndOffsetAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class, suppress = Throwable.class) public static boolean methodEnter( From bd024bcf783b77666300d370fb1cb2eb8d6a06d2 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Fri, 17 Feb 2023 18:36:29 -0800 Subject: [PATCH 14/37] update comments --- .../javaagent-unit-tests/build.gradle.kts | 1 - .../servlet/v3_0/snippet/InjectionTest.java | 13 +++-------- .../SnippetInjectingResponseWrapperTest.java | 22 +------------------ .../Servlet3OutputStreamWriteBytesAdvice.java | 8 ++++--- ...OutputStreamWriteBytesAndOffsetAdvice.java | 8 ++++--- .../Servlet3OutputStreamWriteIntAdvice.java | 8 ++++--- .../SnippetInjectingResponseWrapper.java | 2 +- .../test/groovy/AbstractServlet3Test.groovy | 16 +++++++------- .../src/test/groovy/JettyServlet3Test.groovy | 20 ++++++++--------- .../src/test/groovy/TestServlet3.groovy | 18 ++++++++------- .../src/test/groovy/TomcatServlet3Test.groovy | 20 ++++++++--------- .../test/java/RequestDispatcherServlet.java | 11 +++++----- .../testing/junit/http/ServerEndpoint.java | 8 +++---- 13 files changed, 68 insertions(+), 87 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts index 1046d2b6fece..c6f646595e21 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/build.gradle.kts @@ -4,6 +4,5 @@ plugins { dependencies { testImplementation("javax.servlet:javax.servlet-api:3.0.1") - testImplementation(project(":instrumentation:servlet:servlet-common:bootstrap")) testImplementation(project(":instrumentation:servlet:servlet-3.0:javaagent")) } diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java index 8b170ab9c34b..0b8f3bdc501d 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java @@ -10,7 +10,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; import java.io.IOException; import java.io.StringWriter; import java.nio.charset.StandardCharsets; @@ -23,9 +22,7 @@ class InjectionTest { @Test void testInjectionForStringContainHeadTag() throws IOException { String testSnippet = "\n "; - // read the originalFile String original = readFile("staticHtmlOrigin.html"); - // read the correct answer String correct = readFile("staticHtmlAfter.html"); byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); @@ -57,11 +54,10 @@ public void write(int b) throws IOException { @Disabled void testInjectionForChinese() throws IOException { String testSnippet = "\n "; - // read the originalFile String original = readFile("staticHtmlChineseOrigin.html"); - // read the correct answer String correct = readFile("staticHtmlChineseAfter.html"); byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); + SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); when(response.isCommitted()).thenReturn(false); when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); @@ -90,11 +86,9 @@ public void write(int b) throws IOException { @Test void testInjectionForStringWithoutHeadTag() throws IOException { String testSnippet = "\n "; - ExperimentalSnippetHolder.setSnippet(testSnippet); - // read the originalFile String original = readFile("htmlWithoutHeadTag.html"); - byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); + SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); when(response.isCommitted()).thenReturn(false); when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); @@ -119,9 +113,8 @@ public void write(int b) throws IOException { } @Test - void testHalfHeadTag() throws IOException { + void testHeadTagSplitAcrossTwoWrites() throws IOException { String testSnippet = "\n "; - // read the original string String originalFirstPart = "\n" + "\n" + " Test "; SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response, testSnippet); byte[] originalBytes = original.getBytes(Charset.defaultCharset()); - // byte[] correctBytes = correct.getBytes(UTF_8); - // PrintWriter correctPw = new PrintWriter(correctWriter); for (byte originalByte : originalBytes) { responseWrapper.getWriter().write(originalByte); } @@ -124,17 +112,14 @@ void testWriteInt() throws IOException { responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); - // read file get result String result = writer.toString(); writer.close(); - // check whether new response == correct answer assertThat(result).isEqualTo(correct); } @Test void testWriteCharArray() throws IOException { - // read the originalFile String original = readFile("staticHtmlChineseOrigin.html"); String correct = readFile("staticHtmlChineseAfter.html"); HttpServletResponse response = mock(HttpServletResponse.class); @@ -152,17 +137,14 @@ void testWriteCharArray() throws IOException { responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); - // read file get result String result = writer.toString(); writer.close(); - // check whether new response == correct answer assertThat(result).isEqualTo(correct); } @Test void testWriteWithOffset() throws IOException { - // read the originalFile String original = readFile("staticHtmlChineseOrigin.html"); String correct = readFile("staticHtmlChineseAfter.html"); String extraBuffer = "this buffer should not be print out"; @@ -184,10 +166,8 @@ void testWriteWithOffset() throws IOException { responseWrapper.getWriter().flush(); responseWrapper.getWriter().close(); - // read file get result String result = writer.toString(); writer.close(); - // check whether new response == correct answer assertThat(result).isEqualTo(correct); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java index 9aa550e9fdce..b07fc3b14bd4 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java @@ -23,9 +23,11 @@ public static boolean methodEnter( if (state == null) { return true; } - // if handleWrite return true, then it means the injection has happened and the 'write' - // manipulate is done. the function would return false then, meaning skip the original write - // function + // if handleWrite returns true, then it means the original bytes + the snippet were written + // to the servletOutputStream, and so we no longer need to execute the original method + // call (see skipOn above) + // if it returns false, then it means nothing was written to the servletOutputStream and the + // original method call should be executed return !getSnippetInjectionHelper() .handleWrite(write, 0, write.length, state, servletOutputStream); } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java index 5540a0a4ff91..a9969cfe7eef 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java @@ -25,9 +25,11 @@ public static boolean methodEnter( if (state == null) { return true; } - // if handleWrite return true, then it means the injection has happened and the 'write' - // manipulate is done. the function would return false then, meaning skip the original write - // function + // if handleWrite returns true, then it means the original bytes + the snippet were written + // to the servletOutputStream, and so we no longer need to execute the original method + // call (see skipOn above) + // if it returns false, then it means nothing was written to the servletOutputStream and the + // original method call should be executed return !getSnippetInjectionHelper().handleWrite(write, off, len, state, servletOutputStream); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java index 565c82b3171f..d78696517077 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java @@ -23,9 +23,11 @@ public static boolean methodEnter( if (state == null) { return true; } - // if handleWrite return true, then it means the injection has happened and the 'write' - // manipulate is done. the function would return false then, meaning skip the original write - // function + // if handleWrite returns true, then it means the original bytes + the snippet were written + // to the servletOutputStream, and so we no longer need to execute the original method + // call (see skipOn above) + // if it returns false, then it means nothing was written to the servletOutputStream and the + // original method call should be executed return !getSnippetInjectionHelper().handleWrite(state, servletOutputStream, write); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 5882119eb9e2..494d03bb583b 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -24,7 +24,7 @@ * occurs and the content length was set previously. * *

If the Content-Length is set after snippet injection occurs (either for the first time or is - * set again for some reason),we intentionally do not add the snippet length, because the + * set again for some reason), we intentionally do not add the snippet length, because the * application server may be making that call at the end of a request when it sees the request has * not been submitted, in which case it is likely using the real length of content that has been * written, including the snippet length. diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy index 0d48faf71c59..a79fe7a8c1d8 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy @@ -17,8 +17,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM @@ -53,8 +53,8 @@ abstract class AbstractServlet3Test extends HttpServerTest extends HttpServerTest Test ") - def request = request(HTML2, "GET") + def request = request(HTML_SERVLET_OUTPUT_STREAM, "GET") def response = client.execute(request).aggregate().join() expect: - response.status().code() == HTML2.status + response.status().code() == HTML_SERVLET_OUTPUT_STREAM.status // check response content-length header String result = "\n" + "\n" + @@ -128,11 +128,11 @@ abstract class AbstractServlet3Test extends HttpServerTest Test ") - def request = request(HTML, "GET") + def request = request(HTML_PRINT_WRITER, "GET") def response = client.execute(request).aggregate().join() expect: - response.status().code() == HTML.status + response.status().code() == HTML_PRINT_WRITER.status String result = "\n" + "\n" + "\n" + diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy index ecb656bec4be..a3acb146125e 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy @@ -18,8 +18,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT @@ -172,8 +172,8 @@ class JettyServlet3TestForward extends JettyDispatchTest { super.setupServlets(context) addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Forward) @@ -211,8 +211,8 @@ class JettyServlet3TestInclude extends JettyDispatchTest { super.setupServlets(context) addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Include) @@ -238,8 +238,8 @@ class JettyServlet3TestDispatchImmediate extends JettyDispatchTest { @Override protected void setupServlets(ServletContextHandler context) { super.setupServlets(context) - addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchImmediate) @@ -267,8 +267,8 @@ class JettyServlet3TestDispatchAsync extends JettyDispatchTest { @Override protected void setupServlets(ServletContextHandler context) { super.setupServlets(context) - addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchAsync) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy index 83e1873f3c20..2002be3a88f1 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy @@ -18,8 +18,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT @@ -73,13 +73,13 @@ class TestServlet3 { break case EXCEPTION: throw new ServletException(endpoint.body) - case HTML: + case HTML_PRINT_WRITER: resp.contentType = "text/html" resp.status = endpoint.status resp.setContentLengthLong(endpoint.body.length()) resp.writer.print(endpoint.body) break - case HTML2: + case HTML_SERVLET_OUTPUT_STREAM: resp.contentType = "text/html" resp.status = endpoint.status resp.setContentLength(endpoint.body.length()) @@ -158,14 +158,14 @@ class TestServlet3 { } throw new ServletException(endpoint.body) break - case HTML: + case HTML_PRINT_WRITER: resp.contentType = "text/html" resp.status = endpoint.status resp.setContentLength(endpoint.body.length()) resp.writer.print(endpoint.body) context.complete() break - case HTML2: + case HTML_SERVLET_OUTPUT_STREAM: resp.contentType = "text/html" resp.status = endpoint.status resp.getOutputStream().print(endpoint.body) @@ -227,12 +227,14 @@ class TestServlet3 { resp.status = endpoint.status resp.writer.print(endpoint.body) throw new ServletException(endpoint.body) - case HTML: + case HTML_PRINT_WRITER: + // intentionally testing HTML_PRINT_WRITER and HTML_SERVLET_OUTPUT_STREAM with different order of setting status and contentType resp.status = endpoint.status resp.contentType = "text/html" resp.writer.print(endpoint.body) break - case HTML2: + case HTML_SERVLET_OUTPUT_STREAM: + // intentionally testing HTML_PRINT_WRITER and HTML_SERVLET_OUTPUT_STREAM with different order of setting status and contentType resp.contentType = "text/html" resp.status = endpoint.status resp.getOutputStream().print(endpoint.body) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy index ba5dd0280bb8..60f58741412b 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy @@ -30,8 +30,8 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2 +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM @@ -345,8 +345,8 @@ class TomcatServlet3TestForward extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Forward) addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Forward) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Forward) } } @@ -388,8 +388,8 @@ class TomcatServlet3TestInclude extends TomcatDispatchTest { addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Include) addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML2.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Include) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Include) } } @@ -417,8 +417,8 @@ class TomcatServlet3TestDispatchImmediate extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchImmediate) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchImmediate) addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) } } @@ -442,8 +442,8 @@ class TomcatServlet3TestDispatchAsync extends TomcatDispatchTest { addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + HTML.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + HTML2.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchAsync) + addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchAsync) addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java index 5d94f95e3c41..9abce0892ac1 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML; -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML2; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import java.io.IOException; @@ -41,10 +41,11 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) String target = req.getServletPath().replace("/dispatch", ""); ServletContext context = getServletContext(); RequestDispatcher dispatcher = context.getRequestDispatcher(target); - // for HTML test case, set content type before calling include as - // "include" reject modify on resp later + // for HTML test case, set the content type before calling include because + // setContentType will be rejected if called inside of include // check https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html - if (ServerEndpoint.forPath(target) == HTML || ServerEndpoint.forPath(target) == HTML2) { + if (ServerEndpoint.forPath(target) == HTML_PRINT_WRITER + || ServerEndpoint.forPath(target) == HTML_SERVLET_OUTPUT_STREAM) { resp.setContentType("text/html"); } dispatcher.include(req, resp); diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java index 1a5f340554dd..53895d00f1f8 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java @@ -32,8 +32,8 @@ public enum ServerEndpoint { LOGIN("login", 302, null), AUTH_ERROR("basicsecured/endpoint", 401, null), INDEXED_CHILD("child", 200, ""), - HTML( - "html", + HTML_PRINT_WRITER( + "HTML_PRINT_WRITER", 200, "\n" + "\n" @@ -45,8 +45,8 @@ public enum ServerEndpoint { + "

test works

\n" + "\n" + ""), - HTML2( - "html2", + HTML_SERVLET_OUTPUT_STREAM( + "HTML_SERVLET_OUTPUT_STREAM", 200, "\n" + "\n" From 3bd615e5f6a81e08a3e65798a4d9efd00f66cadd Mon Sep 17 00:00:00 2001 From: siyuniu-ms <123212536+siyuniu-ms@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:40:32 -0800 Subject: [PATCH 15/37] Update instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java Co-authored-by: Trask Stalnaker --- .../servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 494d03bb583b..c6fd569e2844 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -55,7 +55,7 @@ public SnippetInjectingResponseWrapper(HttpServletResponse response, String snip @Override public boolean containsHeader(String name) { - // override this function in order to make sure the response is wrapped + // this function is overridden in order to make sure the response is wrapped // but not wrapped twice // we didn't use the traditional method req.setAttribute // because async would set the original request attribute but didn't pass down the wrapped From 79b2ed5118bc78b6bd46258ad720159952993fc4 Mon Sep 17 00:00:00 2001 From: siyuniu-ms <123212536+siyuniu-ms@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:40:46 -0800 Subject: [PATCH 16/37] Update instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java Co-authored-by: Trask Stalnaker --- .../servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index c6fd569e2844..6aad8c0287a6 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -57,7 +57,7 @@ public SnippetInjectingResponseWrapper(HttpServletResponse response, String snip public boolean containsHeader(String name) { // this function is overridden in order to make sure the response is wrapped // but not wrapped twice - // we didn't use the traditional method req.setAttribute + // we don't use req.setAttribute // because async would set the original request attribute but didn't pass down the wrapped // response // then the response would never be wrapped again From c1b5da5b1631b61c82f6f6ce0c716dd9a497caaf Mon Sep 17 00:00:00 2001 From: siyuniu-ms <123212536+siyuniu-ms@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:41:00 -0800 Subject: [PATCH 17/37] Update instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java Co-authored-by: Trask Stalnaker --- .../servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 6aad8c0287a6..8c5b95f4fd1c 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -178,7 +178,7 @@ public void updateContentLengthIfPreviouslySet() { public boolean isNotSafeToInject() { // if content-length was set and response was already committed (headers sent to the client), - // then not safe to inject because the content-length header cannot be updated to account for + // then it is not safe to inject because the content-length header cannot be updated to account for // the snippet length return isCommitted() && (contentLength != UNSET); } From f0715a1e7cc3c62bff8a14fb7fdc83cdc994b05a Mon Sep 17 00:00:00 2001 From: siyuniu-ms <123212536+siyuniu-ms@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:41:18 -0800 Subject: [PATCH 18/37] Update instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java Co-authored-by: Trask Stalnaker --- .../v3_0/snippet/SnippetInjectingResponseWrapper.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 8c5b95f4fd1c..06bcad41a7c6 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -58,9 +58,9 @@ public boolean containsHeader(String name) { // this function is overridden in order to make sure the response is wrapped // but not wrapped twice // we don't use req.setAttribute - // because async would set the original request attribute but didn't pass down the wrapped - // response - // then the response would never be wrapped again + // because async requests pass down their attributes, but don't pass down our wrapped response + // and so we would see the presence of the attribute and think the response was already wrapped + // when it really is not // see also https://docs.oracle.com/javaee/7/api/javax/servlet/AsyncContext.html if (name.equals(FAKE_SNIPPET_HEADER)) { return true; From 7554774d3ff987ef8b30b12aa66765c67a151ea9 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Tue, 21 Feb 2023 11:11:20 -0800 Subject: [PATCH 19/37] Update SnippetInjectingResponseWrapper.java --- .../servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 06bcad41a7c6..860c81c0141d 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -178,8 +178,8 @@ public void updateContentLengthIfPreviouslySet() { public boolean isNotSafeToInject() { // if content-length was set and response was already committed (headers sent to the client), - // then it is not safe to inject because the content-length header cannot be updated to account for - // the snippet length + // then it is not safe to inject because the content-length header cannot be updated to account + // for the snippet length return isCommitted() && (contentLength != UNSET); } } From 2097d0bb0b1f1d750f057b871bf5736d844b9da0 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Tue, 21 Feb 2023 16:17:06 -0800 Subject: [PATCH 20/37] Update Servlet3Singletons.java --- .../instrumentation/servlet/v3_0/Servlet3Singletons.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java index 8d06e31addc0..6c59213a4619 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Singletons.java @@ -60,7 +60,7 @@ public static MappingResolver getMappingResolver(Object servletOrFilter) { return null; } - static OutputStreamSnippetInjectionHelper getSnippetInjectionHelper() { + public static OutputStreamSnippetInjectionHelper getSnippetInjectionHelper() { return SNIPPET_INJECTION_HELPER; } From 1008aef1692aa7332d679ad553f582a6d9da83c8 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Fri, 24 Feb 2023 15:05:03 -0800 Subject: [PATCH 21/37] refactor, rename --- ...tputStreamSnippetInjectionHelperTest.java} | 20 ++++++++--------- .../SnippetInjectingResponseWrapperTest.java | 22 +++++++++---------- ...lAfter.html => afterSnippetInjection.html} | 0 ...html => afterSnippetInjectionChinese.html} | 0 ...rigin.html => beforeSnippetInjection.html} | 0 ...tml => beforeSnippetInjectionChinese.html} | 0 .../servlet-3.0/javaagent/build.gradle.kts | 1 - .../Servlet3OutputStreamWriteBytesAdvice.java | 2 +- ...OutputStreamWriteBytesAndOffsetAdvice.java | 2 +- .../OutputStreamSnippetInjectionHelper.java | 2 +- .../test/groovy/AbstractServlet3Test.groovy | 1 - .../src/test/groovy/TestServlet3.groovy | 4 +--- .../servlet/ExperimentalSnippetHolder.java | 2 +- .../ServletOutputStreamInstrumentation.java | 16 +++++++------- .../testing/junit/http/ServerEndpoint.java | 4 ++-- 15 files changed, 36 insertions(+), 40 deletions(-) rename instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/{InjectionTest.java => OutputStreamSnippetInjectionHelperTest.java} (88%) rename instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/{staticHtmlAfter.html => afterSnippetInjection.html} (100%) rename instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/{staticHtmlChineseAfter.html => afterSnippetInjectionChinese.html} (100%) rename instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/{staticHtmlOrigin.html => beforeSnippetInjection.html} (100%) rename instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/{staticHtmlChineseOrigin.html => beforeSnippetInjectionChinese.html} (100%) diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelperTest.java similarity index 88% rename from instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java rename to instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelperTest.java index 0b8f3bdc501d..bb719deb43bd 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionTest.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelperTest.java @@ -17,13 +17,13 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -class InjectionTest { +class OutputStreamSnippetInjectionHelperTest { @Test void testInjectionForStringContainHeadTag() throws IOException { String testSnippet = "\n "; - String original = readFile("staticHtmlOrigin.html"); - String correct = readFile("staticHtmlAfter.html"); + String original = readFile("beforeSnippetInjection.html"); + String correct = readFile("afterSnippetInjection.html"); byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); when(response.isCommitted()).thenReturn(false); @@ -40,7 +40,7 @@ public void write(int b) throws IOException { } }; OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); - boolean injected = helper.handleWrite(originalBytes, 0, originalBytes.length, obj, sp); + boolean injected = helper.handleWrite(obj, sp, originalBytes, 0, originalBytes.length); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(injected).isEqualTo(true); writer.flush(); @@ -54,8 +54,8 @@ public void write(int b) throws IOException { @Disabled void testInjectionForChinese() throws IOException { String testSnippet = "\n "; - String original = readFile("staticHtmlChineseOrigin.html"); - String correct = readFile("staticHtmlChineseAfter.html"); + String original = readFile("beforeSnippetInjectionChinese.html"); + String correct = readFile("afterSnippetInjectionChinese.html"); byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); @@ -73,7 +73,7 @@ public void write(int b) throws IOException { } }; OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); - boolean injected = helper.handleWrite(originalBytes, 0, originalBytes.length, obj, sp); + boolean injected = helper.handleWrite(obj, sp, originalBytes, 0, originalBytes.length); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(injected).isEqualTo(true); writer.flush(); @@ -103,7 +103,7 @@ public void write(int b) throws IOException { } }; OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); - boolean injected = helper.handleWrite(originalBytes, 0, originalBytes.length, obj, sp); + boolean injected = helper.handleWrite(obj, sp, originalBytes, 0, originalBytes.length); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(0); assertThat(injected).isEqualTo(false); writer.flush(); @@ -132,7 +132,7 @@ public void write(int b) throws IOException { }; OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); boolean injected = - helper.handleWrite(originalFirstPartBytes, 0, originalFirstPartBytes.length, obj, sp); + helper.handleWrite(obj, sp, originalFirstPartBytes, 0, originalFirstPartBytes.length); writer.flush(); String result = writer.toString(); @@ -150,7 +150,7 @@ public void write(int b) throws IOException { + ""; byte[] originalSecondPartBytes = originalSecondPart.getBytes(StandardCharsets.UTF_8); injected = - helper.handleWrite(originalSecondPartBytes, 0, originalSecondPartBytes.length, obj, sp); + helper.handleWrite(obj, sp, originalSecondPartBytes, 0, originalSecondPartBytes.length); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(injected).isEqualTo(true); String correctSecondPart = diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java index 69d78c1ddfd9..437a446cf7d1 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java @@ -23,8 +23,8 @@ class SnippetInjectingResponseWrapperTest { @Test void testInjectToTextHtml() throws IOException { - String original = readFile("staticHtmlOrigin.html"); - String correct = readFile("staticHtmlAfter.html"); + String original = readFile("beforeSnippetInjection.html"); + String correct = readFile("afterSnippetInjection.html"); HttpServletResponse response = mock(HttpServletResponse.class); when(response.getContentType()).thenReturn("text/html"); when(response.getStatus()).thenReturn(200); @@ -47,8 +47,8 @@ void testInjectToTextHtml() throws IOException { @Disabled void testInjectToChineseTextHtml() throws IOException { - String original = readFile("staticHtmlChineseOrigin.html"); - String correct = readFile("staticHtmlChineseAfter.html"); + String original = readFile("beforeSnippetInjectionChinese.html"); + String correct = readFile("afterSnippetInjectionChinese.html"); HttpServletResponse response = mock(HttpServletResponse.class); when(response.getContentType()).thenReturn("text/html"); @@ -69,7 +69,7 @@ void testInjectToChineseTextHtml() throws IOException { @Test void shouldNotInjectToTextHtml() throws IOException { - String original = readFile("staticHtmlOrigin.html"); + String original = readFile("beforeSnippetInjection.html"); StringWriter writer = new StringWriter(); HttpServletResponse response = mock(HttpServletResponse.class); @@ -94,8 +94,8 @@ void shouldNotInjectToTextHtml() throws IOException { @Test void testWriteInt() throws IOException { - String original = readFile("staticHtmlOrigin.html"); - String correct = readFile("staticHtmlAfter.html"); + String original = readFile("beforeSnippetInjection.html"); + String correct = readFile("afterSnippetInjection.html"); HttpServletResponse response = mock(HttpServletResponse.class); when(response.getContentType()).thenReturn("text/html"); @@ -120,8 +120,8 @@ void testWriteInt() throws IOException { @Test void testWriteCharArray() throws IOException { - String original = readFile("staticHtmlChineseOrigin.html"); - String correct = readFile("staticHtmlChineseAfter.html"); + String original = readFile("beforeSnippetInjectionChinese.html"); + String correct = readFile("afterSnippetInjectionChinese.html"); HttpServletResponse response = mock(HttpServletResponse.class); when(response.getContentType()).thenReturn("text/html"); when(response.getStatus()).thenReturn(200); @@ -145,8 +145,8 @@ void testWriteCharArray() throws IOException { @Test void testWriteWithOffset() throws IOException { - String original = readFile("staticHtmlChineseOrigin.html"); - String correct = readFile("staticHtmlChineseAfter.html"); + String original = readFile("beforeSnippetInjectionChinese.html"); + String correct = readFile("afterSnippetInjectionChinese.html"); String extraBuffer = "this buffer should not be print out"; original = extraBuffer + original; HttpServletResponse response = mock(HttpServletResponse.class); diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlAfter.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/afterSnippetInjection.html similarity index 100% rename from instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlAfter.html rename to instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/afterSnippetInjection.html diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseAfter.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/afterSnippetInjectionChinese.html similarity index 100% rename from instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseAfter.html rename to instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/afterSnippetInjectionChinese.html diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlOrigin.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/beforeSnippetInjection.html similarity index 100% rename from instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlOrigin.html rename to instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/beforeSnippetInjection.html diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseOrigin.html b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/beforeSnippetInjectionChinese.html similarity index 100% rename from instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/staticHtmlChineseOrigin.html rename to instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/resources/beforeSnippetInjectionChinese.html diff --git a/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts b/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts index e9d9f0a6d093..ee6fcecfeb37 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts +++ b/instrumentation/servlet/servlet-3.0/javaagent/build.gradle.kts @@ -27,7 +27,6 @@ dependencies { testLibrary("org.eclipse.jetty:jetty-server:8.0.0.v20110901") testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901") - // TODO (trask?) test against tomcat 7 (servlet 3.0) testLibrary("org.apache.tomcat.embed:tomcat-embed-core:8.0.41") testLibrary("org.apache.tomcat.embed:tomcat-embed-jasper:8.0.41") diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java index b07fc3b14bd4..d17e8e551224 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java @@ -29,6 +29,6 @@ public static boolean methodEnter( // if it returns false, then it means nothing was written to the servletOutputStream and the // original method call should be executed return !getSnippetInjectionHelper() - .handleWrite(write, 0, write.length, state, servletOutputStream); + .handleWrite(state, servletOutputStream, write, 0, write.length); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java index a9969cfe7eef..e2c0a1823f42 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java @@ -30,6 +30,6 @@ public static boolean methodEnter( // call (see skipOn above) // if it returns false, then it means nothing was written to the servletOutputStream and the // original method call should be executed - return !getSnippetInjectionHelper().handleWrite(write, off, len, state, servletOutputStream); + return !getSnippetInjectionHelper().handleWrite(state, servletOutputStream, write, off, len); } } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java index 6e87ce65fb2d..e88cb15ab9b9 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java @@ -29,7 +29,7 @@ public OutputStreamSnippetInjectionHelper(String snippet) { * true, and would write the original bytes when the return value is false. */ public boolean handleWrite( - byte[] original, int off, int length, InjectionState state, OutputStream out) + InjectionState state, OutputStream out, byte[] original, int off, int length) throws IOException { if (state.isHeadTagWritten()) { return false; diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy index a79fe7a8c1d8..aadfefed9952 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy @@ -109,7 +109,6 @@ abstract class AbstractServlet3Test extends HttpServerTest\n" + "\n" + diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy index 2002be3a88f1..e992ea8ba97b 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy @@ -228,13 +228,11 @@ class TestServlet3 { resp.writer.print(endpoint.body) throw new ServletException(endpoint.body) case HTML_PRINT_WRITER: - // intentionally testing HTML_PRINT_WRITER and HTML_SERVLET_OUTPUT_STREAM with different order of setting status and contentType - resp.status = endpoint.status + // intentionally testing setting status before contentType here to cover that case somewhere resp.status = endpoint.status resp.contentType = "text/html" resp.writer.print(endpoint.body) break case HTML_SERVLET_OUTPUT_STREAM: - // intentionally testing HTML_PRINT_WRITER and HTML_SERVLET_OUTPUT_STREAM with different order of setting status and contentType resp.contentType = "text/html" resp.status = endpoint.status resp.getOutputStream().print(endpoint.body) diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java index dcf787748f6f..acc944ffa4b1 100644 --- a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -7,7 +7,7 @@ public class ExperimentalSnippetHolder { - private static String snippet = ""; + private static volatile String snippet = ""; public static void setSnippet(String snippet) { ExperimentalSnippetHolder.snippet = snippet; diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java index df80666d71d1..a545f3874b7a 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java @@ -20,18 +20,18 @@ public class ServletOutputStreamInstrumentation implements TypeInstrumentation { private final String basePackageName; - private final String writeBytesAndOffsetClassName; - private final String writeBytesClassName; + private final String writeBytesAndOffsetAdviceClassName; + private final String writeBytesAdviceClassName; private final String writeIntAdviceClassName; public ServletOutputStreamInstrumentation( String basePackageName, - String writeBytesAndOffsetClassName, - String writeBytesClassName, + String writeBytesAndOffsetAdviceClassName, + String writeBytesAdviceClassName, String writeIntAdviceClassName) { this.basePackageName = basePackageName; - this.writeBytesAndOffsetClassName = writeBytesAndOffsetClassName; - this.writeBytesClassName = writeBytesClassName; + this.writeBytesAndOffsetAdviceClassName = writeBytesAndOffsetAdviceClassName; + this.writeBytesAdviceClassName = writeBytesAdviceClassName; this.writeIntAdviceClassName = writeIntAdviceClassName; } @@ -54,10 +54,10 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(1, int.class)) .and(takesArgument(2, int.class)) .and(isPublic()), - writeBytesAndOffsetClassName); + writeBytesAndOffsetAdviceClassName); transformer.applyAdviceToMethod( named("write").and(takesArguments(1)).and(takesArgument(0, byte[].class)).and(isPublic()), - writeBytesClassName); + writeBytesAdviceClassName); transformer.applyAdviceToMethod( named("write").and(takesArguments(1)).and(takesArgument(0, int.class)).and(isPublic()), writeIntAdviceClassName); diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java index 53895d00f1f8..78751a47f7b3 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java @@ -33,7 +33,7 @@ public enum ServerEndpoint { AUTH_ERROR("basicsecured/endpoint", 401, null), INDEXED_CHILD("child", 200, ""), HTML_PRINT_WRITER( - "HTML_PRINT_WRITER", + "htmlPrintWriter", 200, "\n" + "\n" @@ -46,7 +46,7 @@ public enum ServerEndpoint { + "\n" + ""), HTML_SERVLET_OUTPUT_STREAM( - "HTML_SERVLET_OUTPUT_STREAM", + "htmlServletOutputStream", 200, "\n" + "\n" From b46cbff348c00e41d077eebf71e558fc4d1589f3 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 25 Feb 2023 11:34:30 -0800 Subject: [PATCH 22/37] Some refactoring of the unit tests --- ...utputStreamSnippetInjectionHelperTest.java | 170 ----------------- .../SnippetInjectingResponseWrapperTest.java | 173 ------------------ .../v3_0/snippet/SnippetPrintWriterTest.java | 158 ++++++++++++++++ .../SnippetServletOutputStreamTest.java | 139 ++++++++++++++ .../servlet/v3_0/snippet/TestUtil.java | 9 +- 5 files changed, 303 insertions(+), 346 deletions(-) delete mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelperTest.java delete mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetPrintWriterTest.java create mode 100644 instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelperTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelperTest.java deleted file mode 100644 index bb719deb43bd..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelperTest.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; - -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFile; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import javax.servlet.ServletOutputStream; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -class OutputStreamSnippetInjectionHelperTest { - - @Test - void testInjectionForStringContainHeadTag() throws IOException { - String testSnippet = "\n "; - String original = readFile("beforeSnippetInjection.html"); - String correct = readFile("afterSnippetInjection.html"); - byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); - SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); - when(response.isCommitted()).thenReturn(false); - when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); - InjectionState obj = new InjectionState(response); - - StringWriter writer = new StringWriter(); - - ServletOutputStream sp = - new ServletOutputStream() { - @Override - public void write(int b) throws IOException { - writer.write(b); - } - }; - OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); - boolean injected = helper.handleWrite(obj, sp, originalBytes, 0, originalBytes.length); - assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); - assertThat(injected).isEqualTo(true); - writer.flush(); - - String result = writer.toString(); - writer.close(); - assertThat(result).isEqualTo(correct); - } - - @Test - @Disabled - void testInjectionForChinese() throws IOException { - String testSnippet = "\n "; - String original = readFile("beforeSnippetInjectionChinese.html"); - String correct = readFile("afterSnippetInjectionChinese.html"); - byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); - - SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); - when(response.isCommitted()).thenReturn(false); - when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); - InjectionState obj = new InjectionState(response); - - StringWriter writer = new StringWriter(); - - ServletOutputStream sp = - new ServletOutputStream() { - @Override - public void write(int b) throws IOException { - writer.write(b); - } - }; - OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); - boolean injected = helper.handleWrite(obj, sp, originalBytes, 0, originalBytes.length); - assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); - assertThat(injected).isEqualTo(true); - writer.flush(); - - String result = writer.toString(); - writer.close(); - assertThat(result).isEqualTo(correct); - } - - @Test - void testInjectionForStringWithoutHeadTag() throws IOException { - String testSnippet = "\n "; - String original = readFile("htmlWithoutHeadTag.html"); - byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8); - - SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class); - when(response.isCommitted()).thenReturn(false); - when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name()); - InjectionState obj = new InjectionState(response); - StringWriter writer = new StringWriter(); - - ServletOutputStream sp = - new ServletOutputStream() { - @Override - public void write(int b) throws IOException { - writer.write(b); - } - }; - OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(testSnippet); - boolean injected = helper.handleWrite(obj, sp, originalBytes, 0, originalBytes.length); - assertThat(obj.getHeadTagBytesSeen()).isEqualTo(0); - assertThat(injected).isEqualTo(false); - writer.flush(); - String result = writer.toString(); - writer.close(); - assertThat(result).isEqualTo(""); - } - - @Test - void testHeadTagSplitAcrossTwoWrites() throws IOException { - String testSnippet = "\n "; - String originalFirstPart = "\n" + "\n" + "\n" - + " \n" - + " Title\n" - + "\n" - + "\n" - + "\n" - + "\n" - + ""; - byte[] originalSecondPartBytes = originalSecondPart.getBytes(StandardCharsets.UTF_8); - injected = - helper.handleWrite(obj, sp, originalSecondPartBytes, 0, originalSecondPartBytes.length); - assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); - assertThat(injected).isEqualTo(true); - String correctSecondPart = - "ad>\n" - + " \n" - + " \n" - + " Title\n" - + "\n" - + "\n" - + "\n" - + "\n" - + ""; - writer.flush(); - result = writer.toString(); - assertThat(result).isEqualTo(correctSecondPart); - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java deleted file mode 100644 index 437a446cf7d1..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapperTest.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; - -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFile; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.charset.Charset; -import javax.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -class SnippetInjectingResponseWrapperTest { - - @Test - void testInjectToTextHtml() throws IOException { - - String original = readFile("beforeSnippetInjection.html"); - String correct = readFile("afterSnippetInjection.html"); - HttpServletResponse response = mock(HttpServletResponse.class); - when(response.getContentType()).thenReturn("text/html"); - when(response.getStatus()).thenReturn(200); - when(response.containsHeader("content-type")).thenReturn(true); - StringWriter writer = new StringWriter(); - when(response.getWriter()).thenReturn(new PrintWriter(writer)); - String testSnippet = "\n "; - SnippetInjectingResponseWrapper responseWrapper = - new SnippetInjectingResponseWrapper(response, testSnippet); - responseWrapper.getWriter().write(original); - responseWrapper.getWriter().flush(); - responseWrapper.getWriter().close(); - - String result = writer.toString(); - writer.close(); - assertThat(result).isEqualTo(correct); - } - - @Test - @Disabled - void testInjectToChineseTextHtml() throws IOException { - - String original = readFile("beforeSnippetInjectionChinese.html"); - String correct = readFile("afterSnippetInjectionChinese.html"); - HttpServletResponse response = mock(HttpServletResponse.class); - when(response.getContentType()).thenReturn("text/html"); - - StringWriter writer = new StringWriter(); - when(response.getWriter()).thenReturn(new PrintWriter(writer)); - String testSnippet = "\n "; - SnippetInjectingResponseWrapper responseWrapper = - new SnippetInjectingResponseWrapper(response, testSnippet); - responseWrapper.getWriter().write(original); - responseWrapper.getWriter().flush(); - responseWrapper.getWriter().close(); - - String result = writer.toString(); - writer.close(); - assertThat(result).isEqualTo(correct); - } - - @Test - void shouldNotInjectToTextHtml() throws IOException { - - String original = readFile("beforeSnippetInjection.html"); - - StringWriter writer = new StringWriter(); - HttpServletResponse response = mock(HttpServletResponse.class); - when(response.getContentType()).thenReturn("not/text"); - when(response.getStatus()).thenReturn(200); - when(response.containsHeader("content-type")).thenReturn(true); - - when(response.getWriter()).thenReturn(new PrintWriter(writer, true)); - - String testSnippet = "\n "; - SnippetInjectingResponseWrapper responseWrapper = - new SnippetInjectingResponseWrapper(response, testSnippet); - responseWrapper.getWriter().write(original); - responseWrapper.getWriter().flush(); - responseWrapper.getWriter().close(); - - String result = writer.toString(); - writer.close(); - assertThat(result).isEqualTo(original); - } - - @Test - void testWriteInt() throws IOException { - - String original = readFile("beforeSnippetInjection.html"); - String correct = readFile("afterSnippetInjection.html"); - HttpServletResponse response = mock(HttpServletResponse.class); - when(response.getContentType()).thenReturn("text/html"); - - StringWriter writer = new StringWriter(); - when(response.getWriter()).thenReturn(new PrintWriter(writer)); - String testSnippet = "\n "; - SnippetInjectingResponseWrapper responseWrapper = - new SnippetInjectingResponseWrapper(response, testSnippet); - byte[] originalBytes = original.getBytes(Charset.defaultCharset()); - for (byte originalByte : originalBytes) { - responseWrapper.getWriter().write(originalByte); - } - - responseWrapper.getWriter().flush(); - responseWrapper.getWriter().close(); - - String result = writer.toString(); - writer.close(); - assertThat(result).isEqualTo(correct); - } - - @Test - void testWriteCharArray() throws IOException { - - String original = readFile("beforeSnippetInjectionChinese.html"); - String correct = readFile("afterSnippetInjectionChinese.html"); - HttpServletResponse response = mock(HttpServletResponse.class); - when(response.getContentType()).thenReturn("text/html"); - when(response.getStatus()).thenReturn(200); - when(response.containsHeader("content-type")).thenReturn(true); - - StringWriter writer = new StringWriter(); - when(response.getWriter()).thenReturn(new PrintWriter(writer)); - String testSnippet = "\n "; - SnippetInjectingResponseWrapper responseWrapper = - new SnippetInjectingResponseWrapper(response, testSnippet); - char[] originalChars = original.toCharArray(); - responseWrapper.getWriter().write(originalChars, 0, originalChars.length); - responseWrapper.getWriter().flush(); - responseWrapper.getWriter().close(); - - String result = writer.toString(); - writer.close(); - assertThat(result).isEqualTo(correct); - } - - @Test - void testWriteWithOffset() throws IOException { - - String original = readFile("beforeSnippetInjectionChinese.html"); - String correct = readFile("afterSnippetInjectionChinese.html"); - String extraBuffer = "this buffer should not be print out"; - original = extraBuffer + original; - HttpServletResponse response = mock(HttpServletResponse.class); - when(response.getContentType()).thenReturn("text/html"); - when(response.getStatus()).thenReturn(200); - when(response.containsHeader("content-type")).thenReturn(true); - - StringWriter writer = new StringWriter(); - when(response.getWriter()).thenReturn(new PrintWriter(writer)); - String testSnippet = "\n "; - SnippetInjectingResponseWrapper responseWrapper = - new SnippetInjectingResponseWrapper(response, testSnippet); - - responseWrapper - .getWriter() - .write(original, extraBuffer.length(), original.length() - extraBuffer.length()); - responseWrapper.getWriter().flush(); - responseWrapper.getWriter().close(); - - String result = writer.toString(); - writer.close(); - assertThat(result).isEqualTo(correct); - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetPrintWriterTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetPrintWriterTest.java new file mode 100644 index 000000000000..5ceb9f19644c --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetPrintWriterTest.java @@ -0,0 +1,158 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFile; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.Charset; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import org.junit.jupiter.api.Test; + +class SnippetPrintWriterTest { + + @Test + void testInjectToTextHtml() throws IOException { + String snippet = "\n "; + String html = readFile("beforeSnippetInjection.html"); + + InMemoryHttpServletResponse response = createInMemoryHttpServletResponse("text/html"); + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, snippet); + + responseWrapper.getWriter().write(html); + responseWrapper.getWriter().flush(); + + String expectedHtml = readFile("afterSnippetInjection.html"); + assertThat(response.getStringContent()).isEqualTo(expectedHtml); + } + + @Test + void testInjectToChineseTextHtml() throws IOException { + String snippet = "\n "; + String html = readFile("beforeSnippetInjectionChinese.html"); + + InMemoryHttpServletResponse response = createInMemoryHttpServletResponse("text/html"); + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, snippet); + + responseWrapper.getWriter().write(html); + responseWrapper.getWriter().flush(); + + String expectedHtml = readFile("afterSnippetInjectionChinese.html"); + assertThat(response.getStringContent()).isEqualTo(expectedHtml); + } + + @Test + void shouldNotInjectToTextHtml() throws IOException { + String snippet = "\n "; + String html = readFile("beforeSnippetInjection.html"); + + InMemoryHttpServletResponse response = createInMemoryHttpServletResponse("not/text"); + + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, snippet); + + responseWrapper.getWriter().write(html); + responseWrapper.getWriter().flush(); + + assertThat(response.getStringContent()).isEqualTo(html); + } + + @Test + void testWriteInt() throws IOException { + String snippet = "\n "; + String html = readFile("beforeSnippetInjection.html"); + + InMemoryHttpServletResponse response = createInMemoryHttpServletResponse("text/html"); + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, snippet); + + byte[] originalBytes = html.getBytes(Charset.defaultCharset()); + for (byte originalByte : originalBytes) { + responseWrapper.getWriter().write(originalByte); + } + responseWrapper.getWriter().flush(); + + String expectedHtml = readFile("afterSnippetInjection.html"); + assertThat(response.getStringContent()).isEqualTo(expectedHtml); + } + + @Test + void testWriteCharArray() throws IOException { + String snippet = "\n "; + String html = readFile("beforeSnippetInjectionChinese.html"); + + InMemoryHttpServletResponse response = createInMemoryHttpServletResponse("text/html"); + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, snippet); + + char[] originalChars = html.toCharArray(); + responseWrapper.getWriter().write(originalChars, 0, originalChars.length); + responseWrapper.getWriter().flush(); + + String expectedHtml = readFile("afterSnippetInjectionChinese.html"); + assertThat(response.getStringContent()).isEqualTo(expectedHtml); + } + + @Test + void testWriteWithOffset() throws IOException { + String snippet = "\n "; + String html = readFile("beforeSnippetInjectionChinese.html"); + String extraBuffer = "this buffer should not be print out"; + html = extraBuffer + html; + + InMemoryHttpServletResponse response = createInMemoryHttpServletResponse("text/html"); + SnippetInjectingResponseWrapper responseWrapper = + new SnippetInjectingResponseWrapper(response, snippet); + + responseWrapper + .getWriter() + .write(html, extraBuffer.length(), html.length() - extraBuffer.length()); + responseWrapper.getWriter().flush(); + + String expectedHtml = readFile("afterSnippetInjectionChinese.html"); + assertThat(response.getStringContent()).isEqualTo(expectedHtml); + } + + private static InMemoryHttpServletResponse createInMemoryHttpServletResponse(String contentType) { + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getContentType()).thenReturn(contentType); + when(response.getStatus()).thenReturn(200); + when(response.containsHeader("content-type")).thenReturn(true); + return new InMemoryHttpServletResponse(response); + } + + private static class InMemoryHttpServletResponse extends HttpServletResponseWrapper { + + private PrintWriter printWriter; + private StringWriter stringWriter; + + InMemoryHttpServletResponse(HttpServletResponse delegate) { + super(delegate); + } + + @Override + public PrintWriter getWriter() { + if (printWriter == null) { + stringWriter = new StringWriter(); + printWriter = new PrintWriter(stringWriter); + } + return printWriter; + } + + String getStringContent() { + printWriter.flush(); + return stringWriter.toString(); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java new file mode 100644 index 000000000000..0f6e85d5fcfe --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java @@ -0,0 +1,139 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; + +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFileBytes; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Test; + +class SnippetServletOutputStreamTest { + + @Test + void testInjectionForStringContainHeadTag() throws IOException { + String snippet = "\n "; + byte[] html = readFileBytes("beforeSnippetInjection.html", UTF_8); + + InjectionState obj = createInjectionStateForTesting(snippet, UTF_8); + InMemoryServletOutputStream out = new InMemoryServletOutputStream(); + + OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(snippet); + boolean injected = helper.handleWrite(obj, out, html, 0, html.length); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); + assertThat(injected).isEqualTo(true); + + byte[] expectedHtml = readFileBytes("afterSnippetInjection.html", UTF_8); + assertThat(out.getBytes()).isEqualTo(expectedHtml); + } + + @Test + void testInjectionForChinese() throws IOException { + String snippet = "\n "; + byte[] html = readFileBytes("beforeSnippetInjectionChinese.html", UTF_8); + + InjectionState obj = createInjectionStateForTesting(snippet, UTF_8); + InMemoryServletOutputStream out = new InMemoryServletOutputStream(); + + OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(snippet); + boolean injected = helper.handleWrite(obj, out, html, 0, html.length); + + byte[] expectedHtml = readFileBytes("afterSnippetInjectionChinese.html", UTF_8); + assertThat(injected).isTrue(); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); + assertThat(out.getBytes()).isEqualTo(expectedHtml); + } + + @Test + void testInjectionForStringWithoutHeadTag() throws IOException { + String snippet = "\n "; + byte[] html = readFileBytes("htmlWithoutHeadTag.html", UTF_8); + + InjectionState obj = createInjectionStateForTesting(snippet, UTF_8); + InMemoryServletOutputStream out = new InMemoryServletOutputStream(); + + OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(snippet); + boolean injected = helper.handleWrite(obj, out, html, 0, html.length); + + assertThat(injected).isFalse(); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(0); + assertThat(out.getBytes()).isEmpty(); + } + + @Test + void testHeadTagSplitAcrossTwoWrites() throws IOException { + String snippet = "\n "; + String htmlFirstPart = "\n\n\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + byte[] htmlSecondPartBytes = htmlSecondPart.getBytes(UTF_8); + injected = helper.handleWrite(obj, out, htmlSecondPartBytes, 0, htmlSecondPartBytes.length); + + assertThat(injected).isTrue(); + assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); + + String expectedSecondPart = + "ad>\n" + + " \n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + assertThat(out.getBytes()).isEqualTo(expectedSecondPart.getBytes(UTF_8)); + } + + private static InjectionState createInjectionStateForTesting(String snippet, Charset charset) { + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.isCommitted()).thenReturn(false); + when(response.getCharacterEncoding()).thenReturn(charset.name()); + + return new InjectionState(new SnippetInjectingResponseWrapper(response, snippet)); + } + + private static class InMemoryServletOutputStream extends ServletOutputStream { + + private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + @Override + public void write(int b) { + baos.write(b); + } + + public byte[] getBytes() { + return baos.toByteArray(); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java index fb5fec9fcb6e..bf194e779f5e 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java @@ -8,15 +8,18 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; public class TestUtil { + public static byte[] readFileBytes(String resourceName, Charset charsetName) throws IOException { + return readFile(resourceName).getBytes(charsetName); + } + public static String readFile(String resourceName) throws IOException { InputStream in = - SnippetInjectingResponseWrapperTest.class - .getClassLoader() - .getResourceAsStream(resourceName); + SnippetPrintWriterTest.class.getClassLoader().getResourceAsStream(resourceName); ByteArrayOutputStream result = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length; From db6dcaa07b024f2af5039fcc54a80175cf496964 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 27 Feb 2023 11:43:22 -0800 Subject: [PATCH 23/37] Update instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy --- .../javaagent/src/test/groovy/AbstractServlet3Test.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy index aadfefed9952..b0f36ccf067b 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy @@ -101,7 +101,7 @@ abstract class AbstractServlet3Test extends HttpServerTest Test ") def request = request(HTML_SERVLET_OUTPUT_STREAM, "GET") From 0aa85b0250e13927f3ff248b952dd8046d194dd0 Mon Sep 17 00:00:00 2001 From: siyuniu-ms <123212536+siyuniu-ms@users.noreply.github.com> Date: Mon, 27 Feb 2023 12:33:01 -0800 Subject: [PATCH 24/37] Update instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java Co-authored-by: Trask Stalnaker --- .../instrumentation/servlet/v3_0/snippet/InjectionState.java | 1 + 1 file changed, 1 insertion(+) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java index d5dbe07830ab..62b661d80a92 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; +// this is shared by both ServletOutputStream and PrintWriter injection public class InjectionState { private static final int HEAD_TAG_WRITTEN_FAKE_VALUE = -1; private static final int HEAD_TAG_LENGTH = "".length(); From 175c2f4421fadcadfff5c435710a78eb9973bb21 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Mon, 27 Feb 2023 13:48:38 -0800 Subject: [PATCH 25/37] change naming and util test --- .../servlet/v3_0/snippet/TestUtil.java | 13 +++++++------ .../servlet/v3_0/Servlet3Advice.java | 4 ++-- .../v3_0/Servlet3OutputStreamWriteBytesAdvice.java | 4 ++-- ...rvlet3OutputStreamWriteBytesAndOffsetAdvice.java | 4 ++-- .../v3_0/Servlet3OutputStreamWriteIntAdvice.java | 4 ++-- ....java => ServletOutputStreamInjectionState.java} | 4 ++-- .../snippet/SnippetInjectingResponseWrapper.java | 2 +- .../service/ServletOutputStreamInstrumentation.java | 3 +-- 8 files changed, 19 insertions(+), 19 deletions(-) rename instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/{Injection.java => ServletOutputStreamInjectionState.java} (91%) diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java index bf194e779f5e..2eb691e7276e 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java @@ -5,19 +5,16 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; public class TestUtil { public static byte[] readFileBytes(String resourceName, Charset charsetName) throws IOException { - return readFile(resourceName).getBytes(charsetName); - } - - public static String readFile(String resourceName) throws IOException { InputStream in = SnippetPrintWriterTest.class.getClassLoader().getResourceAsStream(resourceName); ByteArrayOutputStream result = new ByteArrayOutputStream(); @@ -26,7 +23,11 @@ public static String readFile(String resourceName) throws IOException { while ((length = in.read(buffer)) != -1) { result.write(buffer, 0, length); } - return result.toString(StandardCharsets.UTF_8.name()); + return result.toByteArray(); + } + + public static String readFile(String resourceName) throws IOException { + return new String(readFileBytes(resourceName, UTF_8), UTF_8); } private TestUtil() {} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java index a28e7b58d169..11a238eae558 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Advice.java @@ -6,7 +6,6 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.helper; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.SnippetInjectingResponseWrapper.FAKE_SNIPPET_HEADER; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; @@ -45,7 +44,8 @@ public static void onEnter( String snippet = ExperimentalSnippetHolder.getSnippet(); if (!snippet.isEmpty() - && !((HttpServletResponse) response).containsHeader(FAKE_SNIPPET_HEADER)) { + && !((HttpServletResponse) response) + .containsHeader(SnippetInjectingResponseWrapper.FAKE_SNIPPET_HEADER)) { response = new SnippetInjectingResponseWrapper((HttpServletResponse) response, snippet); } callDepth = CallDepth.forClass(AppServerBridge.getCallDepthKey()); diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java index d17e8e551224..8cf67e5166ae 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAdvice.java @@ -6,9 +6,9 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionState; import java.io.IOException; import javax.servlet.ServletOutputStream; import net.bytebuddy.asm.Advice; @@ -19,7 +19,7 @@ public class Servlet3OutputStreamWriteBytesAdvice { public static boolean methodEnter( @Advice.This ServletOutputStream servletOutputStream, @Advice.Argument(0) byte[] write) throws IOException { - InjectionState state = getInjectionState(servletOutputStream); + InjectionState state = ServletOutputStreamInjectionState.getInjectionState(servletOutputStream); if (state == null) { return true; } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java index e2c0a1823f42..563693e795ad 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteBytesAndOffsetAdvice.java @@ -6,9 +6,9 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionState; import java.io.IOException; import javax.servlet.ServletOutputStream; import net.bytebuddy.asm.Advice; @@ -21,7 +21,7 @@ public static boolean methodEnter( @Advice.Argument(value = 1) int off, @Advice.Argument(value = 2) int len) throws IOException { - InjectionState state = getInjectionState(servletOutputStream); + InjectionState state = ServletOutputStreamInjectionState.getInjectionState(servletOutputStream); if (state == null) { return true; } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java index d78696517077..a1e140376998 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3OutputStreamWriteIntAdvice.java @@ -6,9 +6,9 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.Servlet3Singletons.getSnippetInjectionHelper; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.getInjectionState; import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.InjectionState; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionState; import java.io.IOException; import javax.servlet.ServletOutputStream; import net.bytebuddy.asm.Advice; @@ -19,7 +19,7 @@ public class Servlet3OutputStreamWriteIntAdvice { public static boolean methodEnter( @Advice.This ServletOutputStream servletOutputStream, @Advice.Argument(0) int write) throws IOException { - InjectionState state = getInjectionState(servletOutputStream); + InjectionState state = ServletOutputStreamInjectionState.getInjectionState(servletOutputStream); if (state == null) { return true; } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Injection.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionState.java similarity index 91% rename from instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Injection.java rename to instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionState.java index 2ee7578f2600..d1d01f9f81f3 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Injection.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionState.java @@ -8,7 +8,7 @@ import io.opentelemetry.instrumentation.api.util.VirtualField; import javax.servlet.ServletOutputStream; -public class Injection { +public class ServletOutputStreamInjectionState { private static final VirtualField virtualField = VirtualField.find(ServletOutputStream.class, InjectionState.class); @@ -30,5 +30,5 @@ public static InjectionState getInjectionState(ServletOutputStream servletOutput return virtualField.get(servletOutputStream); } - private Injection() {} + private ServletOutputStreamInjectionState() {} } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 860c81c0141d..2b2499a7cbeb 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Injection.initializeInjectionStateIfNeeded; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.ServletOutputStreamInjectionState.initializeInjectionStateIfNeeded; import static java.util.logging.Level.FINE; import java.io.IOException; diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java index a545f3874b7a..631d4f372770 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/common/service/ServletOutputStreamInstrumentation.java @@ -9,7 +9,6 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; @@ -42,7 +41,7 @@ public ElementMatcher classLoaderOptimization() { @Override public ElementMatcher typeMatcher() { - return hasSuperType(namedOneOf(basePackageName + ".ServletOutputStream")); + return hasSuperType(named(basePackageName + ".ServletOutputStream")); } @Override From df436752276ded3ba58df4393486fe88a0810935 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Mon, 27 Feb 2023 16:29:18 -0800 Subject: [PATCH 26/37] optimization on PrintWriter --- .../v3_0/snippet/SnippetServletOutputStreamTest.java | 10 +++++----- .../servlet/v3_0/snippet/TestUtil.java | 5 ++--- .../snippet/OutputStreamSnippetInjectionHelper.java | 6 ++++-- .../v3_0/snippet/SnippetInjectingPrintWriter.java | 11 ++++++++++- .../v3_0/snippet/SnippetInjectingResponseWrapper.java | 2 +- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java index 0f6e85d5fcfe..9fc9ed5b1124 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java @@ -23,7 +23,7 @@ class SnippetServletOutputStreamTest { @Test void testInjectionForStringContainHeadTag() throws IOException { String snippet = "\n "; - byte[] html = readFileBytes("beforeSnippetInjection.html", UTF_8); + byte[] html = readFileBytes("beforeSnippetInjection.html"); InjectionState obj = createInjectionStateForTesting(snippet, UTF_8); InMemoryServletOutputStream out = new InMemoryServletOutputStream(); @@ -33,14 +33,14 @@ void testInjectionForStringContainHeadTag() throws IOException { assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(injected).isEqualTo(true); - byte[] expectedHtml = readFileBytes("afterSnippetInjection.html", UTF_8); + byte[] expectedHtml = readFileBytes("afterSnippetInjection.html"); assertThat(out.getBytes()).isEqualTo(expectedHtml); } @Test void testInjectionForChinese() throws IOException { String snippet = "\n "; - byte[] html = readFileBytes("beforeSnippetInjectionChinese.html", UTF_8); + byte[] html = readFileBytes("beforeSnippetInjectionChinese.html"); InjectionState obj = createInjectionStateForTesting(snippet, UTF_8); InMemoryServletOutputStream out = new InMemoryServletOutputStream(); @@ -48,7 +48,7 @@ void testInjectionForChinese() throws IOException { OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(snippet); boolean injected = helper.handleWrite(obj, out, html, 0, html.length); - byte[] expectedHtml = readFileBytes("afterSnippetInjectionChinese.html", UTF_8); + byte[] expectedHtml = readFileBytes("afterSnippetInjectionChinese.html"); assertThat(injected).isTrue(); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(out.getBytes()).isEqualTo(expectedHtml); @@ -57,7 +57,7 @@ void testInjectionForChinese() throws IOException { @Test void testInjectionForStringWithoutHeadTag() throws IOException { String snippet = "\n "; - byte[] html = readFileBytes("htmlWithoutHeadTag.html", UTF_8); + byte[] html = readFileBytes("htmlWithoutHeadTag.html"); InjectionState obj = createInjectionStateForTesting(snippet, UTF_8); InMemoryServletOutputStream out = new InMemoryServletOutputStream(); diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java index 2eb691e7276e..f99c288b2e55 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java @@ -10,11 +10,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.Charset; public class TestUtil { - public static byte[] readFileBytes(String resourceName, Charset charsetName) throws IOException { + public static byte[] readFileBytes(String resourceName) throws IOException { InputStream in = SnippetPrintWriterTest.class.getClassLoader().getResourceAsStream(resourceName); ByteArrayOutputStream result = new ByteArrayOutputStream(); @@ -27,7 +26,7 @@ public static byte[] readFileBytes(String resourceName, Charset charsetName) thr } public static String readFile(String resourceName) throws IOException { - return new String(readFileBytes(resourceName, UTF_8), UTF_8); + return new String(readFileBytes(resourceName), UTF_8); } private TestUtil() {} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java index e88cb15ab9b9..7192c6d30061 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java @@ -45,7 +45,8 @@ public boolean handleWrite( if (!endOfHeadTagFound) { return false; } - state.setHeadTagWritten(); // set before write to avoid recursive loop + // set before write to avoid recursive loop + state.setHeadTagWritten(); if (state.getWrapper().isNotSafeToInject()) { return false; } @@ -71,7 +72,8 @@ public boolean handleWrite(InjectionState state, OutputStream out, int b) throws if (!state.processByte(b)) { return false; } - state.setHeadTagWritten(); // set before write to avoid recursive loop + // set before write to avoid recursive loop + state.setHeadTagWritten(); if (state.getWrapper().isNotSafeToInject()) { return false; diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java index 88e3a02a21b0..7388c25c3722 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java @@ -20,6 +20,10 @@ public SnippetInjectingPrintWriter( @Override public void write(String s, int off, int len) { + if (state.isHeadTagWritten()) { + super.write(s, off, len); + return; + } for (int i = off; i < s.length() && i - off < len; i++) { write(s.charAt(i)); } @@ -35,7 +39,8 @@ public void write(int b) { if (!endOfHeadTagFound) { return; } - state.setHeadTagWritten(); // set before write to avoid recursive loop + // set before write to avoid recursive loop + state.setHeadTagWritten(); if (state.getWrapper().isNotSafeToInject()) { return; } @@ -45,6 +50,10 @@ public void write(int b) { @Override public void write(char[] buf, int off, int len) { + if (state.isHeadTagWritten()) { + super.write(buf, off, len); + return; + } for (int i = off; i < buf.length && i - off < len; i++) { write(buf[i]); } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 2b2499a7cbeb..00edfa3521f2 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -180,6 +180,6 @@ public boolean isNotSafeToInject() { // if content-length was set and response was already committed (headers sent to the client), // then it is not safe to inject because the content-length header cannot be updated to account // for the snippet length - return isCommitted() && (contentLength != UNSET); + return contentLength != UNSET && isCommitted(); } } From 4a35251cc09145609b79df5894d2e240a1b72d79 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Tue, 4 Apr 2023 18:12:56 -0700 Subject: [PATCH 27/37] change based on comments --- .../v3_0/snippet/SnippetPrintWriterTest.java | 24 +++++++++---------- .../SnippetServletOutputStreamTest.java | 12 +++++----- .../servlet/v3_0/snippet/TestUtil.java | 6 ++--- .../servlet/v3_0/snippet/InjectionState.java | 9 +++++-- .../OutputStreamSnippetInjectionHelper.java | 17 +++++++------ .../ServletOutputStreamInjectionState.java | 2 ++ .../snippet/SnippetInjectingPrintWriter.java | 3 +-- .../SnippetInjectingResponseWrapper.java | 11 +++++---- .../src/test/groovy/TestServlet3.groovy | 3 ++- .../servlet/ExperimentalSnippetHolder.java | 6 +++++ 10 files changed, 53 insertions(+), 40 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetPrintWriterTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetPrintWriterTest.java index 5ceb9f19644c..eedc842a5792 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetPrintWriterTest.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetPrintWriterTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFile; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFileAsString; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -23,7 +23,7 @@ class SnippetPrintWriterTest { @Test void testInjectToTextHtml() throws IOException { String snippet = "\n "; - String html = readFile("beforeSnippetInjection.html"); + String html = readFileAsString("beforeSnippetInjection.html"); InMemoryHttpServletResponse response = createInMemoryHttpServletResponse("text/html"); SnippetInjectingResponseWrapper responseWrapper = @@ -32,14 +32,14 @@ void testInjectToTextHtml() throws IOException { responseWrapper.getWriter().write(html); responseWrapper.getWriter().flush(); - String expectedHtml = readFile("afterSnippetInjection.html"); + String expectedHtml = readFileAsString("afterSnippetInjection.html"); assertThat(response.getStringContent()).isEqualTo(expectedHtml); } @Test void testInjectToChineseTextHtml() throws IOException { String snippet = "\n "; - String html = readFile("beforeSnippetInjectionChinese.html"); + String html = readFileAsString("beforeSnippetInjectionChinese.html"); InMemoryHttpServletResponse response = createInMemoryHttpServletResponse("text/html"); SnippetInjectingResponseWrapper responseWrapper = @@ -48,14 +48,14 @@ void testInjectToChineseTextHtml() throws IOException { responseWrapper.getWriter().write(html); responseWrapper.getWriter().flush(); - String expectedHtml = readFile("afterSnippetInjectionChinese.html"); + String expectedHtml = readFileAsString("afterSnippetInjectionChinese.html"); assertThat(response.getStringContent()).isEqualTo(expectedHtml); } @Test void shouldNotInjectToTextHtml() throws IOException { String snippet = "\n "; - String html = readFile("beforeSnippetInjection.html"); + String html = readFileAsString("beforeSnippetInjection.html"); InMemoryHttpServletResponse response = createInMemoryHttpServletResponse("not/text"); @@ -71,7 +71,7 @@ void shouldNotInjectToTextHtml() throws IOException { @Test void testWriteInt() throws IOException { String snippet = "\n "; - String html = readFile("beforeSnippetInjection.html"); + String html = readFileAsString("beforeSnippetInjection.html"); InMemoryHttpServletResponse response = createInMemoryHttpServletResponse("text/html"); SnippetInjectingResponseWrapper responseWrapper = @@ -83,14 +83,14 @@ void testWriteInt() throws IOException { } responseWrapper.getWriter().flush(); - String expectedHtml = readFile("afterSnippetInjection.html"); + String expectedHtml = readFileAsString("afterSnippetInjection.html"); assertThat(response.getStringContent()).isEqualTo(expectedHtml); } @Test void testWriteCharArray() throws IOException { String snippet = "\n "; - String html = readFile("beforeSnippetInjectionChinese.html"); + String html = readFileAsString("beforeSnippetInjectionChinese.html"); InMemoryHttpServletResponse response = createInMemoryHttpServletResponse("text/html"); SnippetInjectingResponseWrapper responseWrapper = @@ -100,14 +100,14 @@ void testWriteCharArray() throws IOException { responseWrapper.getWriter().write(originalChars, 0, originalChars.length); responseWrapper.getWriter().flush(); - String expectedHtml = readFile("afterSnippetInjectionChinese.html"); + String expectedHtml = readFileAsString("afterSnippetInjectionChinese.html"); assertThat(response.getStringContent()).isEqualTo(expectedHtml); } @Test void testWriteWithOffset() throws IOException { String snippet = "\n "; - String html = readFile("beforeSnippetInjectionChinese.html"); + String html = readFileAsString("beforeSnippetInjectionChinese.html"); String extraBuffer = "this buffer should not be print out"; html = extraBuffer + html; @@ -120,7 +120,7 @@ void testWriteWithOffset() throws IOException { .write(html, extraBuffer.length(), html.length() - extraBuffer.length()); responseWrapper.getWriter().flush(); - String expectedHtml = readFile("afterSnippetInjectionChinese.html"); + String expectedHtml = readFileAsString("afterSnippetInjectionChinese.html"); assertThat(response.getStringContent()).isEqualTo(expectedHtml); } diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java index 9fc9ed5b1124..46018baba9eb 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetServletOutputStreamTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; -import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFileBytes; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFileAsBytes; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.Mockito.mock; @@ -23,7 +23,7 @@ class SnippetServletOutputStreamTest { @Test void testInjectionForStringContainHeadTag() throws IOException { String snippet = "\n "; - byte[] html = readFileBytes("beforeSnippetInjection.html"); + byte[] html = readFileAsBytes("beforeSnippetInjection.html"); InjectionState obj = createInjectionStateForTesting(snippet, UTF_8); InMemoryServletOutputStream out = new InMemoryServletOutputStream(); @@ -33,14 +33,14 @@ void testInjectionForStringContainHeadTag() throws IOException { assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(injected).isEqualTo(true); - byte[] expectedHtml = readFileBytes("afterSnippetInjection.html"); + byte[] expectedHtml = readFileAsBytes("afterSnippetInjection.html"); assertThat(out.getBytes()).isEqualTo(expectedHtml); } @Test void testInjectionForChinese() throws IOException { String snippet = "\n "; - byte[] html = readFileBytes("beforeSnippetInjectionChinese.html"); + byte[] html = readFileAsBytes("beforeSnippetInjectionChinese.html"); InjectionState obj = createInjectionStateForTesting(snippet, UTF_8); InMemoryServletOutputStream out = new InMemoryServletOutputStream(); @@ -48,7 +48,7 @@ void testInjectionForChinese() throws IOException { OutputStreamSnippetInjectionHelper helper = new OutputStreamSnippetInjectionHelper(snippet); boolean injected = helper.handleWrite(obj, out, html, 0, html.length); - byte[] expectedHtml = readFileBytes("afterSnippetInjectionChinese.html"); + byte[] expectedHtml = readFileAsBytes("afterSnippetInjectionChinese.html"); assertThat(injected).isTrue(); assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1); assertThat(out.getBytes()).isEqualTo(expectedHtml); @@ -57,7 +57,7 @@ void testInjectionForChinese() throws IOException { @Test void testInjectionForStringWithoutHeadTag() throws IOException { String snippet = "\n "; - byte[] html = readFileBytes("htmlWithoutHeadTag.html"); + byte[] html = readFileAsBytes("htmlWithoutHeadTag.html"); InjectionState obj = createInjectionStateForTesting(snippet, UTF_8); InMemoryServletOutputStream out = new InMemoryServletOutputStream(); diff --git a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java index f99c288b2e55..9095afd2d3da 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java +++ b/instrumentation/servlet/servlet-3.0/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/TestUtil.java @@ -13,7 +13,7 @@ public class TestUtil { - public static byte[] readFileBytes(String resourceName) throws IOException { + protected static byte[] readFileAsBytes(String resourceName) throws IOException { InputStream in = SnippetPrintWriterTest.class.getClassLoader().getResourceAsStream(resourceName); ByteArrayOutputStream result = new ByteArrayOutputStream(); @@ -25,8 +25,8 @@ public static byte[] readFileBytes(String resourceName) throws IOException { return result.toByteArray(); } - public static String readFile(String resourceName) throws IOException { - return new String(readFileBytes(resourceName), UTF_8); + protected static String readFileAsString(String resourceName) throws IOException { + return new String(readFileAsBytes(resourceName), UTF_8); } private TestUtil() {} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java index 62b661d80a92..a535d81fd152 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/InjectionState.java @@ -24,7 +24,7 @@ public String getCharacterEncoding() { return wrapper.getCharacterEncoding(); } - public void setHeadTagWritten() { + private void setHeadTagWritten() { headTagBytesSeen = HEAD_TAG_WRITTEN_FAKE_VALUE; } @@ -45,7 +45,12 @@ public boolean processByte(int b) { } else { headTagBytesSeen = 0; } - return headTagBytesSeen == HEAD_TAG_LENGTH; + if (headTagBytesSeen == HEAD_TAG_LENGTH) { + setHeadTagWritten(); + return true; + } else { + return false; + } } private boolean inHeadTag(int b) { diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java index 7192c6d30061..c75e2e28ee34 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/OutputStreamSnippetInjectionHelper.java @@ -34,10 +34,12 @@ public boolean handleWrite( if (state.isHeadTagWritten()) { return false; } - int i; + int endOfHeadTagPosition; boolean endOfHeadTagFound = false; - for (i = off; i < length && i - off < length; i++) { - if (state.processByte(original[i])) { + for (endOfHeadTagPosition = off; + endOfHeadTagPosition < length && endOfHeadTagPosition - off < length; + endOfHeadTagPosition++) { + if (state.processByte(original[endOfHeadTagPosition])) { endOfHeadTagFound = true; break; } @@ -45,8 +47,7 @@ public boolean handleWrite( if (!endOfHeadTagFound) { return false; } - // set before write to avoid recursive loop - state.setHeadTagWritten(); + if (state.getWrapper().isNotSafeToInject()) { return false; } @@ -59,9 +60,9 @@ public boolean handleWrite( } // updating Content-Length before any further writing in case that writing triggers a flush state.getWrapper().updateContentLengthIfPreviouslySet(); - out.write(original, off, i + 1); + out.write(original, off, endOfHeadTagPosition + 1); out.write(snippetBytes); - out.write(original, i + 1, length - i - 1); + out.write(original, endOfHeadTagPosition + 1, length - endOfHeadTagPosition - 1); return true; } @@ -72,8 +73,6 @@ public boolean handleWrite(InjectionState state, OutputStream out, int b) throws if (!state.processByte(b)) { return false; } - // set before write to avoid recursive loop - state.setHeadTagWritten(); if (state.getWrapper().isNotSafeToInject()) { return false; diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionState.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionState.java index d1d01f9f81f3..75bda0c2dfdd 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionState.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/ServletOutputStreamInjectionState.java @@ -6,6 +6,7 @@ package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet; import io.opentelemetry.instrumentation.api.util.VirtualField; +import javax.annotation.Nullable; import javax.servlet.ServletOutputStream; public class ServletOutputStreamInjectionState { @@ -26,6 +27,7 @@ public static void initializeInjectionStateIfNeeded( } } + @Nullable public static InjectionState getInjectionState(ServletOutputStream servletOutputStream) { return virtualField.get(servletOutputStream); } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java index 7388c25c3722..55d70d8d9568 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingPrintWriter.java @@ -39,8 +39,7 @@ public void write(int b) { if (!endOfHeadTagFound) { return; } - // set before write to avoid recursive loop - state.setHeadTagWritten(); + if (state.getWrapper().isNotSafeToInject()) { return; } diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java index 00edfa3521f2..f52ceb53369a 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/SnippetInjectingResponseWrapper.java @@ -38,7 +38,8 @@ public class SnippetInjectingResponseWrapper extends HttpServletResponseWrapper private static final int UNSET = -1; // this is for Servlet 3.1 support - @Nullable private static final MethodHandle setContentLengthLongHandler = getMethodHandle(); + @Nullable + private static final MethodHandle setContentLengthLongHandler = findSetContentLengthLongMethod(); private final String snippet; private final int snippetLength; @@ -119,7 +120,7 @@ public void setContentLength(int len) { } @Nullable - private static MethodHandle getMethodHandle() { + private static MethodHandle findSetContentLengthLongMethod() { try { return MethodHandles.lookup() .findSpecial( @@ -143,7 +144,7 @@ public void setContentLengthLong(long length) throws Throwable { } } - public boolean isContentTypeTextHtml() { + boolean isContentTypeTextHtml() { String contentType = super.getContentType(); if (contentType == null) { contentType = super.getHeader("content-type"); @@ -170,13 +171,13 @@ public PrintWriter getWriter() throws IOException { return snippetInjectingPrintWriter; } - public void updateContentLengthIfPreviouslySet() { + void updateContentLengthIfPreviouslySet() { if (contentLength != UNSET) { setContentLength((int) contentLength + snippetLength); } } - public boolean isNotSafeToInject() { + boolean isNotSafeToInject() { // if content-length was set and response was already committed (headers sent to the client), // then it is not safe to inject because the content-length header cannot be updated to account // for the snippet length diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy index e992ea8ba97b..64de51f93a6e 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy @@ -228,7 +228,8 @@ class TestServlet3 { resp.writer.print(endpoint.body) throw new ServletException(endpoint.body) case HTML_PRINT_WRITER: - // intentionally testing setting status before contentType here to cover that case somewhere resp.status = endpoint.status + // intentionally testing setting status before contentType here to cover that case somewhere + resp.status = endpoint.status resp.contentType = "text/html" resp.writer.print(endpoint.body) break diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java index acc944ffa4b1..948e85df0db1 100644 --- a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -9,8 +9,14 @@ public class ExperimentalSnippetHolder { private static volatile String snippet = ""; + private static boolean isSet = false; + public static void setSnippet(String snippet) { + if (isSet) { + return; + } ExperimentalSnippetHolder.snippet = snippet; + isSet = true; } public static String getSnippet() { From 6aaf18b57a446dcfd18c8ffb54ee5154bc4d7ccb Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Mon, 17 Apr 2023 13:38:18 -0700 Subject: [PATCH 28/37] update for atomicity --- .../servlet/ExperimentalSnippetHolder.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java index 948e85df0db1..80ae2a340985 100644 --- a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -5,22 +5,24 @@ package io.opentelemetry.javaagent.bootstrap.servlet; +import java.util.concurrent.atomic.AtomicReference; + public class ExperimentalSnippetHolder { - private static volatile String snippet = ""; + private static final AtomicReference snippet = new AtomicReference<>(""); private static boolean isSet = false; - public static void setSnippet(String snippet) { - if (isSet) { - return; + public static void setSnippet(String newValue) { + String oldValue = snippet.get(); + while (!isSet) { + isSet = snippet.compareAndSet(oldValue, newValue); + oldValue = snippet.get(); } - ExperimentalSnippetHolder.snippet = snippet; - isSet = true; } public static String getSnippet() { - return snippet; + return snippet.get(); } private ExperimentalSnippetHolder() {} From eff7aba0c2e20a338ed076aac813041f3bbc3fd4 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Wed, 19 Apr 2023 00:20:57 -0700 Subject: [PATCH 29/37] Update ExperimentalSnippetHolder.java --- .../bootstrap/servlet/ExperimentalSnippetHolder.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java index 80ae2a340985..9cf4b53339e9 100644 --- a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -11,14 +11,8 @@ public class ExperimentalSnippetHolder { private static final AtomicReference snippet = new AtomicReference<>(""); - private static boolean isSet = false; - public static void setSnippet(String newValue) { - String oldValue = snippet.get(); - while (!isSet) { - isSet = snippet.compareAndSet(oldValue, newValue); - oldValue = snippet.get(); - } + snippet.compareAndSet(null, newValue); } public static String getSnippet() { From 5f33dcaf400c3a7dcfa382ba0f6dc363f2259c0c Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Wed, 19 Apr 2023 12:49:12 -0700 Subject: [PATCH 30/37] initial value change to empty string --- .../javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java index 9cf4b53339e9..51a87bad698d 100644 --- a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -12,7 +12,8 @@ public class ExperimentalSnippetHolder { private static final AtomicReference snippet = new AtomicReference<>(""); public static void setSnippet(String newValue) { - snippet.compareAndSet(null, newValue); + snippet.compareAndSet("", newValue); + System.out.println("setSnippet to " + getSnippet()); } public static String getSnippet() { From 4d269b8dc5254c27cdbc47cbb5b2932c768f2b4f Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Wed, 19 Apr 2023 13:34:53 -0700 Subject: [PATCH 31/37] remove unnecessary print --- .../javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java index 51a87bad698d..007fdd547959 100644 --- a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -13,7 +13,6 @@ public class ExperimentalSnippetHolder { public static void setSnippet(String newValue) { snippet.compareAndSet("", newValue); - System.out.println("setSnippet to " + getSnippet()); } public static String getSnippet() { From 390bf48b85ef7177a69fd61a03d6fbdcd43d420f Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Thu, 20 Apr 2023 14:29:32 -0700 Subject: [PATCH 32/37] Update ExperimentalSnippetHolder.java --- .../javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java index 007fdd547959..110f93727512 100644 --- a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -9,7 +9,7 @@ public class ExperimentalSnippetHolder { - private static final AtomicReference snippet = new AtomicReference<>(""); + private static final AtomicReference snippet = new AtomicReference<>(System.getProperty("otel.experimental.javascript-snippet", "")); public static void setSnippet(String newValue) { snippet.compareAndSet("", newValue); From be5d10a9486d656a5ad20312d03a53cf6118126f Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Sat, 22 Apr 2023 00:13:37 -0700 Subject: [PATCH 33/37] Update ExperimentalSnippetHolder.java --- .../javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java index 110f93727512..c6a9a12d9f3e 100644 --- a/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java +++ b/instrumentation/servlet/servlet-common/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/servlet/ExperimentalSnippetHolder.java @@ -9,7 +9,8 @@ public class ExperimentalSnippetHolder { - private static final AtomicReference snippet = new AtomicReference<>(System.getProperty("otel.experimental.javascript-snippet", "")); + private static final AtomicReference snippet = + new AtomicReference<>(System.getProperty("otel.experimental.javascript-snippet", "")); public static void setSnippet(String newValue) { snippet.compareAndSet("", newValue); From 06dcd947616726ed74c342414be201e1d0182ac3 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Mon, 24 Apr 2023 15:17:26 -0700 Subject: [PATCH 34/37] change based on new serverEndpoint --- .../test/groovy/AbstractServlet3Test.groovy | 28 +++++++++++++++++-- .../src/test/groovy/JettyServlet3Test.groovy | 2 -- .../src/test/groovy/TestServlet3.groovy | 6 ++-- .../src/test/groovy/TomcatServlet3Test.groovy | 2 -- .../test/java/RequestDispatcherServlet.java | 7 ++--- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy index 3af4493998a0..5b3b4632c754 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy @@ -17,8 +17,6 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM @@ -41,6 +39,32 @@ abstract class AbstractServlet3Test extends HttpServerTest servlet) + public static final ServerEndpoint HTML_PRINT_WRITER = + new ServerEndpoint("HTML_PRINT_WRITER", "htmlPrintWriter", + 200, + "\n" + + "\n" + + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""); + public static final ServerEndpoint HTML_SERVLET_OUTPUT_STREAM = + new ServerEndpoint("HTML_SERVLET_OUTPUT_STREAM", "htmlServletOutputStream", + 200, + "\n" + + "\n" + + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""); protected void setupServlets(CONTEXT context) { def servlet = servlet() diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy index a3acb146125e..f2f93e4c2ef9 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy @@ -18,8 +18,6 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy index 64de51f93a6e..389f18505a11 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy @@ -18,8 +18,6 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT @@ -73,13 +71,13 @@ class TestServlet3 { break case EXCEPTION: throw new ServletException(endpoint.body) - case HTML_PRINT_WRITER: + case AbstractServlet3Test.HTML_PRINT_WRITER: resp.contentType = "text/html" resp.status = endpoint.status resp.setContentLengthLong(endpoint.body.length()) resp.writer.print(endpoint.body) break - case HTML_SERVLET_OUTPUT_STREAM: + case AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM: resp.contentType = "text/html" resp.status = endpoint.status resp.setContentLength(endpoint.body.length()) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy index 60f58741412b..1fc69364a0bc 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy @@ -30,8 +30,6 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java index 9abce0892ac1..9a618ad78e38 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java @@ -3,9 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_PRINT_WRITER; -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.HTML_SERVLET_OUTPUT_STREAM; - import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import java.io.IOException; import javax.servlet.RequestDispatcher; @@ -44,8 +41,8 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) // for HTML test case, set the content type before calling include because // setContentType will be rejected if called inside of include // check https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html - if (ServerEndpoint.forPath(target) == HTML_PRINT_WRITER - || ServerEndpoint.forPath(target) == HTML_SERVLET_OUTPUT_STREAM) { + if (ServerEndpoint.forPath(target) == AbstractServlet3Test.HTML_PRINT_WRITER + || ServerEndpoint.forPath(target) == AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM) { resp.setContentType("text/html"); } dispatcher.include(req, resp); From b47fbd13c0aaf2445d53b45b60c4cabcbce578c3 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Mon, 24 Apr 2023 15:53:47 -0700 Subject: [PATCH 35/37] solve the import error --- .../javaagent/src/test/groovy/TestServlet3.groovy | 8 ++++---- .../javaagent/src/test/java/RequestDispatcherServlet.java | 4 ++-- .../testing/junit/http/ServerEndpoint.java | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy index 389f18505a11..9048c157c27f 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy @@ -156,14 +156,14 @@ class TestServlet3 { } throw new ServletException(endpoint.body) break - case HTML_PRINT_WRITER: + case AbstractServlet3Test.HTML_PRINT_WRITER: resp.contentType = "text/html" resp.status = endpoint.status resp.setContentLength(endpoint.body.length()) resp.writer.print(endpoint.body) context.complete() break - case HTML_SERVLET_OUTPUT_STREAM: + case AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM: resp.contentType = "text/html" resp.status = endpoint.status resp.getOutputStream().print(endpoint.body) @@ -225,13 +225,13 @@ class TestServlet3 { resp.status = endpoint.status resp.writer.print(endpoint.body) throw new ServletException(endpoint.body) - case HTML_PRINT_WRITER: + case AbstractServlet3Test.HTML_PRINT_WRITER: // intentionally testing setting status before contentType here to cover that case somewhere resp.status = endpoint.status resp.contentType = "text/html" resp.writer.print(endpoint.body) break - case HTML_SERVLET_OUTPUT_STREAM: + case AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM: resp.contentType = "text/html" resp.status = endpoint.status resp.getOutputStream().print(endpoint.body) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java index 9a618ad78e38..7d9b749a107b 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java @@ -41,8 +41,8 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) // for HTML test case, set the content type before calling include because // setContentType will be rejected if called inside of include // check https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html - if (ServerEndpoint.forPath(target) == AbstractServlet3Test.HTML_PRINT_WRITER - || ServerEndpoint.forPath(target) == AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM) { + if (ServerEndpoint.forPath(target) == ServerEndpoint.forPath("/htmlPrintWriter") + || ServerEndpoint.forPath(target) == ServerEndpoint.forPath("/htmlServletOutputStream")) { resp.setContentType("text/html"); } dispatcher.include(req, resp); diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java index 47b1df38b482..7629db4ff273 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java @@ -91,6 +91,7 @@ public String name() { this.fragment = uriObj.getFragment(); this.status = status; this.body = body; + System.out.println("create new" + name + "path " + this.getPath()); PATH_MAP.put(this.getPath(), this); } From c184c86b0c7b749d8b8f4d4469a8bd6fabeb9124 Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Tue, 25 Apr 2023 00:26:18 -0700 Subject: [PATCH 36/37] Update ServerEndpoint.java --- .../instrumentation/testing/junit/http/ServerEndpoint.java | 1 - 1 file changed, 1 deletion(-) diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java index 7629db4ff273..47b1df38b482 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/ServerEndpoint.java @@ -91,7 +91,6 @@ public String name() { this.fragment = uriObj.getFragment(); this.status = status; this.body = body; - System.out.println("create new" + name + "path " + this.getPath()); PATH_MAP.put(this.getPath(), this); } From 672e8a76a6313c800a5fbab1300903aeefd1e7bd Mon Sep 17 00:00:00 2001 From: siyuniu-ms Date: Tue, 25 Apr 2023 00:57:53 -0700 Subject: [PATCH 37/37] Update AbstractServlet3Test.groovy --- .../javaagent/src/test/groovy/AbstractServlet3Test.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy index 5b3b4632c754..5a9838a60a26 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy @@ -51,7 +51,7 @@ abstract class AbstractServlet3Test extends HttpServerTest\n" + "

test works

\n" + "\n" - + ""); + + "") public static final ServerEndpoint HTML_SERVLET_OUTPUT_STREAM = new ServerEndpoint("HTML_SERVLET_OUTPUT_STREAM", "htmlServletOutputStream", 200, @@ -64,7 +64,7 @@ abstract class AbstractServlet3Test extends HttpServerTest\n" + "

test works

\n" + "\n" - + ""); + + "") protected void setupServlets(CONTEXT context) { def servlet = servlet()