Merge pull request #937 from DataDog/tyler/http-server-testing
Servlet 2, Akka Http, Vert.x testing - Migrate to HttpServerTest
This commit is contained in:
commit
20dd7150a3
|
@ -104,6 +104,7 @@ public final class AkkaHttpServerInstrumentation extends Instrumenter.Default {
|
||||||
final Scope scope =
|
final Scope scope =
|
||||||
GlobalTracer.get()
|
GlobalTracer.get()
|
||||||
.buildSpan("akka-http.request")
|
.buildSpan("akka-http.request")
|
||||||
|
.ignoreActiveSpan()
|
||||||
.asChildOf(extractedContext)
|
.asChildOf(extractedContext)
|
||||||
.startActive(false);
|
.startActive(false);
|
||||||
|
|
||||||
|
|
|
@ -1,188 +1,91 @@
|
||||||
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.api.DDSpanTypes
|
import datadog.trace.api.DDSpanTypes
|
||||||
|
import datadog.trace.instrumentation.akkahttp.AkkaHttpServerDecorator
|
||||||
import io.opentracing.tag.Tags
|
import io.opentracing.tag.Tags
|
||||||
import okhttp3.Request
|
|
||||||
import spock.lang.Shared
|
|
||||||
|
|
||||||
class AkkaHttpServerInstrumentationTest extends AgentTestRunner {
|
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
|
||||||
|
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
|
||||||
|
|
||||||
@Shared
|
abstract class AkkaHttpServerInstrumentationTest extends HttpServerTest<AkkaHttpServerDecorator> {
|
||||||
int asyncPort
|
|
||||||
@Shared
|
|
||||||
int syncPort
|
|
||||||
|
|
||||||
@Shared
|
@Override
|
||||||
def client = OkHttpUtils.client()
|
AkkaHttpServerDecorator decorator() {
|
||||||
|
return AkkaHttpServerDecorator.DECORATE
|
||||||
def setupSpec() {
|
|
||||||
AkkaHttpTestAsyncWebServer.start()
|
|
||||||
asyncPort = AkkaHttpTestAsyncWebServer.port()
|
|
||||||
AkkaHttpTestSyncWebServer.start()
|
|
||||||
syncPort = AkkaHttpTestSyncWebServer.port()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def cleanupSpec() {
|
@Override
|
||||||
AkkaHttpTestAsyncWebServer.stop()
|
String expectedOperationName() {
|
||||||
AkkaHttpTestSyncWebServer.stop()
|
return "akka-http.request"
|
||||||
}
|
}
|
||||||
|
|
||||||
def "#server 200 request trace"() {
|
@Override
|
||||||
setup:
|
boolean testExceptionBody() {
|
||||||
def request = new Request.Builder()
|
false
|
||||||
.url("http://localhost:$port/test")
|
}
|
||||||
.header("x-datadog-trace-id", "123")
|
|
||||||
.header("x-datadog-parent-id", "456")
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
def response = client.newCall(request).execute()
|
|
||||||
|
|
||||||
expect:
|
// FIXME: This doesn't work because we don't support bindAndHandle.
|
||||||
response.code() == 200
|
// @Override
|
||||||
|
// void startServer(int port) {
|
||||||
|
// AkkaHttpTestWebServer.start(port)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// void stopServer() {
|
||||||
|
// AkkaHttpTestWebServer.stop()
|
||||||
|
// }
|
||||||
|
|
||||||
assertTraces(1) {
|
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
|
||||||
trace(0, 2) {
|
trace.span(index) {
|
||||||
span(0) {
|
serviceName expectedServiceName()
|
||||||
traceId "123"
|
operationName expectedOperationName()
|
||||||
parentId "456"
|
resourceName endpoint.status == 404 ? "404" : "$method ${endpoint.resolve(address).path}"
|
||||||
serviceName "unnamed-java-app"
|
spanType DDSpanTypes.HTTP_SERVER
|
||||||
operationName "akka-http.request"
|
errored endpoint.errored
|
||||||
resourceName "GET /test"
|
if (parentID != null) {
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
traceId traceID
|
||||||
errored false
|
parentId parentID
|
||||||
tags {
|
} else {
|
||||||
defaultTags(true)
|
parent()
|
||||||
"$Tags.HTTP_STATUS.key" 200
|
}
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/test"
|
tags {
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
defaultTags(true)
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
"$Tags.COMPONENT.key" serverDecorator.component()
|
||||||
"$Tags.COMPONENT.key" "akka-http-server"
|
if (endpoint.errored) {
|
||||||
}
|
"$Tags.ERROR.key" endpoint.errored
|
||||||
}
|
"error.msg" { it == null || it == EXCEPTION.body }
|
||||||
span(1) {
|
"error.type" { it == null || it == Exception.name }
|
||||||
childOf span(0)
|
"error.stack" { it == null || it instanceof String }
|
||||||
assert span(1).operationName.endsWith('.tracedMethod')
|
|
||||||
}
|
}
|
||||||
|
"$Tags.HTTP_STATUS.key" endpoint.status
|
||||||
|
"$Tags.HTTP_URL.key" "${endpoint.resolve(address)}"
|
||||||
|
"$Tags.HTTP_METHOD.key" method
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
where:
|
}
|
||||||
server | port
|
|
||||||
"async" | asyncPort
|
class AkkaHttpServerInstrumentationTestSync extends AkkaHttpServerInstrumentationTest {
|
||||||
"sync" | syncPort
|
@Override
|
||||||
}
|
void startServer(int port) {
|
||||||
|
AkkaHttpTestSyncWebServer.start(port)
|
||||||
def "#server exceptions trace for #endpoint"() {
|
}
|
||||||
setup:
|
|
||||||
def request = new Request.Builder()
|
@Override
|
||||||
.url("http://localhost:$port/$endpoint")
|
void stopServer() {
|
||||||
.get()
|
AkkaHttpTestSyncWebServer.stop()
|
||||||
.build()
|
}
|
||||||
def response = client.newCall(request).execute()
|
}
|
||||||
|
|
||||||
expect:
|
class AkkaHttpServerInstrumentationTestAsync extends AkkaHttpServerInstrumentationTest {
|
||||||
response.code() == 500
|
@Override
|
||||||
|
void startServer(int port) {
|
||||||
assertTraces(1) {
|
AkkaHttpTestAsyncWebServer.start(port)
|
||||||
trace(0, 1) {
|
}
|
||||||
span(0) {
|
|
||||||
serviceName "unnamed-java-app"
|
@Override
|
||||||
operationName "akka-http.request"
|
void stopServer() {
|
||||||
resourceName "GET /$endpoint"
|
AkkaHttpTestAsyncWebServer.stop()
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
|
||||||
errored true
|
|
||||||
tags {
|
|
||||||
defaultTags()
|
|
||||||
"$Tags.HTTP_STATUS.key" 500
|
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/$endpoint"
|
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
|
||||||
"$Tags.COMPONENT.key" "akka-http-server"
|
|
||||||
errorTags RuntimeException, errorMessage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
where:
|
|
||||||
server | port | endpoint | errorMessage
|
|
||||||
"async" | asyncPort | "throw-handler" | "Oh no handler"
|
|
||||||
"async" | asyncPort | "throw-callback" | "Oh no callback"
|
|
||||||
"sync" | syncPort | "throw-handler" | "Oh no handler"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "#server 5xx trace"() {
|
|
||||||
setup:
|
|
||||||
def request = new Request.Builder()
|
|
||||||
.url("http://localhost:$port/server-error")
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
def response = client.newCall(request).execute()
|
|
||||||
|
|
||||||
expect:
|
|
||||||
response.code() == 500
|
|
||||||
|
|
||||||
assertTraces(1) {
|
|
||||||
trace(0, 1) {
|
|
||||||
span(0) {
|
|
||||||
serviceName "unnamed-java-app"
|
|
||||||
operationName "akka-http.request"
|
|
||||||
resourceName "GET /server-error"
|
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
|
||||||
errored true
|
|
||||||
tags {
|
|
||||||
defaultTags()
|
|
||||||
"$Tags.HTTP_STATUS.key" 500
|
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/server-error"
|
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
|
||||||
"$Tags.COMPONENT.key" "akka-http-server"
|
|
||||||
"$Tags.ERROR.key" true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
where:
|
|
||||||
server | port
|
|
||||||
"async" | asyncPort
|
|
||||||
"sync" | syncPort
|
|
||||||
}
|
|
||||||
|
|
||||||
def "#server 4xx trace"() {
|
|
||||||
setup:
|
|
||||||
def request = new Request.Builder()
|
|
||||||
.url("http://localhost:$port/not-found")
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
def response = client.newCall(request).execute()
|
|
||||||
|
|
||||||
expect:
|
|
||||||
response.code() == 404
|
|
||||||
|
|
||||||
assertTraces(1) {
|
|
||||||
trace(0, 1) {
|
|
||||||
span(0) {
|
|
||||||
serviceName "unnamed-java-app"
|
|
||||||
operationName "akka-http.request"
|
|
||||||
resourceName "404"
|
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
|
||||||
errored false
|
|
||||||
tags {
|
|
||||||
defaultTags()
|
|
||||||
"$Tags.HTTP_STATUS.key" 404
|
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/not-found"
|
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
|
||||||
"$Tags.COMPONENT.key" "akka-http-server"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
where:
|
|
||||||
server | port
|
|
||||||
"async" | asyncPort
|
|
||||||
"sync" | syncPort
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
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 AkkaHttpTestInstrumentation implements Instrumenter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
|
||||||
|
return agentBuilder
|
||||||
|
.type(named("akka.http.impl.engine.server.HttpServerBluePrint$PrepareRequests$$anon$1"))
|
||||||
|
.transform(
|
||||||
|
new AgentBuilder.Transformer.ForAdvice()
|
||||||
|
.advice(named("onPush"), HttpServerTestAdvice.ServerEntryAdvice.class.getName()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import akka.http.scaladsl.Http
|
||||||
|
import akka.http.scaladsl.Http.ServerBinding
|
||||||
|
import akka.http.scaladsl.model.HttpMethods.GET
|
||||||
|
import akka.http.scaladsl.model._
|
||||||
|
import akka.stream.ActorMaterializer
|
||||||
|
import datadog.trace.agent.test.base.HttpServerTest
|
||||||
|
import datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint._
|
||||||
|
import groovy.lang.Closure
|
||||||
|
|
||||||
|
import scala.concurrent.{Await, Future}
|
||||||
|
|
||||||
|
object AkkaHttpTestAsyncWebServer {
|
||||||
|
implicit val system = ActorSystem("my-system")
|
||||||
|
implicit val materializer = ActorMaterializer()
|
||||||
|
// needed for the future flatMap/onComplete in the end
|
||||||
|
implicit val executionContext = system.dispatcher
|
||||||
|
val asyncHandler: HttpRequest => Future[HttpResponse] = {
|
||||||
|
case HttpRequest(GET, uri: Uri, _, _, _) => {
|
||||||
|
Future {
|
||||||
|
val endpoint = HttpServerTest.ServerEndpoint.forPath(uri.path.toString())
|
||||||
|
HttpServerTest.controller(endpoint, new Closure[HttpResponse]() {
|
||||||
|
def doCall(): HttpResponse = {
|
||||||
|
val resp = HttpResponse(status = endpoint.getStatus) //.withHeaders(headers.Type)resp.contentType = "text/plain"
|
||||||
|
endpoint match {
|
||||||
|
case SUCCESS => resp.withEntity(endpoint.getBody)
|
||||||
|
case REDIRECT => resp.withHeaders(headers.Location(endpoint.getBody))
|
||||||
|
case ERROR => resp.withEntity(endpoint.getBody)
|
||||||
|
case EXCEPTION => throw new Exception(endpoint.getBody)
|
||||||
|
case _ => HttpResponse(status = NOT_FOUND.getStatus).withEntity(NOT_FOUND.getBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var binding: ServerBinding = null
|
||||||
|
|
||||||
|
def start(port: Int): Unit = synchronized {
|
||||||
|
if (null == binding) {
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
binding = Await.result(Http().bindAndHandleAsync(asyncHandler, "localhost", port), 10 seconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def stop(): Unit = synchronized {
|
||||||
|
if (null != binding) {
|
||||||
|
binding.unbind()
|
||||||
|
system.terminate()
|
||||||
|
binding = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import akka.http.scaladsl.Http
|
||||||
|
import akka.http.scaladsl.Http.ServerBinding
|
||||||
|
import akka.http.scaladsl.model.HttpMethods.GET
|
||||||
|
import akka.http.scaladsl.model._
|
||||||
|
import akka.stream.ActorMaterializer
|
||||||
|
import datadog.trace.agent.test.base.HttpServerTest
|
||||||
|
import datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint._
|
||||||
|
import groovy.lang.Closure
|
||||||
|
|
||||||
|
import scala.concurrent.Await
|
||||||
|
|
||||||
|
object AkkaHttpTestSyncWebServer {
|
||||||
|
implicit val system = ActorSystem("my-system")
|
||||||
|
implicit val materializer = ActorMaterializer()
|
||||||
|
// needed for the future flatMap/onComplete in the end
|
||||||
|
implicit val executionContext = system.dispatcher
|
||||||
|
val syncHandler: HttpRequest => HttpResponse = {
|
||||||
|
case HttpRequest(GET, uri: Uri, _, _, _) => {
|
||||||
|
val endpoint = HttpServerTest.ServerEndpoint.forPath(uri.path.toString())
|
||||||
|
HttpServerTest.controller(endpoint, new Closure[HttpResponse]() {
|
||||||
|
def doCall(): HttpResponse = {
|
||||||
|
val resp = HttpResponse(status = endpoint.getStatus)
|
||||||
|
endpoint match {
|
||||||
|
case SUCCESS => resp.withEntity(endpoint.getBody)
|
||||||
|
case REDIRECT => resp.withHeaders(headers.Location(endpoint.getBody))
|
||||||
|
case ERROR => resp.withEntity(endpoint.getBody)
|
||||||
|
case EXCEPTION => throw new Exception(endpoint.getBody)
|
||||||
|
case _ => HttpResponse(status = NOT_FOUND.getStatus).withEntity(NOT_FOUND.getBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var binding: ServerBinding = null
|
||||||
|
|
||||||
|
def start(port: Int): Unit = synchronized {
|
||||||
|
if (null == binding) {
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
binding = Await.result(Http().bindAndHandleSync(syncHandler, "localhost", port), 10 seconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def stop(): Unit = synchronized {
|
||||||
|
if (null != binding) {
|
||||||
|
binding.unbind()
|
||||||
|
system.terminate()
|
||||||
|
binding = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,48 +1,43 @@
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import akka.http.scaladsl.Http
|
import akka.http.scaladsl.Http
|
||||||
import akka.http.scaladsl.Http.ServerBinding
|
import akka.http.scaladsl.Http.ServerBinding
|
||||||
import akka.http.scaladsl.model.HttpMethods.GET
|
|
||||||
import akka.http.scaladsl.model._
|
import akka.http.scaladsl.model._
|
||||||
|
import akka.http.scaladsl.server.Directives._
|
||||||
|
import akka.http.scaladsl.server.ExceptionHandler
|
||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
import datadog.trace.agent.test.utils.PortUtils
|
import datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint._
|
||||||
import datadog.trace.api.Trace
|
|
||||||
|
|
||||||
import scala.concurrent.{Await, Future}
|
import scala.concurrent.Await
|
||||||
|
|
||||||
object AkkaHttpTestAsyncWebServer {
|
// FIXME: This doesn't work because we don't support bindAndHandle.
|
||||||
val port = PortUtils.randomOpenPort()
|
object AkkaHttpTestWebServer {
|
||||||
implicit val system = ActorSystem("my-system")
|
implicit val system = ActorSystem("my-system")
|
||||||
implicit val materializer = ActorMaterializer()
|
implicit val materializer = ActorMaterializer()
|
||||||
// needed for the future flatMap/onComplete in the end
|
// needed for the future flatMap/onComplete in the end
|
||||||
implicit val executionContext = system.dispatcher
|
implicit val executionContext = system.dispatcher
|
||||||
val asyncHandler: HttpRequest => Future[HttpResponse] = {
|
|
||||||
case HttpRequest(GET, Uri.Path("/test"), _, _, _) =>
|
val exceptionHandler = ExceptionHandler {
|
||||||
Future {
|
case ex: Exception => complete(HttpResponse(status = EXCEPTION.getStatus).withEntity(ex.getMessage))
|
||||||
tracedMethod()
|
}
|
||||||
HttpResponse(entity = "Hello unit test.")
|
|
||||||
}
|
val route = { //handleExceptions(exceptionHandler) {
|
||||||
case HttpRequest(GET, Uri.Path("/throw-handler"), _, _, _) =>
|
path(SUCCESS.rawPath) {
|
||||||
sys.error("Oh no handler")
|
complete(HttpResponse(status = SUCCESS.getStatus).withEntity(SUCCESS.getBody))
|
||||||
case HttpRequest(GET, Uri.Path("/throw-callback"), _, _, _) =>
|
} ~ path(REDIRECT.rawPath) {
|
||||||
Future {
|
redirect(Uri(REDIRECT.getBody), StatusCodes.Found)
|
||||||
sys.error("Oh no callback")
|
} ~ path(ERROR.rawPath) {
|
||||||
}
|
complete(HttpResponse(status = ERROR.getStatus).withEntity(ERROR.getBody))
|
||||||
case HttpRequest(GET, Uri.Path("/server-error"), _, _, _) =>
|
} ~ path(EXCEPTION.rawPath) {
|
||||||
Future {
|
failWith(new Exception(EXCEPTION.getBody))
|
||||||
HttpResponse(entity = "Error unit test.", status = StatusCodes.InternalServerError)
|
}
|
||||||
}
|
|
||||||
case _ =>
|
|
||||||
Future {
|
|
||||||
HttpResponse(entity = "Not found unit test.", status = StatusCodes.NotFound)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var binding: ServerBinding = null
|
private var binding: ServerBinding = null
|
||||||
|
|
||||||
def start(): Unit = synchronized {
|
def start(port: Int): Unit = synchronized {
|
||||||
if (null == binding) {
|
if (null == binding) {
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
binding = Await.result(Http().bindAndHandleAsync(asyncHandler, "localhost", port), 10 seconds)
|
binding = Await.result(Http().bindAndHandle(route, "localhost", port), 10 seconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,48 +48,4 @@ object AkkaHttpTestAsyncWebServer {
|
||||||
binding = null
|
binding = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Trace
|
|
||||||
def tracedMethod(): Unit = {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object AkkaHttpTestSyncWebServer {
|
|
||||||
val port = PortUtils.randomOpenPort()
|
|
||||||
implicit val system = ActorSystem("my-system")
|
|
||||||
implicit val materializer = ActorMaterializer()
|
|
||||||
// needed for the future flatMap/onComplete in the end
|
|
||||||
implicit val executionContext = system.dispatcher
|
|
||||||
val syncHandler: HttpRequest => HttpResponse = {
|
|
||||||
case HttpRequest(GET, Uri.Path("/test"), _, _, _) =>
|
|
||||||
tracedMethod()
|
|
||||||
HttpResponse(entity = "Hello unit test.")
|
|
||||||
case HttpRequest(GET, Uri.Path("/throw-handler"), _, _, _) =>
|
|
||||||
sys.error("Oh no handler")
|
|
||||||
case HttpRequest(GET, Uri.Path("/server-error"), _, _, _) =>
|
|
||||||
HttpResponse(entity = "Error unit test.", status = StatusCodes.InternalServerError)
|
|
||||||
case _ =>
|
|
||||||
HttpResponse(entity = "Not found unit test.", status = StatusCodes.NotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var binding: ServerBinding = null
|
|
||||||
|
|
||||||
def start(): Unit = synchronized {
|
|
||||||
if (null == binding) {
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
binding = Await.result(Http().bindAndHandleSync(syncHandler, "localhost", port), 10 seconds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def stop(): Unit = synchronized {
|
|
||||||
if (null != binding) {
|
|
||||||
binding.unbind()
|
|
||||||
system.terminate()
|
|
||||||
binding = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Trace
|
|
||||||
def tracedMethod(): Unit = {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,12 @@ muzzle {
|
||||||
versions = "[4.0.0.Final,4.1.0.Final)"
|
versions = "[4.0.0.Final,4.1.0.Final)"
|
||||||
assertInverse = true
|
assertInverse = true
|
||||||
}
|
}
|
||||||
|
pass {
|
||||||
|
group = "io.vertx"
|
||||||
|
module = "vertx-core"
|
||||||
|
versions = "[2.0.0,3.3.0)"
|
||||||
|
assertInverse = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'org.unbroken-dome.test-sets'
|
apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
|
|
|
@ -18,6 +18,12 @@ muzzle {
|
||||||
versions = "[4.1.0.Final,)"
|
versions = "[4.1.0.Final,)"
|
||||||
assertInverse = true
|
assertInverse = true
|
||||||
}
|
}
|
||||||
|
pass {
|
||||||
|
group = "io.vertx"
|
||||||
|
module = "vertx-core"
|
||||||
|
versions = "[3.3.0,)"
|
||||||
|
assertInverse = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'org.unbroken-dome.test-sets'
|
apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
|
|
|
@ -11,6 +11,14 @@ muzzle {
|
||||||
|
|
||||||
apply from: "${rootDir}/gradle/java.gradle"
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
|
||||||
|
apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
|
|
||||||
|
testSets {
|
||||||
|
latestDepTest {
|
||||||
|
dirName = 'test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.3'
|
compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.3'
|
||||||
|
|
||||||
|
@ -26,4 +34,7 @@ dependencies {
|
||||||
}
|
}
|
||||||
testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version: '7.0.0.v20091005'
|
testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version: '7.0.0.v20091005'
|
||||||
testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '7.0.0.v20091005'
|
testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '7.0.0.v20091005'
|
||||||
|
|
||||||
|
latestDepTestCompile group: 'org.eclipse.jetty', name: 'jetty-server', version: '7.+'
|
||||||
|
latestDepTestCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '7.+'
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,10 @@ public abstract class AbstractServlet2Instrumentation extends Instrumenter.Defau
|
||||||
"datadog.trace.agent.decorator.BaseDecorator",
|
"datadog.trace.agent.decorator.BaseDecorator",
|
||||||
"datadog.trace.agent.decorator.ServerDecorator",
|
"datadog.trace.agent.decorator.ServerDecorator",
|
||||||
"datadog.trace.agent.decorator.HttpServerDecorator",
|
"datadog.trace.agent.decorator.HttpServerDecorator",
|
||||||
|
packageName + ".Servlet2Decorator",
|
||||||
packageName + ".HttpServletRequestExtractAdapter",
|
packageName + ".HttpServletRequestExtractAdapter",
|
||||||
packageName + ".HttpServletRequestExtractAdapter$MultivaluedMapFlatIterator",
|
packageName + ".HttpServletRequestExtractAdapter$MultivaluedMapFlatIterator",
|
||||||
packageName + ".Servlet2Decorator",
|
packageName + ".StatusSavingHttpServletResponseWrapper",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,23 +8,35 @@ import io.opentracing.Scope;
|
||||||
import io.opentracing.Span;
|
import io.opentracing.Span;
|
||||||
import io.opentracing.SpanContext;
|
import io.opentracing.SpanContext;
|
||||||
import io.opentracing.propagation.Format;
|
import io.opentracing.propagation.Format;
|
||||||
|
import io.opentracing.tag.Tags;
|
||||||
import io.opentracing.util.GlobalTracer;
|
import io.opentracing.util.GlobalTracer;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
||||||
|
|
||||||
public class Servlet2Advice {
|
public class Servlet2Advice {
|
||||||
|
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 servlet, @Advice.Argument(0) final ServletRequest req) {
|
@Advice.This final Object servlet,
|
||||||
if (GlobalTracer.get().activeSpan() != null || !(req instanceof HttpServletRequest)) {
|
@Advice.Argument(0) final ServletRequest req,
|
||||||
|
@Advice.Argument(value = 1, readOnly = false, typing = Assigner.Typing.DYNAMIC)
|
||||||
|
ServletResponse resp) {
|
||||||
|
final Object spanAttr = req.getAttribute(SERVLET_SPAN);
|
||||||
|
if (!(req instanceof HttpServletRequest) || spanAttr != null) {
|
||||||
// Tracing might already be applied by the FilterChain. If so ignore this.
|
// Tracing might already be applied by the FilterChain. If so ignore this.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resp instanceof HttpServletResponse) {
|
||||||
|
resp = new StatusSavingHttpServletResponseWrapper((HttpServletResponse) resp);
|
||||||
|
}
|
||||||
|
|
||||||
final HttpServletRequest httpServletRequest = (HttpServletRequest) req;
|
final HttpServletRequest httpServletRequest = (HttpServletRequest) req;
|
||||||
final SpanContext extractedContext =
|
final SpanContext extractedContext =
|
||||||
GlobalTracer.get()
|
GlobalTracer.get()
|
||||||
|
@ -35,6 +47,7 @@ public class Servlet2Advice {
|
||||||
final Scope scope =
|
final Scope scope =
|
||||||
GlobalTracer.get()
|
GlobalTracer.get()
|
||||||
.buildSpan("servlet.request")
|
.buildSpan("servlet.request")
|
||||||
|
.ignoreActiveSpan()
|
||||||
.asChildOf(extractedContext)
|
.asChildOf(extractedContext)
|
||||||
.withTag("span.origin.type", servlet.getClass().getName())
|
.withTag("span.origin.type", servlet.getClass().getName())
|
||||||
.startActive(true);
|
.startActive(true);
|
||||||
|
@ -47,6 +60,8 @@ public class Servlet2Advice {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,9 +83,18 @@ public class Servlet2Advice {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scope != null) {
|
if (scope != null) {
|
||||||
DECORATE.onResponse(scope.span(), response);
|
final Span span = scope.span();
|
||||||
DECORATE.onError(scope.span(), throwable);
|
DECORATE.onResponse(span, response);
|
||||||
DECORATE.beforeFinish(scope.span());
|
if (throwable != null) {
|
||||||
|
if (response instanceof StatusSavingHttpServletResponseWrapper
|
||||||
|
&& ((StatusSavingHttpServletResponseWrapper) response).status
|
||||||
|
== HttpServletResponse.SC_OK) {
|
||||||
|
// exception was thrown but status code wasn't set
|
||||||
|
Tags.HTTP_STATUS.set(span, 500);
|
||||||
|
}
|
||||||
|
DECORATE.onError(span, throwable);
|
||||||
|
}
|
||||||
|
DECORATE.beforeFinish(span);
|
||||||
|
|
||||||
if (scope instanceof TraceScope) {
|
if (scope instanceof TraceScope) {
|
||||||
((TraceScope) scope).setAsyncPropagation(false);
|
((TraceScope) scope).setAsyncPropagation(false);
|
||||||
|
|
|
@ -43,13 +43,18 @@ public class Servlet2Decorator
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Integer peerPort(final HttpServletRequest httpServletRequest) {
|
protected Integer peerPort(final HttpServletRequest httpServletRequest) {
|
||||||
|
// HttpServletResponse doesn't have accessor for remote port.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Integer status(final ServletResponse httpServletResponse) {
|
protected Integer status(final ServletResponse httpServletResponse) {
|
||||||
// HttpServletResponse doesn't have accessor for status code.
|
if (httpServletResponse instanceof StatusSavingHttpServletResponseWrapper) {
|
||||||
return null;
|
return ((StatusSavingHttpServletResponseWrapper) httpServletResponse).status;
|
||||||
|
} else {
|
||||||
|
// HttpServletResponse doesn't have accessor for status code.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package datadog.trace.instrumentation.servlet2;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletResponseWrapper;
|
||||||
|
|
||||||
|
public class StatusSavingHttpServletResponseWrapper extends HttpServletResponseWrapper {
|
||||||
|
public int status = 200;
|
||||||
|
|
||||||
|
public StatusSavingHttpServletResponseWrapper(final HttpServletResponse response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendError(final int status) throws IOException {
|
||||||
|
this.status = status;
|
||||||
|
super.sendError(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendError(final int status, final String message) throws IOException {
|
||||||
|
this.status = status;
|
||||||
|
super.sendError(status, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatus(final int status) {
|
||||||
|
this.status = status;
|
||||||
|
super.setStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatus(final int status, final String message) {
|
||||||
|
this.status = status;
|
||||||
|
super.setStatus(status, message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,197 +1,116 @@
|
||||||
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 datadog.trace.api.DDTags
|
import datadog.trace.instrumentation.servlet2.Servlet2Decorator
|
||||||
import okhttp3.Credentials
|
import io.opentracing.tag.Tags
|
||||||
import okhttp3.Interceptor
|
import javax.servlet.http.HttpServletRequest
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.eclipse.jetty.http.HttpHeaders
|
|
||||||
import org.eclipse.jetty.http.security.Constraint
|
|
||||||
import org.eclipse.jetty.security.ConstraintMapping
|
|
||||||
import org.eclipse.jetty.security.ConstraintSecurityHandler
|
|
||||||
import org.eclipse.jetty.security.HashLoginService
|
|
||||||
import org.eclipse.jetty.security.LoginService
|
|
||||||
import org.eclipse.jetty.security.authentication.BasicAuthenticator
|
|
||||||
import org.eclipse.jetty.server.Server
|
import org.eclipse.jetty.server.Server
|
||||||
|
import org.eclipse.jetty.server.handler.ErrorHandler
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||||
|
|
||||||
class JettyServlet2Test extends AgentTestRunner {
|
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED
|
||||||
|
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.REDIRECT
|
||||||
|
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
|
||||||
|
|
||||||
OkHttpClient client = OkHttpUtils.clientBuilder().addNetworkInterceptor(new Interceptor() {
|
class JettyServlet2Test extends HttpServerTest<Servlet2Decorator> {
|
||||||
@Override
|
|
||||||
Response intercept(Interceptor.Chain chain) throws IOException {
|
|
||||||
def response = chain.proceed(chain.request())
|
|
||||||
TEST_WRITER.waitForTraces(1)
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
|
|
||||||
int port
|
private static final CONTEXT = "ctx"
|
||||||
private Server jettyServer
|
private Server jettyServer
|
||||||
private ServletContextHandler servletContext
|
|
||||||
|
|
||||||
def setup() {
|
@Override
|
||||||
port = PortUtils.randomOpenPort()
|
void startServer(int port) {
|
||||||
jettyServer = new Server(port)
|
jettyServer = new Server(port)
|
||||||
servletContext = new ServletContextHandler()
|
jettyServer.connectors.each { it.resolveNames = true } // get localhost instead of 127.0.0.1
|
||||||
servletContext.contextPath = "/ctx"
|
ServletContextHandler servletContext = new ServletContextHandler(null, "/$CONTEXT")
|
||||||
|
servletContext.errorHandler = new ErrorHandler() {
|
||||||
|
protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) throws IOException {
|
||||||
|
Throwable th = (Throwable) request.getAttribute("javax.servlet.error.exception")
|
||||||
|
writer.write(th ? th.message : message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ConstraintSecurityHandler security = setupAuthentication(jettyServer)
|
// FIXME: Add tests for security/authentication.
|
||||||
|
// ConstraintSecurityHandler security = setupAuthentication(jettyServer)
|
||||||
|
// servletContext.setSecurityHandler(security)
|
||||||
|
|
||||||
servletContext.setSecurityHandler(security)
|
servletContext.addServlet(TestServlet2.Sync, SUCCESS.path)
|
||||||
servletContext.addServlet(TestServlet2.Sync, "/sync")
|
servletContext.addServlet(TestServlet2.Sync, ERROR.path)
|
||||||
servletContext.addServlet(TestServlet2.Sync, "/auth/sync")
|
servletContext.addServlet(TestServlet2.Sync, EXCEPTION.path)
|
||||||
|
servletContext.addServlet(TestServlet2.Sync, REDIRECT.path)
|
||||||
|
servletContext.addServlet(TestServlet2.Sync, AUTH_REQUIRED.path)
|
||||||
|
|
||||||
jettyServer.setHandler(servletContext)
|
jettyServer.setHandler(servletContext)
|
||||||
jettyServer.start()
|
jettyServer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
def cleanup() {
|
@Override
|
||||||
|
void stopServer() {
|
||||||
jettyServer.stop()
|
jettyServer.stop()
|
||||||
jettyServer.destroy()
|
jettyServer.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
def "test #path servlet call (auth: #auth, distributed tracing: #distributedTracing)"() {
|
@Override
|
||||||
setup:
|
URI buildAddress() {
|
||||||
def requestBuilder = new Request.Builder()
|
return new URI("http://localhost:$port/$CONTEXT/")
|
||||||
.url("http://localhost:$port/ctx/$path")
|
|
||||||
.get()
|
|
||||||
if (distributedTracing) {
|
|
||||||
requestBuilder.header("x-datadog-trace-id", "123")
|
|
||||||
requestBuilder.header("x-datadog-parent-id", "456")
|
|
||||||
}
|
|
||||||
if (auth) {
|
|
||||||
requestBuilder.header(HttpHeaders.AUTHORIZATION, Credentials.basic("user", "password"))
|
|
||||||
}
|
|
||||||
def response = client.newCall(requestBuilder.build()).execute()
|
|
||||||
|
|
||||||
expect:
|
|
||||||
response.body().string().trim() == expectedResponse
|
|
||||||
|
|
||||||
assertTraces(1) {
|
|
||||||
trace(0, 1) {
|
|
||||||
span(0) {
|
|
||||||
if (distributedTracing) {
|
|
||||||
traceId "123"
|
|
||||||
parentId "456"
|
|
||||||
} else {
|
|
||||||
parent()
|
|
||||||
}
|
|
||||||
serviceName "ctx"
|
|
||||||
operationName "servlet.request"
|
|
||||||
resourceName "GET /ctx/$path"
|
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
|
||||||
errored false
|
|
||||||
tags {
|
|
||||||
"http.url" "http://localhost:$port/ctx/$path"
|
|
||||||
"http.method" "GET"
|
|
||||||
"span.kind" "server"
|
|
||||||
"component" "java-web-servlet"
|
|
||||||
"peer.hostname" "127.0.0.1"
|
|
||||||
"peer.ipv4" "127.0.0.1"
|
|
||||||
"span.origin.type" "TestServlet2\$Sync"
|
|
||||||
"servlet.context" "/ctx"
|
|
||||||
if (auth) {
|
|
||||||
"$DDTags.USER_NAME" "user"
|
|
||||||
}
|
|
||||||
defaultTags(distributedTracing)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
where:
|
|
||||||
path | expectedResponse | auth | distributedTracing
|
|
||||||
"sync" | "Hello Sync" | false | false
|
|
||||||
"auth/sync" | "Hello Sync" | true | false
|
|
||||||
"sync" | "Hello Sync" | false | true
|
|
||||||
"auth/sync" | "Hello Sync" | true | true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def "test #path error servlet call"() {
|
@Override
|
||||||
setup:
|
Servlet2Decorator decorator() {
|
||||||
def request = new Request.Builder()
|
return Servlet2Decorator.DECORATE
|
||||||
.url("http://localhost:$port/ctx/$path?error=true")
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
def response = client.newCall(request).execute()
|
|
||||||
|
|
||||||
expect:
|
|
||||||
response.body().string().trim() != expectedResponse
|
|
||||||
|
|
||||||
assertTraces(1) {
|
|
||||||
trace(0, 1) {
|
|
||||||
span(0) {
|
|
||||||
serviceName "ctx"
|
|
||||||
operationName "servlet.request"
|
|
||||||
resourceName "GET /ctx/$path"
|
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
|
||||||
errored true
|
|
||||||
parent()
|
|
||||||
tags {
|
|
||||||
"http.url" "http://localhost:$port/ctx/$path"
|
|
||||||
"http.method" "GET"
|
|
||||||
"span.kind" "server"
|
|
||||||
"component" "java-web-servlet"
|
|
||||||
"peer.hostname" "127.0.0.1"
|
|
||||||
"peer.ipv4" "127.0.0.1"
|
|
||||||
"span.origin.type" "TestServlet2\$Sync"
|
|
||||||
"servlet.context" "/ctx"
|
|
||||||
errorTags(RuntimeException, "some $path error")
|
|
||||||
defaultTags()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
where:
|
|
||||||
path | expectedResponse
|
|
||||||
"sync" | "Hello Sync"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def "test #path non-throwing-error servlet call"() {
|
@Override
|
||||||
// This doesn't actually detect the error because we can't get the status code via the old servlet API.
|
String expectedServiceName() {
|
||||||
setup:
|
CONTEXT
|
||||||
def request = new Request.Builder()
|
}
|
||||||
.url("http://localhost:$port/ctx/$path?non-throwing-error=true")
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
def response = client.newCall(request).execute()
|
|
||||||
|
|
||||||
expect:
|
@Override
|
||||||
response.body().string().trim() != expectedResponse
|
String expectedOperationName() {
|
||||||
|
return "servlet.request"
|
||||||
|
}
|
||||||
|
|
||||||
assertTraces(1) {
|
@Override
|
||||||
trace(0, 1) {
|
boolean testNotFound() {
|
||||||
span(0) {
|
false
|
||||||
serviceName "ctx"
|
}
|
||||||
operationName "servlet.request"
|
|
||||||
resourceName "GET /ctx/$path"
|
// parent span must be cast otherwise it breaks debugging classloading (junit loads it early)
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
|
||||||
errored false
|
trace.span(index) {
|
||||||
parent()
|
serviceName expectedServiceName()
|
||||||
tags {
|
operationName expectedOperationName()
|
||||||
"http.url" "http://localhost:$port/ctx/$path"
|
resourceName endpoint.status == 404 ? "404" : "$method ${endpoint.resolve(address).path}"
|
||||||
"http.method" "GET"
|
spanType DDSpanTypes.HTTP_SERVER
|
||||||
"span.kind" "server"
|
errored endpoint.errored
|
||||||
"component" "java-web-servlet"
|
if (parentID != null) {
|
||||||
"peer.hostname" "127.0.0.1"
|
traceId traceID
|
||||||
"peer.ipv4" "127.0.0.1"
|
parentId parentID
|
||||||
"span.origin.type" "TestServlet2\$Sync"
|
} else {
|
||||||
"servlet.context" "/ctx"
|
parent()
|
||||||
defaultTags()
|
}
|
||||||
}
|
tags {
|
||||||
|
"servlet.context" "/$CONTEXT"
|
||||||
|
"span.origin.type" TestServlet2.Sync.name
|
||||||
|
|
||||||
|
defaultTags(true)
|
||||||
|
"$Tags.COMPONENT.key" serverDecorator.component()
|
||||||
|
if (endpoint.errored) {
|
||||||
|
"$Tags.ERROR.key" endpoint.errored
|
||||||
|
"error.msg" { it == null || it == EXCEPTION.body }
|
||||||
|
"error.type" { it == null || it == Exception.name }
|
||||||
|
"error.stack" { it == null || it instanceof String }
|
||||||
}
|
}
|
||||||
|
"$Tags.HTTP_STATUS.key" endpoint.status
|
||||||
|
"$Tags.HTTP_URL.key" "${endpoint.resolve(address)}"
|
||||||
|
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||||
|
// No peer port
|
||||||
|
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
||||||
|
"$Tags.HTTP_METHOD.key" method
|
||||||
|
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
where:
|
|
||||||
path | expectedResponse
|
|
||||||
"sync" | "Hello Sync"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -204,26 +123,26 @@ class JettyServlet2Test extends AgentTestRunner {
|
||||||
* @param jettyServer server to attach login service
|
* @param jettyServer server to attach login service
|
||||||
* @return SecurityHandler that can be assigned to servlet
|
* @return SecurityHandler that can be assigned to servlet
|
||||||
*/
|
*/
|
||||||
private ConstraintSecurityHandler setupAuthentication(Server jettyServer) {
|
// private ConstraintSecurityHandler setupAuthentication(Server jettyServer) {
|
||||||
ConstraintSecurityHandler security = new ConstraintSecurityHandler()
|
// ConstraintSecurityHandler security = new ConstraintSecurityHandler()
|
||||||
|
//
|
||||||
Constraint constraint = new Constraint()
|
// Constraint constraint = new Constraint()
|
||||||
constraint.setName("auth")
|
// constraint.setName("auth")
|
||||||
constraint.setAuthenticate(true)
|
// constraint.setAuthenticate(true)
|
||||||
constraint.setRoles("role")
|
// constraint.setRoles("role")
|
||||||
|
//
|
||||||
ConstraintMapping mapping = new ConstraintMapping()
|
// ConstraintMapping mapping = new ConstraintMapping()
|
||||||
mapping.setPathSpec("/auth/*")
|
// mapping.setPathSpec("/auth/*")
|
||||||
mapping.setConstraint(constraint)
|
// mapping.setConstraint(constraint)
|
||||||
|
//
|
||||||
security.setConstraintMappings(mapping)
|
// security.setConstraintMappings(mapping)
|
||||||
security.setAuthenticator(new BasicAuthenticator())
|
// security.setAuthenticator(new BasicAuthenticator())
|
||||||
|
//
|
||||||
LoginService loginService = new HashLoginService("TestRealm",
|
// LoginService loginService = new HashLoginService("TestRealm",
|
||||||
"src/test/resources/realm.properties")
|
// "src/test/resources/realm.properties")
|
||||||
security.setLoginService(loginService)
|
// security.setLoginService(loginService)
|
||||||
jettyServer.addBean(loginService)
|
// jettyServer.addBean(loginService)
|
||||||
|
//
|
||||||
security
|
// security
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
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 ServletTestInstrumentation implements Instrumenter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
|
||||||
|
return agentBuilder
|
||||||
|
// Jetty 7.0
|
||||||
|
.type(named("org.eclipse.jetty.server.HttpConnection"))
|
||||||
|
.transform(
|
||||||
|
new AgentBuilder.Transformer.ForAdvice()
|
||||||
|
.advice(
|
||||||
|
named("handleRequest"), HttpServerTestAdvice.ServerEntryAdvice.class.getName()))
|
||||||
|
// Jetty 7.latest
|
||||||
|
.type(named("org.eclipse.jetty.server.AbstractHttpConnection"))
|
||||||
|
.transform(
|
||||||
|
new AgentBuilder.Transformer.ForAdvice()
|
||||||
|
.advice(
|
||||||
|
named("headerComplete"),
|
||||||
|
HttpServerTestAdvice.ServerEntryAdvice.class.getName()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,37 @@
|
||||||
|
import datadog.trace.agent.test.base.HttpServerTest
|
||||||
import groovy.servlet.AbstractHttpServlet
|
import groovy.servlet.AbstractHttpServlet
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
|
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.REDIRECT
|
||||||
|
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
|
||||||
|
|
||||||
class TestServlet2 {
|
class TestServlet2 {
|
||||||
|
|
||||||
static class Sync extends AbstractHttpServlet {
|
static class Sync extends AbstractHttpServlet {
|
||||||
@Override
|
@Override
|
||||||
void doGet(HttpServletRequest req, HttpServletResponse resp) {
|
protected void service(HttpServletRequest req, HttpServletResponse resp) {
|
||||||
if (req.getParameter("error") != null) {
|
req.getRequestDispatcher()
|
||||||
throw new RuntimeException("some sync error")
|
HttpServerTest.ServerEndpoint endpoint = HttpServerTest.ServerEndpoint.forPath(req.servletPath)
|
||||||
|
HttpServerTest.controller(endpoint) {
|
||||||
|
resp.contentType = "text/plain"
|
||||||
|
switch (endpoint) {
|
||||||
|
case SUCCESS:
|
||||||
|
resp.status = endpoint.status
|
||||||
|
resp.writer.print(endpoint.body)
|
||||||
|
break
|
||||||
|
case REDIRECT:
|
||||||
|
resp.sendRedirect(endpoint.body)
|
||||||
|
break
|
||||||
|
case ERROR:
|
||||||
|
resp.sendError(endpoint.status, endpoint.body)
|
||||||
|
break
|
||||||
|
case EXCEPTION:
|
||||||
|
throw new Exception(endpoint.body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (req.getParameter("non-throwing-error") != null) {
|
|
||||||
resp.sendError(500, "some sync error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.writer.print("Hello Sync")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ abstract class JettyServlet3Test extends AbstractServlet3Test<ServletContextHand
|
||||||
servletContext.errorHandler = new ErrorHandler() {
|
servletContext.errorHandler = new ErrorHandler() {
|
||||||
protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) throws IOException {
|
protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) throws IOException {
|
||||||
Throwable th = (Throwable) request.getAttribute("javax.servlet.error.exception")
|
Throwable th = (Throwable) request.getAttribute("javax.servlet.error.exception")
|
||||||
writer.write(th.message)
|
writer.write(th ? th.message : message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// setupAuthentication(jettyServer, servletContext)
|
// setupAuthentication(jettyServer, servletContext)
|
||||||
|
|
|
@ -22,13 +22,15 @@ class TestServlet3 {
|
||||||
resp.contentType = "text/plain"
|
resp.contentType = "text/plain"
|
||||||
switch (endpoint) {
|
switch (endpoint) {
|
||||||
case SUCCESS:
|
case SUCCESS:
|
||||||
case ERROR:
|
|
||||||
resp.status = endpoint.status
|
resp.status = endpoint.status
|
||||||
resp.writer.print(endpoint.body)
|
resp.writer.print(endpoint.body)
|
||||||
break
|
break
|
||||||
case REDIRECT:
|
case REDIRECT:
|
||||||
resp.sendRedirect(endpoint.body)
|
resp.sendRedirect(endpoint.body)
|
||||||
break
|
break
|
||||||
|
case ERROR:
|
||||||
|
resp.sendError(endpoint.status, endpoint.body)
|
||||||
|
break
|
||||||
case EXCEPTION:
|
case EXCEPTION:
|
||||||
throw new Exception(endpoint.body)
|
throw new Exception(endpoint.body)
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ class ErrorHandlerValve extends ErrorReportValve {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
response.writer.print(t.cause.message)
|
response.writer.print(t ? t.cause.message : response.message)
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,157 +0,0 @@
|
||||||
import datadog.trace.agent.test.AgentTestRunner
|
|
||||||
import datadog.trace.agent.test.utils.OkHttpUtils
|
|
||||||
import datadog.trace.agent.test.utils.PortUtils
|
|
||||||
import datadog.trace.api.DDSpanTypes
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
|
||||||
import io.opentracing.tag.Tags
|
|
||||||
import io.vertx.core.Vertx
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import spock.lang.Shared
|
|
||||||
|
|
||||||
class VertxRxServerTest extends AgentTestRunner {
|
|
||||||
|
|
||||||
@Shared
|
|
||||||
OkHttpClient client = OkHttpUtils.client()
|
|
||||||
|
|
||||||
@Shared
|
|
||||||
int port
|
|
||||||
@Shared
|
|
||||||
Vertx server
|
|
||||||
|
|
||||||
def setupSpec() {
|
|
||||||
port = PortUtils.randomOpenPort()
|
|
||||||
server = VertxRxWebTestServer.start(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
def cleanupSpec() {
|
|
||||||
server.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
def "test server request/response"() {
|
|
||||||
setup:
|
|
||||||
def request = new Request.Builder()
|
|
||||||
.url("http://localhost:$port/proxy")
|
|
||||||
.header("x-datadog-trace-id", "123")
|
|
||||||
.header("x-datadog-parent-id", "456")
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
def response = client.newCall(request).execute()
|
|
||||||
|
|
||||||
expect:
|
|
||||||
response.code() == 200
|
|
||||||
response.body().string() == "Hello World"
|
|
||||||
|
|
||||||
and:
|
|
||||||
assertTraces(2) {
|
|
||||||
trace(0, 2) {
|
|
||||||
span(0) {
|
|
||||||
serviceName "unnamed-java-app"
|
|
||||||
operationName "netty.request"
|
|
||||||
resourceName "GET /test"
|
|
||||||
childOf(trace(1).get(1))
|
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
|
||||||
errored false
|
|
||||||
tags {
|
|
||||||
"$Tags.COMPONENT.key" "netty"
|
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
|
||||||
"$Tags.HTTP_STATUS.key" 200
|
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/test"
|
|
||||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
|
||||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
|
||||||
"$Tags.PEER_PORT.key" Integer
|
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
|
||||||
defaultTags(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
span(1) {
|
|
||||||
childOf span(0)
|
|
||||||
assert span(1).operationName.endsWith('.tracedMethod')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trace(1, 2) {
|
|
||||||
span(0) {
|
|
||||||
serviceName "unnamed-java-app"
|
|
||||||
operationName "netty.request"
|
|
||||||
resourceName "GET /proxy"
|
|
||||||
traceId "123"
|
|
||||||
parentId "456"
|
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
|
||||||
errored false
|
|
||||||
tags {
|
|
||||||
"$Tags.COMPONENT.key" "netty"
|
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
|
||||||
"$Tags.HTTP_STATUS.key" 200
|
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/proxy"
|
|
||||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
|
||||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
|
||||||
"$Tags.PEER_PORT.key" Integer
|
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
|
||||||
defaultTags(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
span(1) {
|
|
||||||
serviceName "unnamed-java-app"
|
|
||||||
operationName "netty.client.request"
|
|
||||||
resourceName "GET /test"
|
|
||||||
childOf(span(0))
|
|
||||||
spanType DDSpanTypes.HTTP_CLIENT
|
|
||||||
errored false
|
|
||||||
tags {
|
|
||||||
"$Tags.COMPONENT.key" "netty-client"
|
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
|
||||||
"$Tags.HTTP_STATUS.key" 200
|
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/test"
|
|
||||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
|
||||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
|
||||||
"$Tags.PEER_PORT.key" Integer
|
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
|
||||||
defaultTags()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def "test #responseCode response handling"() {
|
|
||||||
setup:
|
|
||||||
def request = new Request.Builder().url("http://localhost:$port/$path").get().build()
|
|
||||||
def response = client.newCall(request).execute()
|
|
||||||
|
|
||||||
expect:
|
|
||||||
response.code() == responseCode.code()
|
|
||||||
|
|
||||||
and:
|
|
||||||
assertTraces(1) {
|
|
||||||
trace(0, 1) {
|
|
||||||
span(0) {
|
|
||||||
serviceName "unnamed-java-app"
|
|
||||||
operationName "netty.request"
|
|
||||||
resourceName name
|
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
|
||||||
errored error
|
|
||||||
tags {
|
|
||||||
"$Tags.COMPONENT.key" "netty"
|
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
|
||||||
"$Tags.HTTP_STATUS.key" responseCode.code()
|
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/$path"
|
|
||||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
|
||||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
|
||||||
"$Tags.PEER_PORT.key" Integer
|
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
|
||||||
if (error) {
|
|
||||||
tag("error", true)
|
|
||||||
}
|
|
||||||
defaultTags()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
where:
|
|
||||||
responseCode | name | path | error
|
|
||||||
HttpResponseStatus.OK | "GET /" | "" | false
|
|
||||||
HttpResponseStatus.NOT_FOUND | "404" | "doesnt-exit" | false
|
|
||||||
HttpResponseStatus.INTERNAL_SERVER_ERROR | "GET /error" | "error" | true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
import datadog.trace.agent.test.AgentTestRunner
|
|
||||||
import datadog.trace.agent.test.utils.OkHttpUtils
|
|
||||||
import datadog.trace.agent.test.utils.PortUtils
|
|
||||||
import datadog.trace.api.DDSpanTypes
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
|
||||||
import io.opentracing.tag.Tags
|
|
||||||
import io.vertx.core.Vertx
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import spock.lang.Shared
|
|
||||||
|
|
||||||
class VertxServerTest extends AgentTestRunner {
|
|
||||||
|
|
||||||
@Shared
|
|
||||||
OkHttpClient client = OkHttpUtils.client()
|
|
||||||
|
|
||||||
@Shared
|
|
||||||
int port
|
|
||||||
@Shared
|
|
||||||
Vertx server
|
|
||||||
|
|
||||||
def setupSpec() {
|
|
||||||
port = PortUtils.randomOpenPort()
|
|
||||||
server = VertxWebTestServer.start(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
def cleanupSpec() {
|
|
||||||
server.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
def "test server request/response"() {
|
|
||||||
setup:
|
|
||||||
def request = new Request.Builder()
|
|
||||||
.url("http://localhost:$port/proxy")
|
|
||||||
.header("x-datadog-trace-id", "123")
|
|
||||||
.header("x-datadog-parent-id", "456")
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
def response = client.newCall(request).execute()
|
|
||||||
|
|
||||||
expect:
|
|
||||||
response.code() == 200
|
|
||||||
response.body().string() == "Hello World"
|
|
||||||
|
|
||||||
and:
|
|
||||||
assertTraces(2) {
|
|
||||||
trace(0, 2) {
|
|
||||||
span(0) {
|
|
||||||
serviceName "unnamed-java-app"
|
|
||||||
operationName "netty.request"
|
|
||||||
resourceName "GET /test"
|
|
||||||
childOf(trace(1).get(1))
|
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
|
||||||
errored false
|
|
||||||
tags {
|
|
||||||
"$Tags.COMPONENT.key" "netty"
|
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
|
||||||
"$Tags.HTTP_STATUS.key" 200
|
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/test"
|
|
||||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
|
||||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
|
||||||
"$Tags.PEER_PORT.key" Integer
|
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
|
||||||
defaultTags(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
span(1) {
|
|
||||||
childOf span(0)
|
|
||||||
assert span(1).operationName.endsWith('.tracedMethod')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trace(1, 2) {
|
|
||||||
span(0) {
|
|
||||||
serviceName "unnamed-java-app"
|
|
||||||
operationName "netty.request"
|
|
||||||
resourceName "GET /proxy"
|
|
||||||
traceId "123"
|
|
||||||
parentId "456"
|
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
|
||||||
errored false
|
|
||||||
tags {
|
|
||||||
"$Tags.COMPONENT.key" "netty"
|
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
|
||||||
"$Tags.HTTP_STATUS.key" 200
|
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/proxy"
|
|
||||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
|
||||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
|
||||||
"$Tags.PEER_PORT.key" Integer
|
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
|
||||||
defaultTags(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
span(1) {
|
|
||||||
serviceName "unnamed-java-app"
|
|
||||||
operationName "netty.client.request"
|
|
||||||
resourceName "GET /test"
|
|
||||||
childOf(span(0))
|
|
||||||
spanType DDSpanTypes.HTTP_CLIENT
|
|
||||||
errored false
|
|
||||||
tags {
|
|
||||||
"$Tags.COMPONENT.key" "netty-client"
|
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
|
||||||
"$Tags.HTTP_STATUS.key" 200
|
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/test"
|
|
||||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
|
||||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
|
||||||
"$Tags.PEER_PORT.key" Integer
|
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
|
||||||
defaultTags()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def "test #responseCode response handling"() {
|
|
||||||
setup:
|
|
||||||
def request = new Request.Builder().url("http://localhost:$port/$path").get().build()
|
|
||||||
def response = client.newCall(request).execute()
|
|
||||||
|
|
||||||
expect:
|
|
||||||
response.code() == responseCode.code()
|
|
||||||
|
|
||||||
and:
|
|
||||||
assertTraces(1) {
|
|
||||||
trace(0, 1) {
|
|
||||||
span(0) {
|
|
||||||
serviceName "unnamed-java-app"
|
|
||||||
operationName "netty.request"
|
|
||||||
resourceName name
|
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
|
||||||
errored error
|
|
||||||
tags {
|
|
||||||
"$Tags.COMPONENT.key" "netty"
|
|
||||||
"$Tags.HTTP_METHOD.key" "GET"
|
|
||||||
"$Tags.HTTP_STATUS.key" responseCode.code()
|
|
||||||
"$Tags.HTTP_URL.key" "http://localhost:$port/$path"
|
|
||||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
|
||||||
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
|
|
||||||
"$Tags.PEER_PORT.key" Integer
|
|
||||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
|
|
||||||
if (error) {
|
|
||||||
tag("error", true)
|
|
||||||
}
|
|
||||||
defaultTags()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
where:
|
|
||||||
responseCode | name | path | error
|
|
||||||
HttpResponseStatus.OK | "GET /" | "" | false
|
|
||||||
HttpResponseStatus.NOT_FOUND | "404" | "doesnt-exit" | false
|
|
||||||
HttpResponseStatus.INTERNAL_SERVER_ERROR | "GET /error" | "error" | true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
package client
|
||||||
|
|
||||||
import datadog.trace.agent.test.base.HttpClientTest
|
import datadog.trace.agent.test.base.HttpClientTest
|
||||||
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
|
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
|
||||||
import io.vertx.core.Vertx
|
import io.vertx.core.Vertx
|
|
@ -0,0 +1,74 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import datadog.trace.agent.test.base.HttpClientTest
|
||||||
|
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
|
||||||
|
import io.vertx.circuitbreaker.CircuitBreakerOptions
|
||||||
|
import io.vertx.core.VertxOptions
|
||||||
|
import io.vertx.core.http.HttpMethod
|
||||||
|
import io.vertx.ext.web.client.HttpResponse
|
||||||
|
import io.vertx.reactivex.circuitbreaker.CircuitBreaker
|
||||||
|
import io.vertx.reactivex.core.Future
|
||||||
|
import io.vertx.reactivex.core.Vertx
|
||||||
|
import io.vertx.reactivex.ext.web.client.WebClient
|
||||||
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Timeout
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
|
@Timeout(10)
|
||||||
|
class VertxRxCircuitBreakerWebClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
Vertx vertx = Vertx.vertx(new VertxOptions())
|
||||||
|
@Shared
|
||||||
|
WebClient client = WebClient.create(vertx)
|
||||||
|
@Shared
|
||||||
|
CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx,
|
||||||
|
new CircuitBreakerOptions()
|
||||||
|
.setTimeout(-1) // Disable the timeout otherwise it makes each test take this long.
|
||||||
|
)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||||
|
def request = client.request(HttpMethod.valueOf(method), uri.port, uri.host, "$uri")
|
||||||
|
headers.each { request.putHeader(it.key, it.value) }
|
||||||
|
Future<HttpResponse> result = breaker.execute { command ->
|
||||||
|
request.rxSend().doOnSuccess {
|
||||||
|
command.complete(it)
|
||||||
|
}.doOnError {
|
||||||
|
command.fail(it)
|
||||||
|
}.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
def future = new CompletableFuture<Integer>()
|
||||||
|
result.setHandler {
|
||||||
|
callback?.call()
|
||||||
|
if (it.succeeded()) {
|
||||||
|
future.complete(it.result().statusCode())
|
||||||
|
} else {
|
||||||
|
future.completeExceptionally(it.cause())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return future.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
NettyHttpClientDecorator decorator() {
|
||||||
|
return NettyHttpClientDecorator.DECORATE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String expectedOperationName() {
|
||||||
|
return "netty.client.request"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testRedirects() {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testConnectionFailure() {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
package client
|
||||||
|
|
||||||
import datadog.trace.agent.test.base.HttpClientTest
|
import datadog.trace.agent.test.base.HttpClientTest
|
||||||
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
|
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
|
||||||
import io.vertx.core.VertxOptions
|
import io.vertx.core.VertxOptions
|
|
@ -0,0 +1,22 @@
|
||||||
|
package server;
|
||||||
|
|
||||||
|
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 NettyServerTestInstrumentation implements Instrumenter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
|
||||||
|
return agentBuilder
|
||||||
|
.type(named("io.netty.handler.codec.ByteToMessageDecoder"))
|
||||||
|
.transform(
|
||||||
|
new AgentBuilder.Transformer.ForAdvice()
|
||||||
|
.advice(
|
||||||
|
named("channelRead"), HttpServerTestAdvice.ServerEntryAdvice.class.getName()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import datadog.trace.agent.test.asserts.ListWriterAssert
|
||||||
|
import datadog.trace.agent.test.base.HttpServerTest
|
||||||
|
import datadog.trace.instrumentation.netty41.server.NettyHttpServerDecorator
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.SimpleType
|
||||||
|
import io.vertx.core.AbstractVerticle
|
||||||
|
import io.vertx.core.DeploymentOptions
|
||||||
|
import io.vertx.core.Future
|
||||||
|
import io.vertx.core.Vertx
|
||||||
|
import io.vertx.core.VertxOptions
|
||||||
|
import io.vertx.core.json.JsonObject
|
||||||
|
import io.vertx.ext.web.Router
|
||||||
|
import spock.lang.Shared
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
|
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.REDIRECT
|
||||||
|
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
|
||||||
|
|
||||||
|
class VertxHttpServerTest extends HttpServerTest<NettyHttpServerDecorator> {
|
||||||
|
public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
Vertx server
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void startServer(int port) {
|
||||||
|
server = Vertx.vertx(new VertxOptions()
|
||||||
|
// Useful for debugging:
|
||||||
|
// .setBlockedThreadCheckInterval(Integer.MAX_VALUE)
|
||||||
|
.setClusterPort(port))
|
||||||
|
final CompletableFuture<Void> future = new CompletableFuture<>()
|
||||||
|
server.deployVerticle(verticle().name,
|
||||||
|
new DeploymentOptions()
|
||||||
|
.setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port))
|
||||||
|
.setInstances(3)) { res ->
|
||||||
|
if (!res.succeeded()) {
|
||||||
|
throw new RuntimeException("Cannot deploy server Verticle", res.cause())
|
||||||
|
}
|
||||||
|
future.complete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
future.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Class<io.vertx.reactivex.core.AbstractVerticle> verticle() {
|
||||||
|
return VertxWebTestServer
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void stopServer() {
|
||||||
|
server.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
NettyHttpServerDecorator decorator() {
|
||||||
|
return NettyHttpServerDecorator.DECORATE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String expectedOperationName() {
|
||||||
|
"netty.request"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testExceptionBody() {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
static class VertxWebTestServer extends AbstractVerticle {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void start(final Future<Void> startFuture) {
|
||||||
|
final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT)
|
||||||
|
final Router router = Router.router(vertx)
|
||||||
|
|
||||||
|
router.route(SUCCESS.path).handler { ctx ->
|
||||||
|
controller(SUCCESS) {
|
||||||
|
ctx.response().setStatusCode(SUCCESS.status).end(SUCCESS.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router.route(REDIRECT.path).handler { ctx ->
|
||||||
|
controller(REDIRECT) {
|
||||||
|
ctx.response().setStatusCode(REDIRECT.status).putHeader("location", REDIRECT.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router.route(ERROR.path).handler { ctx ->
|
||||||
|
controller(ERROR) {
|
||||||
|
ctx.response().setStatusCode(ERROR.status).end(ERROR.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router.route(EXCEPTION.path).handler { ctx ->
|
||||||
|
controller(EXCEPTION) {
|
||||||
|
throw new Exception(EXCEPTION.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vertx.createHttpServer()
|
||||||
|
.requestHandler { router.accept(it) }
|
||||||
|
.listen(port) { startFuture.complete() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 * 2)
|
||||||
|
|
||||||
|
// Netty closes the parent span before the controller returns, so we need to manually reorder it.
|
||||||
|
TEST_WRITER.each {
|
||||||
|
def controllerSpan = it.find {
|
||||||
|
it.operationName == "controller"
|
||||||
|
}
|
||||||
|
if (controllerSpan) {
|
||||||
|
it.remove(controllerSpan)
|
||||||
|
it.add(controllerSpan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.cleanAndAssertTraces(size, spec)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import datadog.trace.agent.test.base.HttpServerTest
|
||||||
|
import io.vertx.circuitbreaker.CircuitBreakerOptions
|
||||||
|
import io.vertx.reactivex.circuitbreaker.CircuitBreaker
|
||||||
|
import io.vertx.reactivex.core.AbstractVerticle
|
||||||
|
import io.vertx.reactivex.ext.web.Router
|
||||||
|
|
||||||
|
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.REDIRECT
|
||||||
|
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
|
||||||
|
|
||||||
|
class VertxRxCircuitBreakerHttpServerTest extends VertxHttpServerTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<AbstractVerticle> verticle() {
|
||||||
|
return VertxRxCircuitBreakerWebTestServer
|
||||||
|
}
|
||||||
|
|
||||||
|
static class VertxRxCircuitBreakerWebTestServer extends AbstractVerticle {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void start(final io.vertx.core.Future<Void> startFuture) {
|
||||||
|
final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT)
|
||||||
|
final Router router = Router.router(super.@vertx)
|
||||||
|
final CircuitBreaker breaker =
|
||||||
|
CircuitBreaker.create(
|
||||||
|
"my-circuit-breaker",
|
||||||
|
super.@vertx,
|
||||||
|
new CircuitBreakerOptions()
|
||||||
|
.setTimeout(-1) // Disable the timeout otherwise it makes each test take this long.
|
||||||
|
)
|
||||||
|
|
||||||
|
router.route(SUCCESS.path).handler { ctx ->
|
||||||
|
def result = breaker.execute { future ->
|
||||||
|
future.complete(SUCCESS)
|
||||||
|
}
|
||||||
|
result.setHandler {
|
||||||
|
if (it.failed()) {
|
||||||
|
throw it.cause()
|
||||||
|
}
|
||||||
|
HttpServerTest.ServerEndpoint endpoint = it.result()
|
||||||
|
controller(endpoint) {
|
||||||
|
ctx.response().setStatusCode(endpoint.status).end(endpoint.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router.route(REDIRECT.path).handler { ctx ->
|
||||||
|
def result = breaker.execute { future ->
|
||||||
|
future.complete(REDIRECT)
|
||||||
|
}
|
||||||
|
result.setHandler {
|
||||||
|
if (it.failed()) {
|
||||||
|
throw it.cause()
|
||||||
|
}
|
||||||
|
HttpServerTest.ServerEndpoint endpoint = it.result()
|
||||||
|
controller(endpoint) {
|
||||||
|
ctx.response().setStatusCode(endpoint.status).putHeader("location", endpoint.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router.route(ERROR.path).handler { ctx ->
|
||||||
|
def result = breaker.execute { future ->
|
||||||
|
future.complete(ERROR)
|
||||||
|
}
|
||||||
|
result.setHandler {
|
||||||
|
if (it.failed()) {
|
||||||
|
throw it.cause()
|
||||||
|
}
|
||||||
|
HttpServerTest.ServerEndpoint endpoint = it.result()
|
||||||
|
controller(endpoint) {
|
||||||
|
ctx.response().setStatusCode(endpoint.status).end(endpoint.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router.route(EXCEPTION.path).handler { ctx ->
|
||||||
|
def result = breaker.execute { future ->
|
||||||
|
future.fail(new Exception(EXCEPTION.body))
|
||||||
|
}
|
||||||
|
result.setHandler {
|
||||||
|
try {
|
||||||
|
def cause = it.cause()
|
||||||
|
controller(EXCEPTION) {
|
||||||
|
throw cause
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ctx.response().setStatusCode(EXCEPTION.status).end(ex.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.@vertx.createHttpServer()
|
||||||
|
.requestHandler { router.accept(it) }
|
||||||
|
.listen(port) { startFuture.complete() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
|
||||||
|
import io.vertx.core.Future
|
||||||
|
import io.vertx.reactivex.core.AbstractVerticle
|
||||||
|
import io.vertx.reactivex.ext.web.Router
|
||||||
|
|
||||||
|
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.REDIRECT
|
||||||
|
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
|
||||||
|
|
||||||
|
class VertxRxHttpServerTest extends VertxHttpServerTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<AbstractVerticle> verticle() {
|
||||||
|
return VertxRxWebTestServer
|
||||||
|
}
|
||||||
|
|
||||||
|
static class VertxRxWebTestServer extends AbstractVerticle {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void start(final Future<Void> startFuture) {
|
||||||
|
final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT)
|
||||||
|
final Router router = Router.router(super.@vertx)
|
||||||
|
|
||||||
|
router.route(SUCCESS.path).handler { ctx ->
|
||||||
|
controller(SUCCESS) {
|
||||||
|
ctx.response().setStatusCode(SUCCESS.status).end(SUCCESS.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router.route(REDIRECT.path).handler { ctx ->
|
||||||
|
controller(REDIRECT) {
|
||||||
|
ctx.response().setStatusCode(REDIRECT.status).putHeader("location", REDIRECT.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router.route(ERROR.path).handler { ctx ->
|
||||||
|
controller(ERROR) {
|
||||||
|
ctx.response().setStatusCode(ERROR.status).end(ERROR.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router.route(EXCEPTION.path).handler { ctx ->
|
||||||
|
controller(EXCEPTION) {
|
||||||
|
throw new Exception(EXCEPTION.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.@vertx.createHttpServer()
|
||||||
|
.requestHandler { router.accept(it) }
|
||||||
|
.listen(port) { startFuture.complete() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,116 +0,0 @@
|
||||||
import datadog.trace.api.Trace;
|
|
||||||
import io.vertx.circuitbreaker.CircuitBreakerOptions;
|
|
||||||
import io.vertx.core.DeploymentOptions;
|
|
||||||
import io.vertx.core.Future;
|
|
||||||
import io.vertx.core.Vertx;
|
|
||||||
import io.vertx.core.VertxOptions;
|
|
||||||
import io.vertx.core.json.JsonObject;
|
|
||||||
import io.vertx.reactivex.circuitbreaker.CircuitBreaker;
|
|
||||||
import io.vertx.reactivex.core.AbstractVerticle;
|
|
||||||
import io.vertx.reactivex.core.buffer.Buffer;
|
|
||||||
import io.vertx.reactivex.ext.web.Router;
|
|
||||||
import io.vertx.reactivex.ext.web.RoutingContext;
|
|
||||||
import io.vertx.reactivex.ext.web.client.WebClient;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
public class VertxRxWebTestServer extends AbstractVerticle {
|
|
||||||
public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port";
|
|
||||||
|
|
||||||
public static Vertx start(final int port) throws ExecutionException, InterruptedException {
|
|
||||||
/* This is highly against Vertx ideas, but our tests are synchronous
|
|
||||||
so we have to make sure server is up and running */
|
|
||||||
final CompletableFuture<Void> future = new CompletableFuture<>();
|
|
||||||
|
|
||||||
final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port));
|
|
||||||
|
|
||||||
vertx.deployVerticle(
|
|
||||||
VertxRxWebTestServer.class.getName(),
|
|
||||||
new DeploymentOptions()
|
|
||||||
.setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port))
|
|
||||||
.setInstances(3),
|
|
||||||
res -> {
|
|
||||||
if (!res.succeeded()) {
|
|
||||||
throw new RuntimeException("Cannot deploy server Verticle", res.cause());
|
|
||||||
}
|
|
||||||
future.complete(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
future.get();
|
|
||||||
|
|
||||||
return vertx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(final Future<Void> startFuture) {
|
|
||||||
// final io.vertx.reactivex.core.Vertx vertx = new io.vertx.reactivex.core.Vertx(this.vertx);
|
|
||||||
final WebClient client = WebClient.create(vertx);
|
|
||||||
|
|
||||||
final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT);
|
|
||||||
|
|
||||||
final Router router = Router.router(vertx);
|
|
||||||
final CircuitBreaker breaker =
|
|
||||||
CircuitBreaker.create(
|
|
||||||
"my-circuit-breaker",
|
|
||||||
vertx,
|
|
||||||
new CircuitBreakerOptions()
|
|
||||||
.setMaxFailures(5) // number of failure before opening the circuit
|
|
||||||
.setTimeout(2000) // consider a failure if the operation does not succeed in time
|
|
||||||
// .setFallbackOnFailure(true) // do we call the fallback on failure
|
|
||||||
.setResetTimeout(10000) // time spent in open state before attempting to re-try
|
|
||||||
);
|
|
||||||
|
|
||||||
router
|
|
||||||
.route("/")
|
|
||||||
.handler(
|
|
||||||
routingContext -> {
|
|
||||||
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
|
|
||||||
});
|
|
||||||
router
|
|
||||||
.route("/error")
|
|
||||||
.handler(
|
|
||||||
routingContext -> {
|
|
||||||
routingContext.response().setStatusCode(500).end();
|
|
||||||
});
|
|
||||||
router
|
|
||||||
.route("/proxy")
|
|
||||||
.handler(
|
|
||||||
routingContext -> {
|
|
||||||
breaker.execute(
|
|
||||||
ctx -> {
|
|
||||||
client
|
|
||||||
.get(port, "localhost", "/test")
|
|
||||||
.rxSendBuffer(
|
|
||||||
Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer()))
|
|
||||||
.subscribe(
|
|
||||||
response -> {
|
|
||||||
routingContext
|
|
||||||
.response()
|
|
||||||
.setStatusCode(response.statusCode())
|
|
||||||
.end(response.body());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
router
|
|
||||||
.route("/test")
|
|
||||||
.handler(
|
|
||||||
routingContext -> {
|
|
||||||
tracedMethod();
|
|
||||||
routingContext.next();
|
|
||||||
})
|
|
||||||
.blockingHandler(RoutingContext::next)
|
|
||||||
.handler(
|
|
||||||
routingContext -> {
|
|
||||||
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
|
|
||||||
});
|
|
||||||
|
|
||||||
vertx
|
|
||||||
.createHttpServer()
|
|
||||||
.requestHandler(router::accept)
|
|
||||||
.listen(port, h -> startFuture.complete());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Trace
|
|
||||||
private void tracedMethod() {}
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
import static datadog.trace.agent.test.AgentTestRunner.blockUntilChildSpansFinished;
|
|
||||||
|
|
||||||
import datadog.trace.api.Trace;
|
|
||||||
import io.vertx.core.AbstractVerticle;
|
|
||||||
import io.vertx.core.DeploymentOptions;
|
|
||||||
import io.vertx.core.Future;
|
|
||||||
import io.vertx.core.Vertx;
|
|
||||||
import io.vertx.core.VertxOptions;
|
|
||||||
import io.vertx.core.buffer.Buffer;
|
|
||||||
import io.vertx.core.http.HttpClient;
|
|
||||||
import io.vertx.core.json.JsonObject;
|
|
||||||
import io.vertx.ext.web.Router;
|
|
||||||
import io.vertx.ext.web.RoutingContext;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
public class VertxWebTestServer extends AbstractVerticle {
|
|
||||||
public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port";
|
|
||||||
|
|
||||||
public static Vertx start(final int port) throws ExecutionException, InterruptedException {
|
|
||||||
/* This is highly against Vertx ideas, but our tests are synchronous
|
|
||||||
so we have to make sure server is up and running */
|
|
||||||
final CompletableFuture<Void> future = new CompletableFuture<>();
|
|
||||||
|
|
||||||
final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port));
|
|
||||||
|
|
||||||
vertx.deployVerticle(
|
|
||||||
VertxWebTestServer.class.getName(),
|
|
||||||
new DeploymentOptions()
|
|
||||||
.setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port))
|
|
||||||
.setInstances(3),
|
|
||||||
res -> {
|
|
||||||
if (!res.succeeded()) {
|
|
||||||
throw new RuntimeException("Cannot deploy server Verticle", res.cause());
|
|
||||||
}
|
|
||||||
future.complete(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
future.get();
|
|
||||||
|
|
||||||
return vertx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(final Future<Void> startFuture) {
|
|
||||||
final HttpClient client = vertx.createHttpClient();
|
|
||||||
|
|
||||||
final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT);
|
|
||||||
|
|
||||||
final Router router = Router.router(vertx);
|
|
||||||
|
|
||||||
router
|
|
||||||
.route("/")
|
|
||||||
.handler(
|
|
||||||
routingContext -> {
|
|
||||||
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
|
|
||||||
});
|
|
||||||
router
|
|
||||||
.route("/error")
|
|
||||||
.handler(
|
|
||||||
routingContext -> {
|
|
||||||
routingContext.response().setStatusCode(500).end();
|
|
||||||
});
|
|
||||||
router
|
|
||||||
.route("/proxy")
|
|
||||||
.handler(
|
|
||||||
routingContext -> {
|
|
||||||
client
|
|
||||||
.get(
|
|
||||||
port,
|
|
||||||
"localhost",
|
|
||||||
"/test",
|
|
||||||
response -> {
|
|
||||||
response.bodyHandler(
|
|
||||||
buffer -> {
|
|
||||||
routingContext
|
|
||||||
.response()
|
|
||||||
.setStatusCode(response.statusCode())
|
|
||||||
.end(buffer);
|
|
||||||
});
|
|
||||||
blockUntilChildSpansFinished(1);
|
|
||||||
})
|
|
||||||
.end(Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer()));
|
|
||||||
});
|
|
||||||
router
|
|
||||||
.route("/test")
|
|
||||||
.handler(
|
|
||||||
routingContext -> {
|
|
||||||
tracedMethod();
|
|
||||||
routingContext.next();
|
|
||||||
})
|
|
||||||
.blockingHandler(RoutingContext::next)
|
|
||||||
.handler(
|
|
||||||
routingContext -> {
|
|
||||||
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
|
|
||||||
});
|
|
||||||
|
|
||||||
vertx
|
|
||||||
.createHttpServer()
|
|
||||||
.requestHandler(router::accept)
|
|
||||||
.listen(port, h -> startFuture.complete());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Trace
|
|
||||||
private void tracedMethod() {}
|
|
||||||
}
|
|
|
@ -5,21 +5,6 @@ ext {
|
||||||
|
|
||||||
apply from: "${rootDir}/gradle/java.gradle"
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
|
||||||
muzzle {
|
|
||||||
pass {
|
|
||||||
group = "io.vertx"
|
|
||||||
module = "vertx-web"
|
|
||||||
versions = "[4.1.0.Final,)"
|
|
||||||
assertInverse = true
|
|
||||||
}
|
|
||||||
pass {
|
|
||||||
group = "io.netty"
|
|
||||||
module = "netty"
|
|
||||||
versions = "[4.1.0.Final,)"
|
|
||||||
assertInverse = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'org.unbroken-dome.test-sets'
|
apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
|
|
||||||
testSets {
|
testSets {
|
||||||
|
|
|
@ -48,12 +48,14 @@ abstract class HttpServerTest<DECORATOR extends HttpServerDecorator> extends Age
|
||||||
|
|
||||||
def setupSpec() {
|
def setupSpec() {
|
||||||
startServer(port)
|
startServer(port)
|
||||||
|
println "Http server started at: http://localhost:$port/"
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void startServer(int port)
|
abstract void startServer(int port)
|
||||||
|
|
||||||
def cleanupSpec() {
|
def cleanupSpec() {
|
||||||
stopServer()
|
stopServer()
|
||||||
|
println "Http server stopped at: http://localhost:$port/"
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void stopServer()
|
abstract void stopServer()
|
||||||
|
@ -70,11 +72,15 @@ abstract class HttpServerTest<DECORATOR extends HttpServerDecorator> extends Age
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean testExceptionBody() {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
enum ServerEndpoint {
|
enum ServerEndpoint {
|
||||||
SUCCESS("success", 200, "success"),
|
SUCCESS("success", 200, "success"),
|
||||||
|
REDIRECT("redirect", 302, null),
|
||||||
ERROR("error", 500, "controller error"),
|
ERROR("error", 500, "controller error"),
|
||||||
EXCEPTION("exception", 500, "controller exception"),
|
EXCEPTION("exception", 500, "controller exception"),
|
||||||
REDIRECT("redirect", 302, null),
|
|
||||||
NOT_FOUND("notFound", 404, "not found"),
|
NOT_FOUND("notFound", 404, "not found"),
|
||||||
AUTH_REQUIRED("authRequired", 200, null),
|
AUTH_REQUIRED("authRequired", 200, null),
|
||||||
|
|
||||||
|
@ -94,6 +100,10 @@ abstract class HttpServerTest<DECORATOR extends HttpServerDecorator> extends Age
|
||||||
return "/$path"
|
return "/$path"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String rawPath() {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
URI resolve(URI address) {
|
URI resolve(URI address) {
|
||||||
return address.resolve(path)
|
return address.resolve(path)
|
||||||
}
|
}
|
||||||
|
@ -113,10 +123,9 @@ abstract class HttpServerTest<DECORATOR extends HttpServerDecorator> extends Age
|
||||||
|
|
||||||
static <T> T controller(ServerEndpoint endpoint, Closure<T> closure) {
|
static <T> T controller(ServerEndpoint endpoint, Closure<T> closure) {
|
||||||
if (endpoint == NOT_FOUND) {
|
if (endpoint == NOT_FOUND) {
|
||||||
closure()
|
return closure()
|
||||||
} else {
|
|
||||||
runUnderTrace("controller", closure)
|
|
||||||
}
|
}
|
||||||
|
return runUnderTrace("controller", closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
def "test success with #count requests"() {
|
def "test success with #count requests"() {
|
||||||
|
@ -204,7 +213,9 @@ abstract class HttpServerTest<DECORATOR extends HttpServerDecorator> extends Age
|
||||||
|
|
||||||
expect:
|
expect:
|
||||||
response.code() == EXCEPTION.status
|
response.code() == EXCEPTION.status
|
||||||
response.body().string() == EXCEPTION.body
|
if (testExceptionBody()) {
|
||||||
|
assert response.body().string() == EXCEPTION.body
|
||||||
|
}
|
||||||
|
|
||||||
and:
|
and:
|
||||||
cleanAndAssertTraces(1) {
|
cleanAndAssertTraces(1) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package datadog.trace.agent.test.base;
|
package datadog.trace.agent.test.base;
|
||||||
|
|
||||||
import datadog.trace.api.DDTags;
|
import datadog.trace.api.DDTags;
|
||||||
|
import datadog.trace.context.TraceScope;
|
||||||
import io.opentracing.Scope;
|
import io.opentracing.Scope;
|
||||||
import io.opentracing.Tracer;
|
import io.opentracing.Tracer;
|
||||||
import io.opentracing.noop.NoopScopeManager;
|
import io.opentracing.noop.NoopScopeManager;
|
||||||
|
@ -24,10 +25,13 @@ public abstract class HttpServerTestAdvice {
|
||||||
if (tracer.activeSpan() != null) {
|
if (tracer.activeSpan() != null) {
|
||||||
return NoopScopeManager.NoopScope.INSTANCE;
|
return NoopScopeManager.NoopScope.INSTANCE;
|
||||||
} else {
|
} else {
|
||||||
return tracer
|
final Scope scope =
|
||||||
.buildSpan("TEST_SPAN")
|
tracer
|
||||||
.withTag(DDTags.RESOURCE_NAME, "ServerEntry")
|
.buildSpan("TEST_SPAN")
|
||||||
.startActive(true);
|
.withTag(DDTags.RESOURCE_NAME, "ServerEntry")
|
||||||
|
.startActive(true);
|
||||||
|
((TraceScope) scope).setAsyncPropagation(true);
|
||||||
|
return scope;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue