Merge pull request #942 from DataDog/tyler/http-server-testing

Jetty 8 test migration and ignoreParent
This commit is contained in:
Tyler Benson 2019-08-13 13:07:18 -07:00 committed by GitHub
commit ab623ab7b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 316 additions and 254 deletions

View File

@ -7,7 +7,7 @@ import io.opentracing.tag.Tags
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
abstract class AkkaHttpServerInstrumentationTest extends HttpServerTest<AkkaHttpServerDecorator> { abstract class AkkaHttpServerInstrumentationTest extends HttpServerTest<Object, AkkaHttpServerDecorator> {
@Override @Override
AkkaHttpServerDecorator decorator() { AkkaHttpServerDecorator decorator() {
@ -26,12 +26,12 @@ abstract class AkkaHttpServerInstrumentationTest extends HttpServerTest<AkkaHttp
// FIXME: This doesn't work because we don't support bindAndHandle. // FIXME: This doesn't work because we don't support bindAndHandle.
// @Override // @Override
// void startServer(int port) { // def startServer(int port) {
// AkkaHttpTestWebServer.start(port) // AkkaHttpTestWebServer.start(port)
// } // }
// //
// @Override // @Override
// void stopServer() { // void stopServer(Object ignore) {
// AkkaHttpTestWebServer.stop() // AkkaHttpTestWebServer.stop()
// } // }
@ -68,24 +68,24 @@ abstract class AkkaHttpServerInstrumentationTest extends HttpServerTest<AkkaHttp
class AkkaHttpServerInstrumentationTestSync extends AkkaHttpServerInstrumentationTest { class AkkaHttpServerInstrumentationTestSync extends AkkaHttpServerInstrumentationTest {
@Override @Override
void startServer(int port) { def startServer(int port) {
AkkaHttpTestSyncWebServer.start(port) AkkaHttpTestSyncWebServer.start(port)
} }
@Override @Override
void stopServer() { void stopServer(Object ignore) {
AkkaHttpTestSyncWebServer.stop() AkkaHttpTestSyncWebServer.stop()
} }
} }
class AkkaHttpServerInstrumentationTestAsync extends AkkaHttpServerInstrumentationTest { class AkkaHttpServerInstrumentationTestAsync extends AkkaHttpServerInstrumentationTest {
@Override @Override
void startServer(int port) { def startServer(int port) {
AkkaHttpTestAsyncWebServer.start(port) AkkaHttpTestAsyncWebServer.start(port)
} }
@Override @Override
void stopServer() { void stopServer(Object ignore) {
AkkaHttpTestAsyncWebServer.stop() AkkaHttpTestAsyncWebServer.stop()
} }
} }

View File

@ -24,6 +24,8 @@ dependencies {
testCompile(project(':dd-java-agent:testing')) { testCompile(project(':dd-java-agent:testing')) {
exclude group: 'org.eclipse.jetty', module: 'jetty-server' exclude group: 'org.eclipse.jetty', module: 'jetty-server'
} }
testCompile project(':dd-java-agent:instrumentation:java-concurrent')
testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version: '8.0.0.v20110901' testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version: '8.0.0.v20110901'
testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '8.0.0.v20110901' testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '8.0.0.v20110901'
testCompile group: 'org.eclipse.jetty', name: 'jetty-continuation', version: '8.0.0.v20110901' testCompile group: 'org.eclipse.jetty', name: 'jetty-continuation', version: '8.0.0.v20110901'

View File

@ -16,13 +16,14 @@ import javax.servlet.http.HttpServletResponse;
import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.Advice;
public class JettyHandlerAdvice { public class JettyHandlerAdvice {
public static final String SERVLET_SPAN = "datadog.servlet.span";
@Advice.OnMethodEnter(suppress = Throwable.class) @Advice.OnMethodEnter(suppress = Throwable.class)
public static Scope startSpan( public static Scope startSpan(
@Advice.This final Object source, @Advice.Argument(2) final HttpServletRequest req) { @Advice.This final Object source, @Advice.Argument(2) final HttpServletRequest req) {
if (GlobalTracer.get().activeSpan() != null) { if (req.getAttribute(SERVLET_SPAN) != null) {
// Tracing might already be applied. If so ignore this. // Request already being traced elsewhere.
return null; return null;
} }
@ -32,6 +33,7 @@ public class JettyHandlerAdvice {
final Scope scope = final Scope scope =
GlobalTracer.get() GlobalTracer.get()
.buildSpan("jetty.request") .buildSpan("jetty.request")
.ignoreActiveSpan()
.asChildOf(extractedContext) .asChildOf(extractedContext)
.withTag("span.origin.type", source.getClass().getName()) .withTag("span.origin.type", source.getClass().getName())
.startActive(false); .startActive(false);
@ -46,6 +48,7 @@ public class JettyHandlerAdvice {
if (scope instanceof TraceScope) { if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true); ((TraceScope) scope).setAsyncPropagation(true);
} }
req.setAttribute(SERVLET_SPAN, span);
return scope; return scope;
} }

View File

@ -16,9 +16,9 @@ import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class) @AutoService(Instrumenter.class)
public final class HandlerInstrumentation extends Instrumenter.Default { public final class JettyHandlerInstrumentation extends Instrumenter.Default {
public HandlerInstrumentation() { public JettyHandlerInstrumentation() {
super("jetty", "jetty-8"); super("jetty", "jetty-8");
} }

View File

@ -0,0 +1,62 @@
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.eclipse.jetty.continuation.Continuation
import org.eclipse.jetty.continuation.ContinuationSupport
import org.eclipse.jetty.server.Request
import org.eclipse.jetty.server.handler.AbstractHandler
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
// FIXME: We don't currently handle jetty continuations properly (at all).
abstract class JettyContinuationHandlerTest extends JettyHandlerTest {
@Override
AbstractHandler handler() {
ContinuationTestHandler.INSTANCE
}
static class ContinuationTestHandler extends AbstractHandler {
static final ContinuationTestHandler INSTANCE = new ContinuationTestHandler()
final ExecutorService executorService = Executors.newSingleThreadExecutor()
@Override
void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
final Continuation continuation = ContinuationSupport.getContinuation(request)
if (continuation.initial) {
continuation.suspend()
executorService.execute {
continuation.resume()
}
} else {
handleRequest(baseRequest, response)
}
baseRequest.handled = true
}
}
// // This server seems to generate a TEST_SPAN twice... once for the initial request, and once for the continuation.
// void cleanAndAssertTraces(
// final int size,
// @ClosureParams(value = SimpleType, options = "datadog.trace.agent.test.asserts.ListWriterAssert")
// @DelegatesTo(value = ListWriterAssert, strategy = Closure.DELEGATE_FIRST)
// final Closure spec) {
//
// // If this is failing, make sure HttpServerTestAdvice is applied correctly.
// TEST_WRITER.waitForTraces(size * 3)
// // TEST_WRITER is a CopyOnWriteArrayList, which doesn't support remove()
// def toRemove = TEST_WRITER.findAll {
// it.size() == 1 && it.get(0).operationName == "TEST_SPAN"
// }
// toRemove.each {
// assertTrace(it, 1) {
// basicSpan(it, 0, "TEST_SPAN", "ServerEntry")
// }
// }
// assert toRemove.size() == size * 2
// TEST_WRITER.removeAll(toRemove)
//
// assertTraces(size, spec)
// }
}

View File

@ -1,204 +1,144 @@
import datadog.trace.agent.test.AgentTestRunner import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.agent.test.utils.OkHttpUtils import datadog.trace.agent.test.base.HttpServerTest
import datadog.trace.agent.test.utils.PortUtils
import datadog.trace.api.DDSpanTypes import datadog.trace.api.DDSpanTypes
import okhttp3.OkHttpClient import datadog.trace.instrumentation.jetty8.JettyDecorator
import org.eclipse.jetty.continuation.Continuation import io.opentracing.tag.Tags
import org.eclipse.jetty.continuation.ContinuationSupport
import org.eclipse.jetty.server.Handler
import org.eclipse.jetty.server.Request
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.AbstractHandler
import javax.servlet.DispatcherType import javax.servlet.DispatcherType
import javax.servlet.ServletException import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse import javax.servlet.http.HttpServletResponse
import java.util.concurrent.atomic.AtomicBoolean import org.eclipse.jetty.server.Request
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.AbstractHandler
import org.eclipse.jetty.server.handler.ErrorHandler
class JettyHandlerTest extends AgentTestRunner { import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
class JettyHandlerTest extends HttpServerTest<Server, JettyDecorator> {
static { static {
System.setProperty("dd.integration.jetty.enabled", "true") System.setProperty("dd.integration.jetty.enabled", "true")
} }
int port = PortUtils.randomOpenPort() static errorHandler = new ErrorHandler() {
Server server = new Server(port) @Override
protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) throws IOException {
Throwable th = (Throwable) request.getAttribute("javax.servlet.error.exception")
message = th ? th.message : message
if (message) {
writer.write(message)
}
}
}
OkHttpClient client = OkHttpUtils.client() @Override
Server startServer(int port) {
def server = new Server(port)
server.setHandler(handler())
server.addBean(errorHandler)
server.start()
return server
}
def cleanup() { AbstractHandler handler() {
TestHandler.INSTANCE
}
@Override
void stopServer(Server server) {
server.stop() server.stop()
} }
def "call to jetty creates a trace"() { @Override
setup: JettyDecorator decorator() {
Handler handler = new AbstractHandler() { return JettyDecorator.DECORATE
}
@Override
String expectedOperationName() {
return "jetty.request"
}
@Override
boolean testExceptionBody() {
false
}
static void handleRequest(Request request, HttpServletResponse response) {
ServerEndpoint endpoint = ServerEndpoint.forPath(request.requestURI)
controller(endpoint) {
response.contentType = "text/plain"
switch (endpoint) {
case SUCCESS:
response.status = endpoint.status
response.writer.print(endpoint.body)
break
case REDIRECT:
response.sendRedirect(endpoint.body)
break
case ERROR:
response.sendError(endpoint.status, endpoint.body)
break
case EXCEPTION:
throw new Exception(endpoint.body)
default:
response.status = NOT_FOUND.status
response.writer.print(NOT_FOUND.body)
break
}
}
}
static class TestHandler extends AbstractHandler {
static final TestHandler INSTANCE = new TestHandler()
@Override @Override
void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/plain;charset=utf-8") if (baseRequest.dispatcherType != DispatcherType.ERROR) {
response.setStatus(HttpServletResponse.SC_OK) handleRequest(baseRequest, response)
baseRequest.setHandled(true) baseRequest.handled = true
response.getWriter().println("Hello World")
}
}
server.setHandler(handler)
server.start()
def request = new okhttp3.Request.Builder()
.url("http://localhost:$port/")
.get()
.build()
def response = client.newCall(request).execute()
expect:
response.body().string().trim() == "Hello World"
assertTraces(1) {
trace(0, 1) {
span(0) {
serviceName "unnamed-java-app"
operationName "jetty.request"
resourceName "GET ${handler.class.name}"
spanType DDSpanTypes.HTTP_SERVER
errored false
parent()
tags {
"http.url" "http://localhost:$port/"
"http.method" "GET"
"span.kind" "server"
"component" "jetty-handler"
"span.origin.type" handler.class.name
"http.status_code" 200
"peer.hostname" "127.0.0.1"
"peer.ipv4" "127.0.0.1"
"peer.port" Integer
defaultTags()
}
}
}
}
}
def "handler instrumentation clears state after async request"() {
setup:
Handler handler = new AbstractHandler() {
@Override
void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
final Continuation continuation = ContinuationSupport.getContinuation(request)
continuation.suspend(response)
// By the way, this is a terrible async server
new Thread() {
@Override
void run() {
continuation.getServletResponse().setContentType("text/plain;charset=utf-8")
continuation.getServletResponse().getWriter().println("Hello World")
continuation.complete()
}
}.start()
baseRequest.setHandled(true)
}
}
server.setHandler(handler)
server.start()
def request = new okhttp3.Request.Builder()
.url("http://localhost:$port/")
.get()
.build()
def numTraces = 10
for (int i = 0; i < numTraces; ++i) {
assert client.newCall(request).execute().body().string().trim() == "Hello World"
}
expect:
assertTraces(numTraces) {
for (int i = 0; i < numTraces; ++i) {
trace(i, 1) {
span(0) {
serviceName "unnamed-java-app"
operationName "jetty.request"
resourceName "GET ${handler.class.name}"
spanType DDSpanTypes.HTTP_SERVER
}
}
}
}
}
def "call to jetty with error creates a trace"() {
setup:
def errorHandlerCalled = new AtomicBoolean(false)
Handler handler = new AbstractHandler() {
@Override
void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if (baseRequest.dispatcherType == DispatcherType.ERROR) {
errorHandlerCalled.set(true)
baseRequest.setHandled(true)
} else { } else {
throw new RuntimeException() errorHandler.handle(target, baseRequest, response, response)
} }
} }
} }
server.setHandler(handler)
server.start()
def request = new okhttp3.Request.Builder()
.url("http://localhost:$port/")
.get()
.build()
def response = client.newCall(request).execute()
expect: @Override
response.body().string().trim() == "" void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
def handlerName = handler().class.name
assertTraces(errorHandlerCalled.get() ? 2 : 1) { trace.span(index) {
trace(0, 1) { serviceName expectedServiceName()
span(0) { operationName expectedOperationName()
serviceName "unnamed-java-app" resourceName endpoint.status == 404 ? "404" : "$method $handlerName"
operationName "jetty.request"
resourceName "GET ${handler.class.name}"
spanType DDSpanTypes.HTTP_SERVER spanType DDSpanTypes.HTTP_SERVER
errored true errored endpoint.errored
if (parentID != null) {
traceId traceID
parentId parentID
} else {
parent() parent()
}
tags { tags {
"http.url" "http://localhost:$port/" "span.origin.type" handlerName
"http.method" "GET" defaultTags(true)
"span.kind" "server" "$Tags.COMPONENT.key" serverDecorator.component()
"component" "jetty-handler" if (endpoint.errored) {
"span.origin.type" handler.class.name "$Tags.ERROR.key" endpoint.errored
"http.status_code" 500 "error.msg" { it == null || it == EXCEPTION.body }
"peer.hostname" "127.0.0.1" "error.type" { it == null || it == Exception.name }
"peer.ipv4" "127.0.0.1" "error.stack" { it == null || it instanceof String }
"peer.port" Integer
errorTags RuntimeException
defaultTags()
}
}
}
if (errorHandlerCalled.get()) {
// FIXME: This doesn't ever seem to be called.
trace(1, 1) {
span(0) {
serviceName "unnamed-java-app"
operationName "jetty.request"
resourceName "GET ${handler.class.name}"
spanType DDSpanTypes.HTTP_SERVER
errored true
parent()
tags {
"http.url" "http://localhost:$port/"
"http.method" "GET"
"span.kind" "server"
"component" "jetty-handler"
"span.origin.type" handler.class.name
"http.status_code" 500
"peer.hostname" "127.0.0.1"
"peer.ipv4" "127.0.0.1"
"peer.port" Integer
"error" true
defaultTags()
}
}
} }
"$Tags.HTTP_STATUS.key" endpoint.status
"$Tags.HTTP_URL.key" "${endpoint.resolve(address)}"
"$Tags.PEER_HOSTNAME.key" { it == "localhost" || it == "127.0.0.1" }
"$Tags.PEER_PORT.key" Integer
"$Tags.PEER_HOST_IPV4.key" { it == null || it == "127.0.0.1" } // Optional
"$Tags.HTTP_METHOD.key" method
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
} }
} }
} }

View File

@ -0,0 +1,33 @@
import static net.bytebuddy.matcher.ElementMatchers.named;
import com.google.auto.service.AutoService;
import datadog.trace.agent.test.base.HttpServerTestAdvice;
import datadog.trace.agent.tooling.Instrumenter;
import net.bytebuddy.agent.builder.AgentBuilder;
@AutoService(Instrumenter.class)
public class JettyTestInstrumentation implements Instrumenter {
@Override
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
return agentBuilder
// Jetty 8.0
.type(named("org.eclipse.jetty.server.HttpConnection"))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(
named("handleRequest"), HttpServerTestAdvice.ServerEntryAdvice.class.getName()))
// Jetty 8.?
.type(named("org.eclipse.jetty.server.AbstractHttpConnection"))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(
named("headerComplete"),
HttpServerTestAdvice.ServerEntryAdvice.class.getName()))
// Jetty 9
.type(named("org.eclipse.jetty.server.HttpChannel"))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(named("handle"), HttpServerTestAdvice.ServerEntryAdvice.class.getName()));
}
}

