Restlet 2.0 instrumentation (#4535)

* add restlet 2.0 instrumentation

* add restlet 2.0 instrumentation

* revies: testLibrary, create RestletInstrumenterFactory
This commit is contained in:
Anna Nosek 2021-11-09 20:52:04 +01:00 committed by GitHub
parent 21d6648c95
commit 8b7c097912
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 973 additions and 0 deletions

View File

@ -0,0 +1,42 @@
plugins {
id("otel.javaagent-instrumentation")
}
muzzle {
pass {
group.set("org.restlet")
module.set("org.restlet.jse")
versions.set("[2.0.0,)")
assertInverse.set(true)
}
}
repositories {
mavenCentral()
maven("https://maven.restlet.talend.com/")
mavenLocal()
}
dependencies {
api(project(":instrumentation:restlet:restlet-2.0:library"))
library("org.restlet.jse:org.restlet:2.0.2")
implementation(project(":instrumentation:restlet:restlet-2.0:library"))
testImplementation(project(":instrumentation:restlet:restlet-2.0:testing"))
testLibrary("org.restlet.jse:org.restlet.ext.jetty:2.0.2")
}
// restlet registers the first engine that is present on classpath, so we need to enforce the appropriate version
if (findProperty("testLatestDeps") as Boolean) {
configurations.configureEach {
resolutionStrategy {
eachDependency {
if (requested.group == "org.restlet.jse") {
useVersion("2.+")
}
}
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.restlet.v2_0;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.Arrays;
import java.util.List;
@AutoService(InstrumentationModule.class)
public class RestletInstrumentationModule extends InstrumentationModule {
public RestletInstrumentationModule() {
super("restlet", "restlet-2.0");
}
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Arrays.asList(new ServerInstrumentation(), new RouteInstrumentation());
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.restlet.v2_0;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletInstrumenterFactory;
import org.restlet.Request;
import org.restlet.Response;
public final class RestletSingletons {
private static final Instrumenter<Request, Response> INSTRUMENTER =
RestletInstrumenterFactory.newServerInstrumenter(GlobalOpenTelemetry.get());
public static Instrumenter<Request, Response> instrumenter() {
return INSTRUMENTER;
}
private RestletSingletons() {}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.restlet.v2_0;
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTROLLER;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletServerSpanNaming;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.restlet.Request;
import org.restlet.routing.TemplateRoute;
public class RouteInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.restlet.routing.TemplateRoute").or(named("org.restlet.routing.Route"));
}
@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("beforeHandle"))
.and(takesArgument(0, named("org.restlet.Request")))
.and(takesArgument(1, named("org.restlet.Response"))),
this.getClass().getName() + "$RouteBeforeHandleAdvice");
}
@SuppressWarnings("unused")
public static class RouteBeforeHandleAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void getRouteInfo(
@Advice.This TemplateRoute route, @Advice.Argument(0) Request request) {
String pattern = route.getTemplate().getPattern();
ServerSpanNaming.updateServerSpanName(
currentContext(), CONTROLLER, RestletServerSpanNaming.SERVER_SPAN_NAME, pattern);
}
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.restlet.v2_0;
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTROLLER;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.restlet.v2_0.RestletSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletServerSpanNaming;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Status;
public class ServerInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.restlet.Server");
}
@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("handle"))
.and(takesArgument(0, named("org.restlet.Request")))
.and(takesArgument(1, named("org.restlet.Response"))),
this.getClass().getName() + "$ServerHandleAdvice");
}
@SuppressWarnings("unused")
public static class ServerHandleAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void beginRequest(
@Advice.Argument(0) Request request,
@Advice.Argument(1) Response response,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
if (!instrumenter().shouldStart(parentContext, request)) {
return;
}
context = instrumenter().start(parentContext, request);
scope = context.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void finishRequest(
@Advice.Argument(0) Request request,
@Advice.Argument(1) Response response,
@Advice.Thrown Throwable exception,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}
scope.close();
if (Status.CLIENT_ERROR_NOT_FOUND.equals(response.getStatus())) {
ServerSpanNaming.updateServerSpanName(
context, CONTROLLER, RestletServerSpanNaming.SERVER_SPAN_NAME, "/*");
}
if (exception != null) {
instrumenter().end(context, request, response, exception);
return;
}
// Restlet suppresses exceptions and sets the throwable in status
Throwable statusThrowable = response.getStatus().getThrowable();
instrumenter().end(context, request, response, statusThrowable);
}
}
}

View File

