Skip to content

Commit 8ab88ee

Browse files
authored
Merge pull request #5393 from DataDog/glopes/tomcat-block-resp
Support block on response on tomcat
2 parents a82bded + 9250f83 commit 8ab88ee

File tree

27 files changed

+700
-90
lines changed

27 files changed

+700
-90
lines changed

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,21 @@ public AgentSpan onResponseStatus(final AgentSpan span, final int status) {
310310
return span;
311311
}
312312

313+
/**
314+
* Whether AppSec should NOT be called during onResponse() for analysis of the status code and
315+
* headers.
316+
*
317+
* <p>{@link #onResponse(AgentSpan, Object)} is usually called too late for AppSec to be able to
318+
* alter the response, so for those modules where we support blocking on response this is <code>
319+
* true</code> and AppSec has its own (earlier) hook point for processing the response (just
320+
* before commit).
321+
*
322+
* @return whether AppSec analysis of the response is run separately from onResponse
323+
*/
324+
protected boolean isAppSecOnResponseSeparate() {
325+
return false;
326+
}
327+
313328
public AgentSpan onResponse(final AgentSpan span, final RESPONSE response) {
314329
if (response != null) {
315330
final int status = status(response);
@@ -324,7 +339,9 @@ public AgentSpan onResponse(final AgentSpan span, final RESPONSE response) {
324339
}
325340
}
326341

327-
callIGCallbackResponseAndHeaders(span, response, status);
342+
if (!isAppSecOnResponseSeparate()) {
343+
callIGCallbackResponseAndHeaders(span, response, status);
344+
}
328345
}
329346
return span;
330347
}
@@ -401,30 +418,40 @@ private Flow<Void> callIGCallbackRequestHeaders(AgentSpan span, REQUEST_CARRIER
401418
return Flow.ResultFlow.empty();
402419
}
403420

404-
private void callIGCallbackResponseAndHeaders(AgentSpan span, RESPONSE carrier, int status) {
421+
private Flow<Void> callIGCallbackResponseAndHeaders(
422+
AgentSpan span, RESPONSE carrier, int status) {
423+
return callIGCallbackResponseAndHeaders(span, carrier, status, responseGetter());
424+
}
425+
426+
public <RESP> Flow<Void> callIGCallbackResponseAndHeaders(
427+
AgentSpan span,
428+
RESP carrier,
429+
int status,
430+
AgentPropagation.ContextVisitor<RESP> contextVisitor) {
405431
CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC);
406432
RequestContext requestContext = span.getRequestContext();
407433
if (cbp == null || requestContext == null) {
408-
return;
434+
return Flow.ResultFlow.empty();
409435
}
436+
410437
BiFunction<RequestContext, Integer, Flow<Void>> addrCallback =
411438
cbp.getCallback(EVENTS.responseStarted());
412439
if (null != addrCallback) {
413440
addrCallback.apply(requestContext, status);
414441
}
415-
AgentPropagation.ContextVisitor<RESPONSE> getter = responseGetter();
416-
if (getter == null) {
417-
return;
442+
if (contextVisitor == null) {
443+
return Flow.ResultFlow.empty();
418444
}
419445
IGKeyClassifier igKeyClassifier =
420446
IGKeyClassifier.create(
421447
requestContext,
422448
cbp.getCallback(EVENTS.responseHeader()),
423449
cbp.getCallback(EVENTS.responseHeaderDone()));
424450
if (null != igKeyClassifier) {
425-
getter.forEachKey(carrier, igKeyClassifier);
426-
igKeyClassifier.done();
451+
contextVisitor.forEachKey(carrier, igKeyClassifier);
452+
return igKeyClassifier.done();
427453
}
454+
return Flow.ResultFlow.empty();
428455
}
429456

430457
private Flow<Void> callIGCallbackURI(
@@ -498,9 +525,9 @@ private Flow<Void> callIGCallbackAddressAndPort(
498525
}
499526

500527
/** This passes the headers through to the InstrumentationGateway */
501-
private static final class IGKeyClassifier implements AgentPropagation.KeyClassifier {
528+
protected static final class IGKeyClassifier implements AgentPropagation.KeyClassifier {
502529

503-
private static IGKeyClassifier create(
530+
public static IGKeyClassifier create(
504531
RequestContext requestContext,
505532
TriConsumer<RequestContext, String, String> headerCallback,
506533
Function<RequestContext, Flow<Void>> doneCallback) {

dd-java-agent/instrumentation/servlet/request-3/src/test/groovy/TomcatServlet3Test.groovy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ abstract class TomcatServlet3Test extends AbstractServlet3Test<Tomcat, Context>
3636
@Shared
3737
TestAccessLogValve accessLogValue
3838

39+
@Override
40+
boolean testBlockingOnResponse() {
41+
true
42+
}
43+
3944
class TomcatServer implements HttpServer {
4045
def port = 0
4146
final Tomcat server

dd-java-agent/instrumentation/spring-webmvc-3.1/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ dependencies {
5858
testImplementation project(':dd-java-agent:instrumentation:servlet-common')
5959
testImplementation project(':dd-java-agent:instrumentation:servlet:request-3')
6060

61-
// include also tomcat appsec instrumentation for intercepting urlencoded/multipart bodies
61+
// include also tomcat instrumentation
62+
testRuntimeOnly project(':dd-java-agent:instrumentation:tomcat-5.5')
6263
testRuntimeOnly project(':dd-java-agent:instrumentation:tomcat-appsec-6')
6364
testRuntimeOnly project(':dd-java-agent:instrumentation:tomcat-appsec-7')
6465

dd-java-agent/instrumentation/spring-webmvc-3.1/src/test/groovy/test/boot/SpringBootBasedTest.groovy

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import datadog.trace.agent.test.asserts.TraceAssert
44
import datadog.trace.agent.test.base.HttpServer
55
import datadog.trace.agent.test.base.HttpServerTest
66
import datadog.trace.api.DDSpanTypes
7+
import datadog.trace.api.DDTags
78
import datadog.trace.api.iast.InstrumentationBridge
89
import datadog.trace.api.iast.SourceTypes
910
import datadog.trace.api.iast.propagation.PropagationModule
1011
import datadog.trace.bootstrap.instrumentation.api.Tags
1112
import datadog.trace.core.DDSpan
12-
import datadog.trace.instrumentation.servlet3.Servlet3Decorator
1313
import datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator
1414
import okhttp3.FormBody
1515
import okhttp3.Request
@@ -65,7 +65,7 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
6565

6666
@Override
6767
String component() {
68-
return Servlet3Decorator.DECORATE.component()
68+
'tomcat-server'
6969
}
7070

7171
String getServletContext() {
@@ -155,9 +155,12 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
155155
int spanCount(ServerEndpoint endpoint) {
156156
if (endpoint == REDIRECT) {
157157
// Spring is generates a RenderView and ResponseSpan for REDIRECT
158-
return super.spanCount(endpoint) + 1
158+
super.spanCount(endpoint) + 1
159+
} else if (endpoint == NOT_FOUND) {
160+
super.spanCount(endpoint) + 2
161+
} else {
162+
super.spanCount(endpoint)
159163
}
160-
return super.spanCount(endpoint)
161164
}
162165

163166
@Override
@@ -399,4 +402,37 @@ class SpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext>
399402
}
400403
}
401404
}
405+
406+
407+
protected void trailingSpans(TraceAssert traceAssert, ServerEndpoint serverEndpoint) {
408+
if (serverEndpoint == NOT_FOUND) {
409+
traceAssert.with {
410+
span {
411+
spanType 'web'
412+
serviceName expectedServiceName()
413+
operationName 'servlet.forward'
414+
resourceName 'GET /error'
415+
tags {
416+
"$Tags.COMPONENT" 'java-web-servlet-dispatcher'
417+
"$Tags.HTTP_ROUTE" '/error'
418+
'servlet.context' "/$servletContext"
419+
'servlet.path' '/not-found'
420+
"$DDTags.PATHWAY_HASH" String
421+
defaultTags()
422+
}
423+
}
424+
span {
425+
spanType 'web'
426+
serviceName expectedServiceName()
427+
operationName 'spring.handler'
428+
resourceName 'BasicErrorController.error'
429+
tags {
430+
"$Tags.COMPONENT" 'spring-web-controller'
431+
"$Tags.SPAN_KIND" 'server'
432+
defaultTags()
433+
}
434+
}
435+
}
436+
}
437+
}
402438
}

dd-java-agent/instrumentation/spring-webmvc-3.1/src/test/groovy/test/dynamic/DynamicRoutingTest.groovy

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package test.dynamic
22

3-
43
import datadog.trace.agent.test.asserts.TraceAssert
54
import datadog.trace.agent.test.base.HttpServer
65
import datadog.trace.agent.test.base.HttpServerTest
76
import datadog.trace.api.DDSpanTypes
7+
import datadog.trace.api.DDTags
88
import datadog.trace.bootstrap.instrumentation.api.Tags
9-
import datadog.trace.instrumentation.servlet3.Servlet3Decorator
109
import datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator
1110
import org.springframework.boot.SpringApplication
1211
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext
1312
import org.springframework.context.ConfigurableApplicationContext
1413
import org.springframework.web.servlet.view.RedirectView
14+
import org.springframework.web.util.NestedServletException
1515
import test.boot.SecurityConfig
1616

1717
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
@@ -61,9 +61,14 @@ class DynamicRoutingTest extends HttpServerTest<ConfigurableApplicationContext>
6161
return new SpringBootServer()
6262
}
6363

64+
@Override
65+
String expectedServiceName() {
66+
'root-servlet'
67+
}
68+
6469
@Override
6570
String component() {
66-
return Servlet3Decorator.DECORATE.component()
71+
'tomcat-server'
6772
}
6873

6974
@Override
@@ -110,15 +115,18 @@ class DynamicRoutingTest extends HttpServerTest<ConfigurableApplicationContext>
110115

111116
@Override
112117
Map<String, Serializable> expectedExtraServerTags(ServerEndpoint endpoint) {
113-
["servlet.path": endpoint.path]
118+
["servlet.path": endpoint.path, 'servlet.context': '/']
114119
}
115120

116121
int spanCount(ServerEndpoint endpoint) {
117122
if (endpoint == REDIRECT) {
118123
// Spring is generates a RenderView and ResponseSpan for REDIRECT
119-
return super.spanCount(endpoint) + 1
124+
super.spanCount(endpoint) + 1
125+
} else if (endpoint == EXCEPTION) {
126+
super.spanCount(endpoint) +2
127+
} else {
128+
super.spanCount(endpoint)
120129
}
121-
return super.spanCount(endpoint)
122130
}
123131

124132
@Override
@@ -187,4 +195,47 @@ class DynamicRoutingTest extends HttpServerTest<ConfigurableApplicationContext>
187195
int firstUnderscore = x.indexOf('_')
188196
return firstUnderscore == -1 ? x : x.substring(0, firstUnderscore)
189197
}
198+
199+
Map<String, Serializable> expectedExtraErrorInformation(ServerEndpoint endpoint) {
200+
if (endpoint == EXCEPTION) {
201+
["error.message" : 'Request processing failed; nested exception is java.lang.Exception: controller exception',
202+
"error.type" : NestedServletException.name,
203+
"error.stack": String]
204+
} else {
205+
super.expectedExtraErrorInformation(endpoint)
206+
}
207+
}
208+
209+
protected void trailingSpans(TraceAssert traceAssert, ServerEndpoint serverEndpoint) {
210+
if (serverEndpoint == EXCEPTION) {
211+
traceAssert.with {
212+
span {
213+
spanType 'web'
214+
serviceName expectedServiceName()
215+
operationName 'servlet.forward'
216+
resourceName 'GET /error'
217+
tags {
218+
"$Tags.COMPONENT" 'java-web-servlet-dispatcher'
219+
"$Tags.HTTP_ROUTE" '/error'
220+
'servlet.context' '/'
221+
'servlet.path' serverEndpoint.path
222+
"$DDTags.PATHWAY_HASH" String
223+
defaultTags()
224+
}
225+
}
226+
span {
227+
spanType 'web'
228+
childOfPrevious()
229+
serviceName expectedServiceName()
230+
operationName 'spring.handler'
231+
resourceName 'BasicErrorController.error'
232+
tags {
233+
"$Tags.COMPONENT" 'spring-web-controller'
234+
"$Tags.SPAN_KIND" 'server'
235+
defaultTags()
236+
}
237+
}
238+
}
239+
}
240+
}
190241
}

0 commit comments

Comments
 (0)