View File

@ -12,6 +12,7 @@ import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.nio.NioServerSocketChannel import io.netty.channel.socket.nio.NioServerSocketChannel
import io.netty.handler.codec.http.DefaultFullHttpResponse import io.netty.handler.codec.http.DefaultFullHttpResponse
import io.netty.handler.codec.http.FullHttpResponse import io.netty.handler.codec.http.FullHttpResponse
import io.netty.handler.codec.http.HttpHeaders
import io.netty.handler.codec.http.HttpRequest import io.netty.handler.codec.http.HttpRequest
import io.netty.handler.codec.http.HttpRequestDecoder import io.netty.handler.codec.http.HttpRequestDecoder
import io.netty.handler.codec.http.HttpResponseEncoder import io.netty.handler.codec.http.HttpResponseEncoder
@ -19,7 +20,6 @@ import io.netty.handler.codec.http.HttpResponseStatus
import io.netty.handler.logging.LogLevel import io.netty.handler.logging.LogLevel
import io.netty.handler.logging.LoggingHandler import io.netty.handler.logging.LoggingHandler
import io.netty.util.CharsetUtil import io.netty.util.CharsetUtil
import spock.lang.Shared
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
@ -31,13 +31,11 @@ import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1 import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1
class Netty40ServerTest extends HttpServerTest<NettyHttpServerDecorator> { class Netty40ServerTest extends HttpServerTest<EventLoopGroup, NettyHttpServerDecorator> {
@Shared
EventLoopGroup eventLoopGroup
@Override @Override
void startServer(int port) { EventLoopGroup startServer(int port) {
eventLoopGroup = new NioEventLoopGroup() def eventLoopGroup = new NioEventLoopGroup()
ServerBootstrap bootstrap = new ServerBootstrap() ServerBootstrap bootstrap = new ServerBootstrap()
.group(eventLoopGroup) .group(eventLoopGroup)
@ -62,7 +60,7 @@ class Netty40ServerTest extends HttpServerTest<NettyHttpServerDecorator> {
break break
case REDIRECT: case REDIRECT:
response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status)) response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
response.headers().set(HttpHeaderNames.LOCATION, endpoint.body) response.headers().set(HttpHeaders.Names.LOCATION, endpoint.body)
break break
case EXCEPTION: case EXCEPTION:
throw new Exception(endpoint.body) throw new Exception(endpoint.body)
@ -91,11 +89,13 @@ class Netty40ServerTest extends HttpServerTest<NettyHttpServerDecorator> {
} }
] as ChannelInitializer).channel(NioServerSocketChannel) ] as ChannelInitializer).channel(NioServerSocketChannel)
bootstrap.bind(port).sync() bootstrap.bind(port).sync()
return eventLoopGroup
} }
@Override @Override
void stopServer() { void stopServer(EventLoopGroup server) {
eventLoopGroup?.shutdownGracefully() server?.shutdownGracefully()
} }
@Override @Override

