Skip to content

Commit d666c35

Browse files
authored
Fix vert.x route containing dupicate segments when RoutingContext.next is used (#12260)
1 parent f26a937 commit d666c35

File tree

6 files changed

+131
-9
lines changed

6 files changed

+131
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx;
7+
8+
import static io.opentelemetry.context.ContextKey.named;
9+
10+
import io.opentelemetry.context.Context;
11+
import io.opentelemetry.context.ContextKey;
12+
import io.opentelemetry.context.ImplicitContextKeyed;
13+
14+
public final class RouteHolder implements ImplicitContextKeyed {
15+
private static final ContextKey<RouteHolder> KEY = named("opentelemetry-vertx-route");
16+
17+
private String route;
18+
19+
private RouteHolder(String route) {
20+
this.route = route;
21+
}
22+
23+
public static Context init(Context context, String route) {
24+
if (context.get(KEY) != null) {
25+
return context;
26+
}
27+
return context.with(new RouteHolder(route));
28+
}
29+
30+
public static String get(Context context) {
31+
RouteHolder holder = context.get(KEY);
32+
return holder != null ? holder.route : null;
33+
}
34+
35+
public static void set(Context context, String route) {
36+
RouteHolder holder = context.get(KEY);
37+
if (holder != null) {
38+
holder.route = route;
39+
}
40+
}
41+
42+
@Override
43+
public Context storeInContext(Context context) {
44+
return context.with(KEY, this);
45+
}
46+
}

instrumentation/vertx/vertx-web-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/RoutingContextHandlerWrapper.java

+4-7
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
55

66
package io.opentelemetry.javaagent.instrumentation.vertx;
77

8-
import static io.opentelemetry.context.ContextKey.named;
9-
108
import io.opentelemetry.api.trace.Span;
119
import io.opentelemetry.context.Context;
12-
import io.opentelemetry.context.ContextKey;
1310
import io.opentelemetry.context.Scope;
1411
import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
1512
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
@@ -24,8 +21,6 @@
2421
/** This is used to wrap Vert.x Handlers to provide nice user-friendly SERVER span names */
2522
public final class RoutingContextHandlerWrapper implements Handler<RoutingContext> {
2623

27-
private static final ContextKey<String> ROUTE_KEY = named("opentelemetry-vertx-route");
28-
2924
private final Handler<RoutingContext> handler;
3025

3126
public RoutingContextHandlerWrapper(Handler<RoutingContext> handler) {
@@ -35,13 +30,15 @@ public RoutingContextHandlerWrapper(Handler<RoutingContext> handler) {
3530
@Override
3631
public void handle(RoutingContext context) {
3732
Context otelContext = Context.current();
33+
// remember currently set route so it could be restored
34+
RoutingContextUtil.setRoute(context, RouteHolder.get(otelContext));
3835
String route = getRoute(otelContext, context);
3936
if (route != null && route.endsWith("/")) {
4037
route = route.substring(0, route.length() - 1);
4138
}
4239
HttpServerRoute.update(otelContext, HttpServerRouteSource.NESTED_CONTROLLER, route);
4340

44-
try (Scope ignore = otelContext.with(ROUTE_KEY, route).makeCurrent()) {
41+
try (Scope ignore = RouteHolder.init(otelContext, route).makeCurrent()) {
4542
handler.handle(context);
4643
} catch (Throwable throwable) {
4744
Span serverSpan = LocalRootSpan.fromContextOrNull(otelContext);
@@ -54,7 +51,7 @@ public void handle(RoutingContext context) {
5451

5552
private static String getRoute(Context otelContext, RoutingContext routingContext) {
5653
String route = routingContext.currentRoute().getPath();
57-
String existingRoute = otelContext.get(ROUTE_KEY);
54+
String existingRoute = RouteHolder.get(otelContext);
5855
return existingRoute != null ? existingRoute + route : route;
5956
}
6057

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
10+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;
13+
14+
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
15+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
16+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
17+
import io.vertx.ext.web.RoutingContext;
18+
import net.bytebuddy.asm.Advice;
19+
import net.bytebuddy.description.type.TypeDescription;
20+
import net.bytebuddy.matcher.ElementMatcher;
21+
22+
public class RoutingContextInstrumentation implements TypeInstrumentation {
23+
@Override
24+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
25+
return hasClassesNamed("io.vertx.ext.web.RoutingContext");
26+
}
27+
28+
@Override
29+
public ElementMatcher<TypeDescription> typeMatcher() {
30+
return implementsInterface(named("io.vertx.ext.web.RoutingContext"));
31+
}
32+
33+
@Override
34+
public void transform(TypeTransformer transformer) {
35+
transformer.applyAdviceToMethod(
36+
isPublic().and(named("next")).and(takesNoArguments()),
37+
this.getClass().getName() + "$NextAdvice");
38+
}
39+
40+
@SuppressWarnings("unused")
41+
public static class NextAdvice {
42+
43+
@Advice.OnMethodEnter(suppress = Throwable.class)
44+
public static void next(@Advice.This RoutingContext routingContext) {
45+
// calling next tells router to move to the next matching route
46+
// restore remembered route to remove currently matched route from it
47+
String previousRoute = RoutingContextUtil.getRoute(routingContext);
48+
if (previousRoute != null) {
49+
RouteHolder.set(Java8BytecodeBridge.currentContext(), previousRoute);
50+
}
51+
}
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx;
7+
8+
import io.opentelemetry.instrumentation.api.util.VirtualField;
9+
import io.vertx.ext.web.RoutingContext;
10+
11+
public final class RoutingContextUtil {
12+
private static final VirtualField<RoutingContext, String> routeField =
13+
VirtualField.find(RoutingContext.class, String.class);
14+
15+
public static void setRoute(RoutingContext routingContext, String route) {
16+
routeField.set(routingContext, route);
17+
}
18+
19+
public static String getRoute(RoutingContext routingContext) {
20+
return routeField.get(routingContext);
21+
}
22+
23+
private RoutingContextUtil() {}
24+
}

instrumentation/vertx/vertx-web-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/VertxWebInstrumentationModule.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
package io.opentelemetry.javaagent.instrumentation.vertx;
77

8-
import static java.util.Collections.singletonList;
8+
import static java.util.Arrays.asList;
99

1010
import com.google.auto.service.AutoService;
1111
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
@@ -21,6 +21,6 @@ public VertxWebInstrumentationModule() {
2121

2222
@Override
2323
public List<TypeInstrumentation> typeInstrumentations() {
24-
return singletonList(new RouteInstrumentation());
24+
return asList(new RouteInstrumentation(), new RoutingContextInstrumentation());
2525
}
2626
}

instrumentation/vertx/vertx-web-3.0/testing/src/main/java/server/AbstractVertxWebServer.java

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public abstract class AbstractVertxWebServer extends AbstractVerticle {
3131
public Router buildRouter() {
3232
Router router = Router.router(vertx);
3333

34+
// verify that calling RoutingContext::next doesn't affect http.route
35+
router.route(SUCCESS.getPath()).handler(RoutingContext::next);
3436
//noinspection Convert2Lambda
3537
router
3638
.route(SUCCESS.getPath())

0 commit comments

Comments
 (0)