@ -0,0 +1,13 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.restlet.v2_0
import io.opentelemetry.instrumentation.restlet.v2_0.AbstractRestletServerTest
import io.opentelemetry.instrumentation.test.AgentTestTrait
class RestletServerTest extends AbstractRestletServerTest implements AgentTestTrait {
}

View File

@ -0,0 +1,29 @@
plugins {
id("otel.library-instrumentation")
}
repositories {
mavenCentral()
maven("https://maven.restlet.talend.com/")
mavenLocal()
}
dependencies {
library("org.restlet.jse:org.restlet:2.0.2")
testImplementation(project(":instrumentation:restlet:restlet-2.0:testing"))
testLibrary("org.restlet.jse:org.restlet.ext.jetty:2.0.2")
}
// restlet registers the first engine that is present on classpath, so we need to enforce the appropriate version
if (findProperty("testLatestDeps") as Boolean) {
configurations.configureEach {
resolutionStrategy {
eachDependency {
if (requested.group == "org.restlet.jse") {
useVersion("2.+")
}
}
}
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v2_0;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.routing.Filter;
/** Entrypoint for tracing Restlet servers. */
public final class RestletTracing {
/** Returns a new {@link RestletTracing} configured with the given {@link OpenTelemetry}. */
public static RestletTracing create(OpenTelemetry openTelemetry) {
return builder(openTelemetry).build();
}
/**
* Returns a new {@link RestletTracingBuilder} configured with the given {@link OpenTelemetry}.
*/
public static RestletTracingBuilder builder(OpenTelemetry openTelemetry) {
return new RestletTracingBuilder(openTelemetry);
}
private final Instrumenter<Request, Response> serverInstrumenter;
RestletTracing(Instrumenter<Request, Response> serverInstrumenter) {
this.serverInstrumenter = serverInstrumenter;
}
/**
* Returns a new {@link Filter} which can be used to wrap {@link org.restlet.Restlet}
* implementations.
*/
public Filter newFilter(String path) {
return new TracingFilter(serverInstrumenter, path);
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v2_0;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders;
import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletInstrumenterFactory;
import java.util.ArrayList;
import java.util.List;
import org.restlet.Request;
import org.restlet.Response;
/** A builder of {@link RestletTracing}. */
public final class RestletTracingBuilder {
private final OpenTelemetry openTelemetry;
private final List<AttributesExtractor<Request, Response>> additionalExtractors =
new ArrayList<>();
private CapturedHttpHeaders capturedHttpHeaders = CapturedHttpHeaders.server(Config.get());
RestletTracingBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}
/**
* Adds an additional {@link AttributesExtractor} to invoke to set attributes to instrumented
* items.
*/
public RestletTracingBuilder addAttributesExtractor(
AttributesExtractor<Request, Response> attributesExtractor) {
additionalExtractors.add(attributesExtractor);
return this;
}
/**
* Configure the instrumentation to capture chosen HTTP request and response headers as span
* attributes.
*
* @param capturedHttpHeaders An instance of {@link CapturedHttpHeaders} containing the configured
* HTTP request and response names.
*/
public RestletTracingBuilder captureHttpHeaders(CapturedHttpHeaders capturedHttpHeaders) {
this.capturedHttpHeaders = capturedHttpHeaders;
return this;
}
/**
* Returns a new {@link RestletTracing} with the settings of this {@link RestletTracingBuilder}.
*/
public RestletTracing build() {
Instrumenter<Request, Response> serverInstrumenter =
RestletInstrumenterFactory.newServerInstrumenter(
openTelemetry, capturedHttpHeaders, additionalExtractors);
return new RestletTracing(serverInstrumenter);
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v2_0;
import static io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming.Source.CONTROLLER;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNaming;
import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletServerSpanNaming;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.resource.ResourceException;
import org.restlet.routing.Filter;
final class TracingFilter extends Filter {
private final Instrumenter<Request, Response> instrumenter;
private final String path;
public TracingFilter(Instrumenter<Request, Response> instrumenter, String path) {
this.instrumenter = instrumenter;
this.path = path;
}
@Override
public int doHandle(Request request, Response response) {
Context parentContext = Context.current();
Context context = parentContext;
Scope scope = null;
if (instrumenter.shouldStart(parentContext, request)) {
context = instrumenter.start(parentContext, request);
scope = context.makeCurrent();
}
ServerSpanNaming.updateServerSpanName(
context, CONTROLLER, RestletServerSpanNaming.SERVER_SPAN_NAME, path);
Throwable statusThrowable = null;
try {
super.doHandle(request, response);
} catch (Throwable t) {
statusThrowable = t;
if (t instanceof Error || t instanceof RuntimeException) {
throw t;
} else {
throw new ResourceException(t);
}
} finally {
if (scope != null) {
scope.close();
if (response.getStatus() != null && response.getStatus().isError()) {
statusThrowable = response.getStatus().getThrowable();
}
instrumenter.end(context, request, response, statusThrowable);
}
}
return CONTINUE;
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v2_0.internal;
import io.opentelemetry.context.propagation.TextMapGetter;
import org.restlet.Message;
import org.restlet.Request;
import org.restlet.util.Series;
final class RestletHeadersGetter implements TextMapGetter<Request> {
@Override
public Iterable<String> keys(Request carrier) {
return getHeaders(carrier).getNames();
}
@Override
public String get(Request carrier, String key) {
Series<?> headers = getHeaders(carrier);
return headers.getFirstValue(key, /* ignoreCase = */ true);
}
static Series<?> getHeaders(Message carrier) {
return (Series<?>) carrier.getAttributes().get("org.restlet.http.headers");
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v2_0.internal;
import static io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletHeadersGetter.getHeaders;
import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Reference;
import org.restlet.util.Series;
final class RestletHttpAttributesExtractor
extends HttpServerAttributesExtractor<Request, Response> {
RestletHttpAttributesExtractor(CapturedHttpHeaders capturedHttpHeaders) {
super(capturedHttpHeaders);
}
@Override
protected String method(Request request) {
return request.getMethod().toString();
}
@Override
@Nullable
protected String target(Request request) {
Reference ref = request.getOriginalRef();
String path = ref.getPath();
return ref.hasQuery() ? path + "?" + ref.getQuery() : path;
}
@Override
@Nullable
protected String route(Request request) {
return null;
}
@Override
@Nullable
protected String scheme(Request request) {
return request.getOriginalRef().getScheme();
}
@Override
protected List<String> requestHeader(Request request, String name) {
Series<?> headers = getHeaders(request);
if (headers == null) {
return Collections.emptyList();
}
return Arrays.asList(headers.getValuesArray(name, true));
}
@Override
@Nullable
protected Long requestContentLength(Request request, @Nullable Response response) {
return null;
}
@Override
@Nullable
protected Long requestContentLengthUncompressed(Request request, @Nullable Response response) {
return null;
}
@Override
@Nullable
protected String flavor(Request request) {
switch (request.getProtocol().toString()) {
case "HTTP/1.0":
return SemanticAttributes.HttpFlavorValues.HTTP_1_0;
case "HTTP/1.1":
return SemanticAttributes.HttpFlavorValues.HTTP_1_1;
case "HTTP/2.0":
return SemanticAttributes.HttpFlavorValues.HTTP_2_0;
default:
// fall through
}
return null;
}
@Override
@Nullable
protected String serverName(Request request, @Nullable Response response) {
return null;
}
@Override
protected Integer statusCode(Request request, Response response) {
return response.getStatus().getCode();
}
@Override
@Nullable
protected Long responseContentLength(Request request, Response response) {
return null;
}
@Override
@Nullable
protected Long responseContentLengthUncompressed(Request request, Response response) {
return null;
}
@Override
protected List<String> responseHeader(Request request, Response response, String name) {
Series<?> headers = getHeaders(response);
if (headers == null) {
return Collections.emptyList();
}
return Arrays.asList(headers.getValuesArray(name, true));
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v2_0.internal;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesExtractor;
import java.util.Collections;
import java.util.List;
import org.restlet.Request;
import org.restlet.Response;
public class RestletInstrumenterFactory {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.restlet-2.0";
public static Instrumenter<Request, Response> newServerInstrumenter(OpenTelemetry openTelemetry) {
return newServerInstrumenter(
openTelemetry, CapturedHttpHeaders.server(Config.get()), Collections.emptyList());
}
public static Instrumenter<Request, Response> newServerInstrumenter(
OpenTelemetry openTelemetry,
CapturedHttpHeaders capturedHttpHeaders,
List<AttributesExtractor<Request, Response>> additionalExtractors) {
HttpServerAttributesExtractor<Request, Response> httpAttributesExtractor =
new RestletHttpAttributesExtractor(capturedHttpHeaders);
SpanNameExtractor<Request> spanNameExtractor =
HttpSpanNameExtractor.create(httpAttributesExtractor);
SpanStatusExtractor<Request, Response> spanStatusExtractor =
HttpSpanStatusExtractor.create(httpAttributesExtractor);
NetServerAttributesExtractor<Request, Response> netAttributesExtractor =
new RestletNetAttributesExtractor();
return Instrumenter.<Request, Response>builder(
openTelemetry, INSTRUMENTATION_NAME, spanNameExtractor)
.setSpanStatusExtractor(spanStatusExtractor)
.addAttributesExtractor(httpAttributesExtractor)
.addAttributesExtractor(netAttributesExtractor)
.addAttributesExtractors(additionalExtractors)
.addRequestMetrics(HttpServerMetrics.get())
.newServerInstrumenter(new RestletHeadersGetter());
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v2_0.internal;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesExtractor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import javax.annotation.Nullable;
import org.restlet.Request;
import org.restlet.Response;
final class RestletNetAttributesExtractor extends NetServerAttributesExtractor<Request, Response> {
@Override
public String transport(Request request) {
return SemanticAttributes.NetTransportValues.IP_TCP;
}
@Override
@Nullable
public String peerName(Request request) {
return null;
}
@Override
public Integer peerPort(Request request) {
return request.getClientInfo().getPort();
}
@Override
@Nullable
public String peerIp(Request request) {
return request.getClientInfo().getAddress();
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v2_0.internal;
import io.opentelemetry.instrumentation.api.servlet.ServerSpanNameSupplier;
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
public final class RestletServerSpanNaming {
public static final ServerSpanNameSupplier<String> SERVER_SPAN_NAME =
(context, pattern) -> {
if (pattern == null || pattern.equals("")) {
return null;
}
return ServletContextPath.prepend(context, pattern);
};
private RestletServerSpanNaming() {}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v2_0
import io.opentelemetry.instrumentation.test.LibraryTestTrait
import org.restlet.Restlet
import org.restlet.engine.application.StatusFilter
import org.restlet.service.StatusService
class RestletServerTest extends AbstractRestletServerTest implements LibraryTestTrait {
@Override
Restlet wrapRestlet(Restlet restlet, String path) {
RestletTracing tracing = RestletTracing.builder(openTelemetry)
.captureHttpHeaders(capturedHttpHeadersForTesting())
.build()
def tracingFilter = tracing.newFilter(path)
def statusFilter = new StatusFilter(component.getContext(), new StatusService())
tracingFilter.setNext(statusFilter)
statusFilter.setNext(restlet)
return tracingFilter
}
}

View File

@ -0,0 +1,19 @@
plugins {
id("otel.java-conventions")
}
repositories {
mavenCentral()
maven("https://maven.restlet.talend.com/")
mavenLocal()
}
dependencies {
api(project(":testing-common"))
implementation("org.restlet.jse:org.restlet:2.0.2")
implementation("org.codehaus.groovy:groovy-all")
implementation("io.opentelemetry:opentelemetry-api")
implementation("org.spockframework:spock-core")
}

View File

@ -0,0 +1,195 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.restlet.v2_0
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.instrumentation.test.base.HttpServerTest
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import org.restlet.Component
import org.restlet.Context
import org.restlet.Request
import org.restlet.Response
import org.restlet.Restlet
import org.restlet.Server
import org.restlet.data.Form
import org.restlet.data.MediaType
import org.restlet.data.Protocol
import org.restlet.data.Status
import org.restlet.routing.Redirector
import org.restlet.routing.Router
import org.restlet.routing.Template
import org.restlet.routing.VirtualHost
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.CAPTURE_HEADERS
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.INDEXED_CHILD
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS
abstract class AbstractRestletServerTest extends HttpServerTest<Server> {
Component component
VirtualHost host
@Override
Server startServer(int port) {
component = new Component()
host = component.getDefaultHost()
def server = setupServer(component)
setupRouting()
component.start()
return server
}
@Override
void stopServer(Server server) {
component.stop()
}
def attachAndWrap(path, restlet) {
host.attach(path, wrapRestlet(restlet, path))
}
Server setupServer(Component component) {
return component.getServers().add(Protocol.HTTP, port)
}
void setupRouting() {
def defaultRouter = wrapRestlet(new Router(host.getContext()), "/*")
host.attach("/", defaultRouter).setMatchingMode(Template.MODE_STARTS_WITH)
attachAndWrap(SUCCESS.path, new Restlet() {
@Override
void handle(Request request, Response response) {
controller(SUCCESS) {
response.setEntity(SUCCESS.body, MediaType.TEXT_PLAIN)
response.setStatus(Status.valueOf(SUCCESS.status), SUCCESS.body)
}
}
})
attachAndWrap(REDIRECT.path, new Redirector(Context.getCurrent(), REDIRECT.body, Redirector.MODE_CLIENT_FOUND) {
@Override
void handle(Request request, Response response) {
super.handle(request, response)
controller(REDIRECT) {
}
}
})
attachAndWrap(ERROR.path, new Restlet() {
@Override
void handle(Request request, Response response) {
controller(ERROR) {
response.setStatus(Status.valueOf(ERROR.getStatus()), ERROR.getBody())
}
}
})
attachAndWrap(EXCEPTION.path, new Restlet() {
@Override
void handle(Request request, Response response) {
controller(EXCEPTION) {
throw new Exception(EXCEPTION.getBody())
}
}
})
attachAndWrap(QUERY_PARAM.path, new Restlet() {
@Override
void handle(Request request, Response response) {
controller(QUERY_PARAM) {
response.setEntity(QUERY_PARAM.getBody(), MediaType.TEXT_PLAIN)
response.setStatus(Status.valueOf(QUERY_PARAM.getStatus()), QUERY_PARAM.getBody())
}
}
})
attachAndWrap("/path/{id}/param", new Restlet() {
@Override
void handle(Request request, Response response) {
controller(PATH_PARAM) {
response.setEntity(PATH_PARAM.getBody(), MediaType.TEXT_PLAIN)
response.setStatus(Status.valueOf(PATH_PARAM.getStatus()), PATH_PARAM.getBody())
}
}
})
attachAndWrap("/captureHeaders", new Restlet() {
@Override
void handle(Request request, Response response) {
controller(CAPTURE_HEADERS) {
def requestHeaders = request.getAttributes().get("org.restlet.http.headers")
def responseHeaders
try {
def headerClass = Class.forName("org.restlet.data.Header")
def seriesClass = Class.forName("org.restlet.util.Series")
//to avoid constructor error (Series is abstract in 2.0.x)
responseHeaders = response.getAttributes().computeIfAbsent("org.restlet.http.headers", { seriesClass.newInstance(headerClass) })
} catch (ClassNotFoundException | NoClassDefFoundError e) {
responseHeaders = response.getAttributes().computeIfAbsent("org.restlet.http.headers", { new Form() })
}
responseHeaders.add("X-Test-Response", requestHeaders.getValues("X-Test-Request"))
response.setEntity(CAPTURE_HEADERS.getBody(), MediaType.TEXT_PLAIN)
response.setStatus(Status.valueOf(CAPTURE_HEADERS.getStatus()), CAPTURE_HEADERS.getBody())
}
}
})
attachAndWrap(INDEXED_CHILD.path, new Restlet() {
@Override
void handle(Request request, Response response) {
controller(INDEXED_CHILD) {
INDEXED_CHILD.collectSpanAttributes { request.getOriginalRef().getQueryAsForm().getFirst(it).getValue() }
response.setStatus(Status.valueOf(INDEXED_CHILD.status))
}
}
})
}
@Override
List<AttributeKey<?>> extraAttributes() {
[
SemanticAttributes.NET_TRANSPORT
]
}
@Override
boolean testPathParam() {
true
}
@Override
String expectedServerSpanName(ServerEndpoint endpoint) {
switch (endpoint) {
case PATH_PARAM:
return getContextPath() + "/path/{id}/param"
case NOT_FOUND:
return getContextPath() + "/*"
default:
return endpoint.resolvePath(address).path
}
}
Restlet wrapRestlet(Restlet restlet, String path) {
return restlet
}
}

View File

@ -294,6 +294,9 @@ include(":instrumentation:redisson-3.0:javaagent")
include(":instrumentation:restlet:restlet-1.0:javaagent") include(":instrumentation:restlet:restlet-1.0:javaagent")
include(":instrumentation:restlet:restlet-1.0:library") include(":instrumentation:restlet:restlet-1.0:library")
include(":instrumentation:restlet:restlet-1.0:testing") include(":instrumentation:restlet:restlet-1.0:testing")
include(":instrumentation:restlet:restlet-2.0:javaagent")
include(":instrumentation:restlet:restlet-2.0:library")
include(":instrumentation:restlet:restlet-2.0:testing")
include(":instrumentation:rmi:bootstrap") include(":instrumentation:rmi:bootstrap")
include(":instrumentation:rmi:javaagent") include(":instrumentation:rmi:javaagent")
include(":instrumentation:rocketmq-client-4.8:javaagent") include(":instrumentation:rocketmq-client-4.8:javaagent")