View File

@ -19,7 +19,6 @@ import io.netty.handler.codec.http.HttpServerCodec
import io.netty.handler.logging.LogLevel import io.netty.handler.logging.LogLevel
import io.netty.handler.logging.LoggingHandler import io.netty.handler.logging.LoggingHandler
import io.netty.util.CharsetUtil import io.netty.util.CharsetUtil
import spock.lang.Shared
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
@ -31,13 +30,11 @@ import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1 import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1
class Netty41ServerTest extends HttpServerTest<NettyHttpServerDecorator> { class Netty41ServerTest extends HttpServerTest<EventLoopGroup, NettyHttpServerDecorator> {
@Shared
EventLoopGroup eventLoopGroup
@Override @Override
void startServer(int port) { EventLoopGroup startServer(int port) {
eventLoopGroup = new NioEventLoopGroup() def eventLoopGroup = new NioEventLoopGroup()
ServerBootstrap bootstrap = new ServerBootstrap() ServerBootstrap bootstrap = new ServerBootstrap()
.group(eventLoopGroup) .group(eventLoopGroup)
@ -91,11 +88,13 @@ class Netty41ServerTest extends HttpServerTest<NettyHttpServerDecorator> {
} }
] as ChannelInitializer).channel(NioServerSocketChannel) ] as ChannelInitializer).channel(NioServerSocketChannel)
bootstrap.bind(port).sync() bootstrap.bind(port).sync()
return eventLoopGroup
} }
@Override @Override
void stopServer() { void stopServer(EventLoopGroup server) {
eventLoopGroup?.shutdownGracefully() server?.shutdownGracefully()
} }
@Override @Override

