Skip to content

Commit ba96eb4

Browse files
authored
✨ Evaluate http bodies for Java EE with Hypertrace Filter (#349)
* ♻️ enhance mock filter to block when block=true * ✅ add request for blocking servlet request bodies' * 🚧 add exception for blocking request' * ✨ suppress HT evaluation exception for servlets * ✨ block request based on filter response when reading from servlet request bodies * 🐛 fix bad null check resulting in NPE being thrown and polluting logs/hurting performance * ✨ remove suppression for body reading instrumentation * 🐛 remove log statement that causes module to not load in production * ♻️ modify Filter API to accept headers for evaluateRequestBody * 🚧 provide headers to ByteBufferSpanPair via ServletRequestInstrumentation * ✨ include headers in SpanAndObjectPair * ✨ rovide headers to ByteBufferSpanPair from SpanAndObject pair * ✨ support body evaluation for char buffers * ➕ add bootstrap api to compile classpath * 🚧 functional custom exception handler * ♻️ avoid exception handler approach, instead use try-catch in each onMethodExit
1 parent 0070c2c commit ba96eb4

File tree

18 files changed

+343
-101
lines changed

18 files changed

+343
-101
lines changed

filter-api/src/main/java/org/hypertrace/agent/filter/MultiFilter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ public boolean evaluateRequestHeaders(Span span, Map<String, String> headers) {
5252
}
5353

5454
@Override
55-
public boolean evaluateRequestBody(Span span, String body) {
55+
public boolean evaluateRequestBody(Span span, String body, Map<String, String> headers) {
5656
boolean shouldBlock = false;
5757
for (Filter filter : filters) {
5858
try {
59-
if (filter.evaluateRequestBody(span, body)) {
59+
if (filter.evaluateRequestBody(span, body, headers)) {
6060
shouldBlock = true;
6161
}
6262
} catch (Throwable t) {

filter-api/src/main/java/org/hypertrace/agent/filter/api/Filter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ public interface Filter {
3737
/**
3838
* Evaluate the execution.
3939
*
40+
* @param span of the HTTP request associated with this body
4041
* @param body request body
42+
* @param headers of the request associated with this body
4143
* @return filter result
4244
*/
43-
boolean evaluateRequestBody(Span span, String body);
45+
boolean evaluateRequestBody(Span span, String body, Map<String, String> headers);
4446
}

instrumentation/servlet/servlet-3.0/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ val versions: Map<String, String> by extra
3232
dependencies {
3333
implementation("io.opentelemetry.instrumentation:opentelemetry-servlet-3.0:${versions["opentelemetry_java_agent"]}")
3434
testImplementation("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-servlet-3.0:${versions["opentelemetry_java_agent"]}")
35-
35+
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-bootstrap:${versions["opentelemetry_java_agent"]}")
3636
compileOnly("javax.servlet:javax.servlet-api:3.1.0")
3737

3838
testImplementation(project(":instrumentation:servlet:servlet-rw"))

instrumentation/servlet/servlet-3.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/servlet/v3_0/nowrapping/Servlet30AndFilterInstrumentation.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
3333
import java.io.BufferedReader;
3434
import java.io.PrintWriter;
35+
import java.util.Collections;
3536
import java.util.Enumeration;
3637
import java.util.HashMap;
3738
import java.util.Map;
@@ -46,6 +47,7 @@
4647
import net.bytebuddy.matcher.ElementMatcher;
4748
import org.hypertrace.agent.core.config.InstrumentationConfig;
4849
import org.hypertrace.agent.core.instrumentation.HypertraceCallDepthThreadLocalMap;
50+
import org.hypertrace.agent.core.instrumentation.HypertraceEvaluationException;
4951
import org.hypertrace.agent.core.instrumentation.HypertraceSemanticAttributes;
5052
import org.hypertrace.agent.core.instrumentation.SpanAndObjectPair;
5153
import org.hypertrace.agent.core.instrumentation.buffer.BoundedByteArrayOutputStream;
@@ -78,6 +80,7 @@ public void transform(TypeTransformer transformer) {
7880
}
7981

8082
public static class ServletAdvice {
83+
8184
@Advice.OnMethodEnter(suppress = Throwable.class, skipOn = Advice.OnNonDefaultValue.class)
8285
public static boolean start(
8386
@Advice.Argument(value = 0) ServletRequest request,
@@ -99,15 +102,6 @@ public static boolean start(
99102

100103
InstrumentationConfig instrumentationConfig = InstrumentationConfig.ConfigProvider.get();
101104

102-
String contentType = httpRequest.getContentType();
103-
if (instrumentationConfig.httpBody().request()
104-
&& ContentTypeUtils.shouldCapture(contentType)) {
105-
// The HttpServletRequest instrumentation uses this to
106-
// enable the instrumentation
107-
InstrumentationContext.get(HttpServletRequest.class, SpanAndObjectPair.class)
108-
.put(httpRequest, new SpanAndObjectPair(currentSpan));
109-
}
110-
111105
Utils.addSessionId(currentSpan, httpRequest);
112106

113107
// set request headers
@@ -130,13 +124,24 @@ public static boolean start(
130124
// skip execution of the user code
131125
return true;
132126
}
127+
128+
if (instrumentationConfig.httpBody().request()
129+
&& ContentTypeUtils.shouldCapture(httpRequest.getContentType())) {
130+
// The HttpServletRequest instrumentation uses this to
131+
// enable the instrumentation
132+
InstrumentationContext.get(HttpServletRequest.class, SpanAndObjectPair.class)
133+
.put(
134+
httpRequest,
135+
new SpanAndObjectPair(currentSpan, Collections.unmodifiableMap(headers)));
136+
}
133137
return false;
134138
}
135139

136-
@Advice.OnMethodExit(suppress = Throwable.class)
140+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
137141
public static void exit(
138142
@Advice.Argument(0) ServletRequest request,
139143
@Advice.Argument(1) ServletResponse response,
144+
@Advice.Thrown(readOnly = false) Throwable throwable,
140145
@Advice.Local("currentSpan") Span currentSpan) {
141146
int callDepth =
142147
HypertraceCallDepthThreadLocalMap.decrementCallDepth(Servlet30InstrumentationName.class);
@@ -153,6 +158,14 @@ public static void exit(
153158
HttpServletRequest httpRequest = (HttpServletRequest) request;
154159
InstrumentationConfig instrumentationConfig = InstrumentationConfig.ConfigProvider.get();
155160

161+
if (throwable instanceof HypertraceEvaluationException) {
162+
httpResponse.setStatus(403);
163+
// bytebuddy treats the reassignment of this variable to null as an instruction to suppress
164+
// this exception, which is what we want
165+
throwable = null;
166+
return;
167+
}
168+
156169
// response context to capture body and clear the context
157170
ContextStore<HttpServletResponse, SpanAndObjectPair> responseContextStore =
158171
InstrumentationContext.get(HttpServletResponse.class, SpanAndObjectPair.class);

instrumentation/servlet/servlet-3.0/src/main/java/io/opentelemetry/javaagent/instrumentation/hypertrace/servlet/v3_0/nowrapping/Utils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public static void resetRequestBodyBuffers(
9090
ContextStore<BufferedReader, CharBufferSpanPair> bufferedReaderContextStore) {
9191

9292
SpanAndObjectPair requestStreamReaderHolder = requestContextStore.get(httpServletRequest);
93-
if (requestContextStore == null) {
93+
if (requestStreamReaderHolder == null) {
9494
return;
9595
}
9696
requestContextStore.put(httpServletRequest, null);

0 commit comments

Comments
 (0)