View File

@ -23,6 +23,12 @@ public class StatusSavingHttpServletResponseWrapper extends HttpServletResponseW
super.sendError(status, message); super.sendError(status, message);
} }
@Override
public void sendRedirect(final String location) throws IOException {
status = 302;
super.sendRedirect(location);
}
@Override @Override
public void setStatus(final int status) { public void setStatus(final int status) {
this.status = status; this.status = status;

View File

@ -14,14 +14,13 @@ import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
class JettyServlet2Test extends HttpServerTest<Servlet2Decorator> { class JettyServlet2Test extends HttpServerTest<Server, Servlet2Decorator> {
private static final CONTEXT = "ctx" private static final CONTEXT = "ctx"
private Server jettyServer
@Override @Override
void startServer(int port) { Server startServer(int port) {
jettyServer = new Server(port) def jettyServer = new Server(port)
jettyServer.connectors.each { it.resolveNames = true } // get localhost instead of 127.0.0.1 jettyServer.connectors.each { it.resolveNames = true } // get localhost instead of 127.0.0.1
ServletContextHandler servletContext = new ServletContextHandler(null, "/$CONTEXT") ServletContextHandler servletContext = new ServletContextHandler(null, "/$CONTEXT")
servletContext.errorHandler = new ErrorHandler() { servletContext.errorHandler = new ErrorHandler() {
@ -43,12 +42,14 @@ class JettyServlet2Test extends HttpServerTest<Servlet2Decorator> {
jettyServer.setHandler(servletContext) jettyServer.setHandler(servletContext)
jettyServer.start() jettyServer.start()
return jettyServer
} }
@Override @Override
void stopServer() { void stopServer(Server server) {
jettyServer.stop() server.stop()
jettyServer.destroy() server.destroy()
} }
@Override @Override

View File

@ -13,7 +13,7 @@ import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
abstract class AbstractServlet3Test<CONTEXT> extends HttpServerTest<Servlet3Decorator> { abstract class AbstractServlet3Test<SERVER, CONTEXT> extends HttpServerTest<SERVER, Servlet3Decorator> {
@Override @Override
URI buildAddress() { URI buildAddress() {
return new URI("http://localhost:$port/$context/") return new URI("http://localhost:$port/$context/")

View File

@ -10,7 +10,6 @@ import org.apache.catalina.core.ApplicationFilterChain
import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.ErrorHandler import org.eclipse.jetty.server.handler.ErrorHandler
import org.eclipse.jetty.servlet.ServletContextHandler import org.eclipse.jetty.servlet.ServletContextHandler
import spock.lang.Shared
import static datadog.trace.agent.test.asserts.TraceAssert.assertTrace import static datadog.trace.agent.test.asserts.TraceAssert.assertTrace
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED
@ -20,10 +19,7 @@ import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRE
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
abstract class JettyServlet3Test extends AbstractServlet3Test<ServletContextHandler> { abstract class JettyServlet3Test extends AbstractServlet3Test<Server, ServletContextHandler> {
@Shared
private Server jettyServer
@Override @Override
boolean testNotFound() { boolean testNotFound() {
@ -31,8 +27,8 @@ abstract class JettyServlet3Test extends AbstractServlet3Test<ServletContextHand
} }
@Override @Override
void startServer(int port) { Server startServer(int port) {
jettyServer = new Server(port) def jettyServer = new Server(port)
jettyServer.connectors.each { jettyServer.connectors.each {
if (it.hasProperty("resolveNames")) { if (it.hasProperty("resolveNames")) {
it.resolveNames = true // get localhost instead of 127.0.0.1 it.resolveNames = true // get localhost instead of 127.0.0.1
@ -52,14 +48,13 @@ abstract class JettyServlet3Test extends AbstractServlet3Test<ServletContextHand
jettyServer.start() jettyServer.start()
System.out.println( return jettyServer
"Jetty server: http://localhost:" + port + "/")
} }
@Override @Override
void stopServer() { void stopServer(Server server) {
jettyServer.stop() server.stop()
jettyServer.destroy() server.destroy()
} }
@Override @Override

View File

@ -15,7 +15,6 @@ import org.apache.catalina.startup.Tomcat
import org.apache.catalina.valves.ErrorReportValve import org.apache.catalina.valves.ErrorReportValve
import org.apache.tomcat.JarScanFilter import org.apache.tomcat.JarScanFilter
import org.apache.tomcat.JarScanType import org.apache.tomcat.JarScanType
import spock.lang.Shared
import static datadog.trace.agent.test.asserts.TraceAssert.assertTrace import static datadog.trace.agent.test.asserts.TraceAssert.assertTrace
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED
@ -26,14 +25,11 @@ import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRE
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
abstract class TomcatServlet3Test extends AbstractServlet3Test<Context> { abstract class TomcatServlet3Test extends AbstractServlet3Test<Tomcat, Context> {
@Shared
Tomcat tomcatServer
@Override @Override
void startServer(int port) { Tomcat startServer(int port) {
tomcatServer = new Tomcat() def tomcatServer = new Tomcat()
def baseDir = Files.createTempDir() def baseDir = Files.createTempDir()
baseDir.deleteOnExit() baseDir.deleteOnExit()
@ -62,14 +58,14 @@ abstract class TomcatServlet3Test extends AbstractServlet3Test<Context> {
(tomcatServer.host as StandardHost).errorReportValveClass = ErrorHandlerValve.name (tomcatServer.host as StandardHost).errorReportValveClass = ErrorHandlerValve.name
tomcatServer.start() tomcatServer.start()
System.out.println(
"Tomcat server: http://" + tomcatServer.getHost().getName() + ":" + port + "/") return tomcatServer
} }
@Override @Override
void stopServer() { void stopServer(Tomcat server) {
tomcatServer.stop() server.stop()
tomcatServer.destroy() server.destroy()
} }
@Override @Override

View File

@ -34,7 +34,7 @@ class SpringWebfluxTest extends AgentTestRunner {
@LocalServerPort @LocalServerPort
private int port private int port
OkHttpClient client = OkHttpUtils.client() OkHttpClient client = OkHttpUtils.client(true)
def "Basic GET test #testName"() { def "Basic GET test #testName"() {
setup: setup:

View File

@ -12,7 +12,6 @@ import io.vertx.core.Vertx
import io.vertx.core.VertxOptions import io.vertx.core.VertxOptions
import io.vertx.core.json.JsonObject import io.vertx.core.json.JsonObject
import io.vertx.ext.web.Router import io.vertx.ext.web.Router
import spock.lang.Shared
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
@ -21,15 +20,12 @@ import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
class VertxHttpServerTest extends HttpServerTest<NettyHttpServerDecorator> { class VertxHttpServerTest extends HttpServerTest<Vertx, NettyHttpServerDecorator> {
public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port" public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"
@Shared
Vertx server
@Override @Override
void startServer(int port) { Vertx startServer(int port) {
server = Vertx.vertx(new VertxOptions() def server = Vertx.vertx(new VertxOptions()
// Useful for debugging: // Useful for debugging:
// .setBlockedThreadCheckInterval(Integer.MAX_VALUE) // .setBlockedThreadCheckInterval(Integer.MAX_VALUE)
.setClusterPort(port)) .setClusterPort(port))
@ -45,6 +41,7 @@ class VertxHttpServerTest extends HttpServerTest<NettyHttpServerDecorator> {
} }
future.get() future.get()
return server
} }
protected Class<io.vertx.reactivex.core.AbstractVerticle> verticle() { protected Class<io.vertx.reactivex.core.AbstractVerticle> verticle() {
@ -52,7 +49,7 @@ class VertxHttpServerTest extends HttpServerTest<NettyHttpServerDecorator> {
} }
@Override @Override
void stopServer() { void stopServer(Vertx server) {
server.close() server.close()
} }
@ -85,7 +82,7 @@ class VertxHttpServerTest extends HttpServerTest<NettyHttpServerDecorator> {
} }
router.route(REDIRECT.path).handler { ctx -> router.route(REDIRECT.path).handler { ctx ->
controller(REDIRECT) { controller(REDIRECT) {
ctx.response().setStatusCode(REDIRECT.status).putHeader("location", REDIRECT.body) ctx.response().setStatusCode(REDIRECT.status).putHeader("location", REDIRECT.body).end()
} }
} }
router.route(ERROR.path).handler { ctx -> router.route(ERROR.path).handler { ctx ->

View File

@ -56,7 +56,7 @@ class VertxRxCircuitBreakerHttpServerTest extends VertxHttpServerTest {
} }
HttpServerTest.ServerEndpoint endpoint = it.result() HttpServerTest.ServerEndpoint endpoint = it.result()
controller(endpoint) { controller(endpoint) {
ctx.response().setStatusCode(endpoint.status).putHeader("location", endpoint.body) ctx.response().setStatusCode(endpoint.status).putHeader("location", endpoint.body).end()
} }
} }
} }

View File

@ -31,7 +31,7 @@ class VertxRxHttpServerTest extends VertxHttpServerTest {
} }
router.route(REDIRECT.path).handler { ctx -> router.route(REDIRECT.path).handler { ctx ->
controller(REDIRECT) { controller(REDIRECT) {
ctx.response().setStatusCode(REDIRECT.status).putHeader("location", REDIRECT.body) ctx.response().setStatusCode(REDIRECT.status).putHeader("location", REDIRECT.body).end()
} }
} }
router.route(ERROR.path).handler { ctx -> router.route(ERROR.path).handler { ctx ->

View File

@ -24,14 +24,17 @@ import static datadog.trace.agent.test.asserts.TraceAssert.assertTrace
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
import static org.junit.Assume.assumeTrue import static org.junit.Assume.assumeTrue
@Unroll @Unroll
abstract class HttpServerTest<DECORATOR extends HttpServerDecorator> extends AgentTestRunner { abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> extends AgentTestRunner {
@Shared
SERVER server
@Shared @Shared
OkHttpClient client = OkHttpUtils.client() OkHttpClient client = OkHttpUtils.client()
@Shared @Shared
@ -47,18 +50,19 @@ abstract class HttpServerTest<DECORATOR extends HttpServerDecorator> extends Age
DECORATOR serverDecorator = decorator() DECORATOR serverDecorator = decorator()
def setupSpec() { def setupSpec() {
startServer(port) server = startServer(port)
println "Http server started at: http://localhost:$port/" println getClass().name + " http server started at: http://localhost:$port/"
} }
abstract void startServer(int port) abstract SERVER startServer(int port)
def cleanupSpec() { def cleanupSpec() {
stopServer() stopServer(server)
println "Http server stopped at: http://localhost:$port/" server = null
println getClass().name + " http server stopped at: http://localhost:$port/"
} }
abstract void stopServer() abstract void stopServer(SERVER server)
abstract DECORATOR decorator() abstract DECORATOR decorator()
@ -78,7 +82,7 @@ abstract class HttpServerTest<DECORATOR extends HttpServerDecorator> extends Age
enum ServerEndpoint { enum ServerEndpoint {
SUCCESS("success", 200, "success"), SUCCESS("success", 200, "success"),
REDIRECT("redirect", 302, null), REDIRECT("redirect", 302, "/redirected"),
ERROR("error", 500, "controller error"), ERROR("error", 500, "controller error"),
EXCEPTION("exception", 500, "controller exception"), EXCEPTION("exception", 500, "controller exception"),
NOT_FOUND("notFound", 404, "not found"), NOT_FOUND("notFound", 404, "not found"),
@ -184,6 +188,30 @@ abstract class HttpServerTest<DECORATOR extends HttpServerDecorator> extends Age
body = null body = null
} }
def "test redirect"() {
setup:
def request = request(REDIRECT, method, body).build()
def response = client.newCall(request).execute()
expect:
response.code() == REDIRECT.status
response.header("location") == REDIRECT.body ||
response.header("location") == "${address.resolve(REDIRECT.body)}"
response.body().contentLength() < 1
and:
cleanAndAssertTraces(1) {
trace(0, 2) {
serverSpan(it, 0, null, null, method, REDIRECT)
controllerSpan(it, 1, span(0))
}
}
where:
method = "GET"
body = null
}
def "test error"() { def "test error"() {
setup: setup:
def request = request(ERROR, method, body).build() def request = request(ERROR, method, body).build()

View File

@ -14,7 +14,7 @@ class OkHttpUtils {
.readTimeout(1, unit) .readTimeout(1, unit)
} }
static client() { static client(boolean followRedirects = false) {
clientBuilder().build() clientBuilder().followRedirects(followRedirects).build()
} }
} }