diff --git a/common/http/src/main/java/io/helidon/common/http/Http.java b/common/http/src/main/java/io/helidon/common/http/Http.java
index 2ff7d4ee150..58fb1578168 100644
--- a/common/http/src/main/java/io/helidon/common/http/Http.java
+++ b/common/http/src/main/java/io/helidon/common/http/Http.java
@@ -1615,6 +1615,11 @@ public static final class HeaderValues {
*/
public static final HeaderValue CACHE_NORMAL = Header.createCached(Header.CACHE_CONTROL, "no-transform");
+ /**
+ * TE header set to {@code trailers}, used to enable trailer headers.
+ */
+ public static final HeaderValue TE_TRAILERS = Header.createCached(Header.TE, "trailers");
+
private HeaderValues() {
}
}
diff --git a/examples/nima/tracing/src/main/java/io/helidon/examples/nima/tracing/TracingMain.java b/examples/nima/tracing/src/main/java/io/helidon/examples/nima/tracing/TracingMain.java
index ca4b0842d10..d3129c9d1c6 100644
--- a/examples/nima/tracing/src/main/java/io/helidon/examples/nima/tracing/TracingMain.java
+++ b/examples/nima/tracing/src/main/java/io/helidon/examples/nima/tracing/TracingMain.java
@@ -23,7 +23,7 @@
import io.helidon.nima.webserver.http.ServerRequest;
import io.helidon.nima.webserver.http.ServerResponse;
import io.helidon.nima.webserver.http1.Http1Route;
-import io.helidon.nima.webserver.tracing.TracingSupport;
+import io.helidon.nima.webserver.tracing.TracingFeature;
import io.helidon.tracing.Span;
import io.helidon.tracing.Tracer;
import io.helidon.tracing.TracerBuilder;
@@ -51,7 +51,7 @@ public static void main(String[] args) {
.port(8080)
.host("127.0.0.1")
.routing(router -> router
- .update(TracingSupport.create(tracer)::register)
+ .addFeature(TracingFeature.create(tracer))
.route(Http1Route.route(GET, "/versionspecific", new TracedHandler(tracer, "HTTP/1.1 route")))
.route(Http2Route.route(GET, "/versionspecific", new TracedHandler(tracer, "HTTP/2 route")))
)
diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsService.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsService.java
index 51a3f5be141..527d44fcb62 100644
--- a/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsService.java
+++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/JaxRsService.java
@@ -136,10 +136,6 @@ public void routing(HttpRules rules) {
rules.any(this::handle);
}
- private void handle(ServerRequest req, ServerResponse res) {
- Contexts.runInContext(req.context(), () -> doHandle(req.context(), req, res));
- }
-
@Override
public void beforeStart() {
appHandler.onStartup(container);
@@ -187,14 +183,20 @@ private static URI baseUri(ServerRequest req) {
return URI.create(uri);
}
+ private void handle(ServerRequest req, ServerResponse res) {
+ Contexts.runInContext(req.context(), () -> doHandle(req.context(), req, res));
+ }
+
private void doHandle(Context ctx, ServerRequest req, ServerResponse res) {
URI baseUri = baseUri(req);
URI requestUri;
+ String rawPath = req.path().rawPath();
+ rawPath = rawPath.startsWith("/") ? rawPath.substring(1) : rawPath;
if (req.query().isEmpty()) {
- requestUri = baseUri.resolve(req.path().rawPath());
+ requestUri = baseUri.resolve(rawPath);
} else {
- requestUri = baseUri.resolve(req.path().rawPath() + "?" + req.query().rawValue());
+ requestUri = baseUri.resolve(rawPath + "?" + req.query().rawValue());
}
ContainerRequest requestContext = new ContainerRequest(baseUri,
@@ -347,14 +349,16 @@ public void setSuspendTimeout(long l, TimeUnit timeUnit) throws IllegalStateExce
@Override
public void commit() {
- if (outputStream != null) {
- try {
+ try {
+ if (outputStream == null) {
+ res.outputStream().close();
+ } else {
outputStream.close();
- cdl.countDown();
- } catch (IOException e) {
- cdl.countDown();
- throw new UncheckedIOException(e);
}
+ cdl.countDown();
+ } catch (IOException e) {
+ cdl.countDown();
+ throw new UncheckedIOException(e);
}
}
diff --git a/microprofile/tests/tck/pom.xml b/microprofile/tests/tck/pom.xml
index 55adbe508bb..d1096ec6e9a 100644
--- a/microprofile/tests/tck/pom.xml
+++ b/microprofile/tests/tck/pom.xml
@@ -43,9 +43,7 @@
tck-jwt-auth
-->
tck-openapi
-
-
+ tck-opentracing
tck-rest-client
tck-reactive-operators
diff --git a/microprofile/tests/tck/tck-opentracing/logging.properties b/microprofile/tests/tck/tck-opentracing/logging.properties
new file mode 100644
index 00000000000..bf874ecb8c4
--- /dev/null
+++ b/microprofile/tests/tck/tck-opentracing/logging.properties
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2022 Oracle and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=WARNING
+
+io.helidon.level=INFO
+
+
+
diff --git a/microprofile/tracing/pom.xml b/microprofile/tracing/pom.xml
index 3e935fe139c..ab2659c48eb 100644
--- a/microprofile/tracing/pom.xml
+++ b/microprofile/tracing/pom.xml
@@ -40,6 +40,10 @@
jakarta.enterprise.cdi-api
provided
+
+ io.helidon.nima.webserver
+ helidon-nima-webserver-tracing
+
io.helidon.tracing
helidon-tracing-opentracing
diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java
index b3afaa669f4..1b72795703b 100644
--- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java
+++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java
@@ -65,7 +65,7 @@ public MpTracingContextFilter(@Context Provider request) {
public void filter(ContainerRequestContext requestContext) {
ServerRequest serverRequest = this.request.get();
- Tracer tracer = Tracer.global();
+ Tracer tracer = serverRequest.context().get(Tracer.class).orElseGet(Tracer::global);
Optional parentSpan = Span.current().map(Span::context);
boolean clientEnabled = config.getOptionalValue("tracing.client.enabled", Boolean.class).orElse(true);
diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracingCdiExtension.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracingCdiExtension.java
index b260403c1c1..9d9a8bb98f9 100644
--- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracingCdiExtension.java
+++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracingCdiExtension.java
@@ -22,6 +22,8 @@
import io.helidon.config.Config;
import io.helidon.microprofile.server.JaxRsApplication;
import io.helidon.microprofile.server.JaxRsCdiExtension;
+import io.helidon.microprofile.server.ServerCdiExtension;
+import io.helidon.nima.webserver.tracing.TracingFeature;
import io.helidon.tracing.TracerBuilder;
import io.opentelemetry.opentracingshim.OpenTracingShim;
@@ -57,6 +59,7 @@ private void observeBeforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd) {
private void prepareTracer(@Observes @Priority(PLATFORM_BEFORE + 1) @Initialized(ApplicationScoped.class) Object event,
BeanManager bm) {
JaxRsCdiExtension jaxrs = bm.getExtension(JaxRsCdiExtension.class);
+ ServerCdiExtension server = bm.getExtension(ServerCdiExtension.class);
Config config = ((Config) ConfigProvider.getConfig()).get("tracing");
@@ -105,9 +108,8 @@ private void prepareTracer(@Observes @Priority(PLATFORM_BEFORE + 1) @Initialized
- // TODO Helidon Níma
-// server.serverRoutingBuilder()
-// .register(WebTracingConfig.create(config));
+ server.serverRoutingBuilder()
+ .addFeature(TracingFeature.create(helidonTracer, config));
jaxRsApps
.forEach(app -> app.resourceConfig().register(MpTracingFilter.class));
diff --git a/microprofile/tracing/src/main/java/module-info.java b/microprofile/tracing/src/main/java/module-info.java
index 1ffdaec060a..1cf6d6b19b7 100644
--- a/microprofile/tracing/src/main/java/module-info.java
+++ b/microprofile/tracing/src/main/java/module-info.java
@@ -36,6 +36,7 @@
requires io.helidon.common;
requires io.helidon.nima.webserver;
requires io.helidon.jersey.common;
+ requires io.helidon.nima.webserver.tracing;
requires transitive io.helidon.tracing;
requires transitive io.helidon.tracing.jersey;
requires io.helidon.tracing.tracerresolver;
diff --git a/nima/webserver/tracing/pom.xml b/nima/webserver/tracing/pom.xml
index 6f4643a9c8c..56c939c0a8e 100644
--- a/nima/webserver/tracing/pom.xml
+++ b/nima/webserver/tracing/pom.xml
@@ -41,6 +41,10 @@
io.helidon.tracing
helidon-tracing
+
+ io.helidon.tracing
+ helidon-tracing-config
+
io.helidon.nima.testing.junit5
helidon-nima-testing-junit5-webserver
diff --git a/nima/webserver/tracing/src/main/java/io/helidon/nima/webserver/tracing/PathTracingConfig.java b/nima/webserver/tracing/src/main/java/io/helidon/nima/webserver/tracing/PathTracingConfig.java
new file mode 100644
index 00000000000..4effe1e6ea5
--- /dev/null
+++ b/nima/webserver/tracing/src/main/java/io/helidon/nima/webserver/tracing/PathTracingConfig.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2019, 2022 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.nima.webserver.tracing;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import io.helidon.common.http.Http;
+import io.helidon.common.http.PathMatcher;
+import io.helidon.common.http.PathMatchers;
+import io.helidon.common.uri.UriPath;
+import io.helidon.config.Config;
+import io.helidon.tracing.config.TracingConfig;
+
+/**
+ * Traced system configuration for web server for a specific path.
+ */
+public interface PathTracingConfig {
+ /**
+ * Create a new traced path configuration from {@link io.helidon.config.Config}.
+ *
+ * @param config config of a path
+ * @return traced path configuration
+ */
+ static PathTracingConfig create(Config config) {
+ return builder().config(config).build();
+ }
+
+ /**
+ * Create a new builder to configure traced path configuration.
+ *
+ * @return a new builder instance
+ */
+ static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Method used by Helidon to check if this tracing is valid for the specified method and path.
+ *
+ * @param method HTTP method
+ * @param path invoked path
+ * @return {@code true} if matched
+ */
+ boolean matches(Http.Method method, UriPath path);
+
+ /**
+ * Associated configuration of tracing valid for the configured path and (possibly) methods.
+ *
+ * @return traced system configuration
+ */
+ TracingConfig tracedConfig();
+
+ /**
+ * Fluent API builder for {@link io.helidon.nima.webserver.tracing.PathTracingConfig}.
+ */
+ final class Builder implements io.helidon.common.Builder {
+ private final List methods = new LinkedList<>();
+ private String path;
+ private TracingConfig tracedConfig;
+
+ private Builder() {
+ }
+
+ @Override
+ public PathTracingConfig build() {
+ // immutable
+ final Collection finalMethods = methods.stream()
+ .map(Http.Method::create)
+ .toList();
+ final TracingConfig finalTracingConfig = tracedConfig;
+
+ PathMatcher pathMatcher = PathMatchers.create(path);
+ Http.MethodPredicate methodPredicate = Http.Method.predicate(finalMethods);
+
+ return new PathTracingConfig() {
+ @Override
+ public boolean matches(Http.Method method, UriPath path) {
+ return methodPredicate.test(method) && pathMatcher.match(path).accepted();
+ }
+
+ @Override
+ public TracingConfig tracedConfig() {
+ return finalTracingConfig;
+ }
+
+ @Override
+ public String toString() {
+ return path + "(" + finalMethods + "): " + finalTracingConfig;
+ }
+ };
+ }
+
+ /**
+ * Update this builder from provided {@link io.helidon.config.Config}.
+ *
+ * @param config config to update this builder from
+ * @return updated builder instance
+ */
+ public Builder config(Config config) {
+ path(config.get("path").asString().get());
+ List methods = config.get("methods").asList(String.class).orElse(null);
+ if (null != methods) {
+ methods(methods);
+ }
+ tracingConfig(TracingConfig.create(config));
+
+ return this;
+ }
+
+ /**
+ * Path to register the traced configuration on.
+ *
+ * @param path path as understood by {@link io.helidon.nima.webserver.http.HttpRouting.Builder} of web server
+ * @return updated builder instance
+ */
+ public Builder path(String path) {
+ this.path = path;
+ return this;
+ }
+
+ /**
+ * HTTP methods to restrict registration of this configuration on web server.
+ *
+ * @param methods list of methods to use, empty means all methods
+ * @return updated builder instance
+ */
+ public Builder methods(List methods) {
+ this.methods.clear();
+ this.methods.addAll(methods);
+ return this;
+ }
+
+ /**
+ * Add a new HTTP method to restrict this configuration for.
+ *
+ * @param method method to add to the list of supported methods
+ * @return updated builder instance
+ */
+ public Builder addMethod(String method) {
+ this.methods.add(method);
+ return this;
+ }
+
+ /**
+ * Configuration of a traced system to use on this path and possibly method(s).
+ *
+ * @param tracedConfig configuration of components, spans and span logs
+ * @return updated builder instance
+ */
+ public Builder tracingConfig(TracingConfig tracedConfig) {
+ this.tracedConfig = tracedConfig;
+ return this;
+ }
+ }
+}
diff --git a/nima/webserver/tracing/src/main/java/io/helidon/nima/webserver/tracing/TracingFeature.java b/nima/webserver/tracing/src/main/java/io/helidon/nima/webserver/tracing/TracingFeature.java
new file mode 100644
index 00000000000..ba88ea41696
--- /dev/null
+++ b/nima/webserver/tracing/src/main/java/io/helidon/nima/webserver/tracing/TracingFeature.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2019, 2022 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.nima.webserver.tracing;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import io.helidon.common.context.Context;
+import io.helidon.common.context.Contexts;
+import io.helidon.common.http.Http;
+import io.helidon.common.http.HttpPrologue;
+import io.helidon.config.Config;
+import io.helidon.nima.webserver.http.Filter;
+import io.helidon.nima.webserver.http.FilterChain;
+import io.helidon.nima.webserver.http.HttpFeature;
+import io.helidon.nima.webserver.http.HttpRouting;
+import io.helidon.nima.webserver.http.RoutingRequest;
+import io.helidon.nima.webserver.http.RoutingResponse;
+import io.helidon.nima.webserver.http.ServerRequest;
+import io.helidon.tracing.HeaderProvider;
+import io.helidon.tracing.Scope;
+import io.helidon.tracing.Span;
+import io.helidon.tracing.SpanContext;
+import io.helidon.tracing.Tag;
+import io.helidon.tracing.Tracer;
+import io.helidon.tracing.config.SpanTracingConfig;
+import io.helidon.tracing.config.TracingConfig;
+
+/**
+ * Tracing configuration for webserver.
+ * Tracing configuration has two components - an overall (application wide) {@link io.helidon.tracing.config.TracingConfig}
+ * and a path specific {@link io.helidon.nima.webserver.tracing.PathTracingConfig}.
+ */
+public class TracingFeature implements HttpFeature {
+ private final boolean enabled;
+ private final Tracer tracer;
+ private final TracingConfig envConfig;
+ private final List pathConfigs;
+
+ /**
+ * No side effects.
+ */
+ private TracingFeature(Builder builder) {
+ this.enabled = builder.enabled;
+ this.tracer = builder.tracer;
+ this.envConfig = builder.tracedConfig;
+ this.pathConfigs = List.copyOf(builder.pathTracingConfigs);
+ }
+
+ /**
+ * Create a tracing configuration that is enabled for all paths and spans (that are enabled by default).
+ *
+ * @param tracer tracer to use for tracing spans created by this feature
+ * @return tracing configuration to register with
+ * {@link
+ * io.helidon.nima.webserver.http.HttpRouting.Builder#register(java.util.function.Supplier[])}
+ */
+ public static TracingFeature create(Tracer tracer) {
+ return create(tracer, TracingConfig.ENABLED);
+ }
+
+ /**
+ * Create a new tracing support base on {@link io.helidon.tracing.config.TracingConfig}.
+ *
+ * @param tracer tracer to use for tracing spans created by this feature
+ * @param configuration traced system configuration
+ * @return a new tracing support to register with web server routing
+ */
+ public static TracingFeature create(Tracer tracer, TracingConfig configuration) {
+ return builder().tracer(tracer).envConfig(configuration).build();
+ }
+
+ /**
+ * Create a new tracing support base on {@link io.helidon.config.Config}.
+ *
+ * @param tracer tracer to use for tracing spans created by this feature
+ * @param config to base this support on
+ * @return a new tracing support to register with web server routing
+ */
+ public static TracingFeature create(Tracer tracer, Config config) {
+ return builder().tracer(tracer).config(config).build();
+ }
+
+ /**
+ * A fluent API builder to create tracing support.
+ *
+ * @return a new builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public void setup(HttpRouting.Builder routing) {
+ if (enabled) {
+ // and now register the tracing of requests
+ routing.addFilter(new TracingFilter(tracer, envConfig, pathConfigs));
+ }
+ }
+
+ /**
+ * A fluent API builder for {@link TracingFeature}.
+ */
+ public static class Builder implements io.helidon.common.Builder {
+ private final List pathTracingConfigs = new LinkedList<>();
+
+ private TracingConfig tracedConfig = TracingConfig.ENABLED;
+ private Tracer tracer;
+ private boolean enabled = true;
+
+ /**
+ * OpenTracing spec states that certain MP paths need to be disabled by default.
+ * Note that if a user changes the default location of any of these using
+ * web-context's, then they would need to provide these exclusions manually.
+ *
+ * The default path configs below are overridable via configuration. For example,
+ * health could be enabled by setting {@code tracing.paths.0.path=/health} and
+ * {@code tracing.paths.0.enabled=true}.
+ */
+ Builder() {
+ addPathConfig(PathTracingConfig.builder()
+ .path("/metrics/*")
+ .tracingConfig(TracingConfig.DISABLED)
+ .build());
+ addPathConfig(PathTracingConfig.builder()
+ .path("/health/*")
+ .tracingConfig(TracingConfig.DISABLED)
+ .build());
+ addPathConfig(PathTracingConfig.builder()
+ .path("/openapi/*")
+ .tracingConfig(TracingConfig.DISABLED)
+ .build());
+ }
+
+ @Override
+ public TracingFeature build() {
+ if (tracer == null) {
+ throw new IllegalArgumentException("Tracing feature must be configured with a tracer");
+ }
+ return new TracingFeature(this);
+ }
+
+ /**
+ * Add a path specific configuration of tracing.
+ *
+ * @param pathTracingConfig configuration of tracing for a specific path
+ * @return updated builder instance
+ */
+ public Builder addPathConfig(PathTracingConfig pathTracingConfig) {
+ this.pathTracingConfigs.add(pathTracingConfig);
+ return this;
+ }
+
+ /**
+ * Use the provided configuration as a default for any request.
+ *
+ * @param tracingConfig default web server tracing configuration
+ * @return updated builder instance
+ */
+ public Builder envConfig(TracingConfig tracingConfig) {
+ this.tracedConfig = tracingConfig;
+ return this;
+ }
+
+ /**
+ * Update builder from {@link io.helidon.config.Config}.
+ *
+ * @param config config to read default configuration and path specific configuration from
+ * @return updated builder instance
+ */
+ public Builder config(Config config) {
+ // read the overall configuration
+ envConfig(TracingConfig.create(config));
+
+ // and then the paths
+ Config allPaths = config.get("paths");
+ allPaths.asNodeList().ifPresent(this::addPaths);
+ enabled(tracedConfig.enabled());
+ return this;
+ }
+
+ /**
+ * Explicitly enable/disable tracing feature.
+ *
+ * @param enabled if set to {@code false}, this feature will be disabled and tracing filter will never be registered
+ * @return updated builder
+ */
+ public Builder enabled(boolean enabled) {
+ this.enabled = enabled;
+ return this;
+ }
+
+ /**
+ * Tracer to use to extract inbound span context.
+ *
+ * @param tracer tracer to use
+ * @return updated builder
+ */
+ public Builder tracer(Tracer tracer) {
+ this.tracer = tracer;
+ return this;
+ }
+
+ private void addPaths(List configs) {
+ configs.stream()
+ .map(PathTracingConfig::create)
+ .forEach(this::addPathConfig);
+ }
+ }
+
+ private static class TracingFilter implements Filter {
+ private static final String TRACING_SPAN_HTTP_REQUEST = "HTTP Request";
+ private final Tracer tracer;
+ private final TracingConfig envConfig;
+ private final List pathConfigs;
+
+ TracingFilter(Tracer tracer, TracingConfig envConfig, List pathConfigs) {
+ this.tracer = tracer;
+ this.envConfig = envConfig;
+ this.pathConfigs = pathConfigs;
+ }
+
+ @Override
+ public void filter(FilterChain chain, RoutingRequest req, RoutingResponse res) {
+ // context of the request - we register configuration and parent spans to it
+ Context context = req.context();
+
+ TracingConfig resolved = configureTracingConfig(req, context);
+
+ /*
+ Extract inbound span context, this will act as a parent of the new webserver span
+ */
+ Optional inboundSpanContext = tracer.extract(new NimaHeaderProvider(req));
+
+ /*
+ Find configuration of the web server span (can customize name, disable etc.)
+ */
+ SpanTracingConfig spanConfig = resolved.spanConfig("web-server", TRACING_SPAN_HTTP_REQUEST);
+ if (!spanConfig.enabled()) {
+ // nope, do not start this span, but still register parent span context for components further down
+ if (inboundSpanContext.isPresent()) {
+ context.register(inboundSpanContext.get());
+ context.register(TracingConfig.class, inboundSpanContext.get());
+ }
+ Contexts.runInContext(context, chain::proceed);
+ return;
+ }
+ /*
+ Create web server span
+ */
+ HttpPrologue prologue = req.prologue();
+
+ String spanName = spanConfig.newName().orElse(TRACING_SPAN_HTTP_REQUEST);
+ if (spanName.indexOf('%') > -1) {
+ spanName = String.format(spanName, prologue.method().text(), req.path().rawPath(), req.query().rawValue());
+ }
+ // tracing is enabled, so we replace the parent span with web server parent span
+ Span span = tracer.spanBuilder(spanName)
+ .kind(Span.Kind.SERVER)
+ .update(it -> inboundSpanContext.ifPresent(it::parent))
+ .start();
+
+ context.register(span.context());
+ context.register(TracingConfig.class, span.context());
+
+ try (Scope ignored = span.activate()) {
+ span.tag(Tag.COMPONENT.create("helidon-nima-webserver"));
+ span.tag(Tag.HTTP_METHOD.create(prologue.method().text()));
+ span.tag(Tag.HTTP_URL.create(prologue.protocol() + "://" + req.authority() + "/" + prologue.uriPath().path()));
+ span.tag(Tag.HTTP_VERSION.create(prologue.protocolVersion()));
+
+ Contexts.runInContext(context, chain::proceed);
+
+ Http.Status status = res.status();
+ span.tag(Tag.HTTP_STATUS.create(status.code()));
+
+ if (status.code() >= 400) {
+ span.status(Span.Status.ERROR);
+ span.addEvent("error", Map.of("message", "Response HTTP status: " + status,
+ "error.kind", status.code() < 500 ? "ClientError" : "ServerError"));
+ } else {
+ span.status(Span.Status.OK);
+ }
+
+ span.end();
+ } catch (Exception e) {
+ span.end(e);
+ throw e;
+ }
+ }
+
+ private TracingConfig configureTracingConfig(RoutingRequest req, Context context) {
+ TracingConfig discovered = null;
+
+ for (PathTracingConfig pathConfig : pathConfigs) {
+ if (pathConfig.matches(req.prologue().method(), req.prologue().uriPath())) {
+ if (discovered == null) {
+ discovered = pathConfig.tracedConfig();
+ } else {
+ discovered = TracingConfig.merge(discovered, pathConfig.tracedConfig());
+ }
+ }
+ }
+
+ if (discovered == null) {
+ context.register(envConfig);
+ return envConfig;
+ } else {
+ context.register(discovered);
+ return discovered;
+ }
+ }
+ }
+
+ private static class NimaHeaderProvider implements HeaderProvider {
+ private final ServerRequest request;
+
+ private NimaHeaderProvider(ServerRequest request) {
+ this.request = request;
+ }
+
+ @Override
+ public Iterable keys() {
+ List result = new LinkedList<>();
+ for (Http.HeaderValue header : request.headers()) {
+ result.add(header.headerName().lowerCase());
+ }
+ return result;
+ }
+
+ @Override
+ public Optional get(String key) {
+ return request.headers().first(Http.Header.create(key));
+ }
+
+ @Override
+ public Iterable getAll(String key) {
+ return request.headers().all(Http.Header.create(key), List::of);
+ }
+
+ @Override
+ public boolean contains(String key) {
+ return request.headers().contains(Http.Header.create(key));
+ }
+ }
+}
diff --git a/nima/webserver/tracing/src/main/java/io/helidon/nima/webserver/tracing/TracingSupport.java b/nima/webserver/tracing/src/main/java/io/helidon/nima/webserver/tracing/TracingSupport.java
deleted file mode 100644
index b68cc2ac31d..00000000000
--- a/nima/webserver/tracing/src/main/java/io/helidon/nima/webserver/tracing/TracingSupport.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (c) 2022 Oracle and/or its affiliates.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.helidon.nima.webserver.tracing;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Optional;
-
-import io.helidon.common.http.Http;
-import io.helidon.common.http.HttpPrologue;
-import io.helidon.nima.webserver.http.Filter;
-import io.helidon.nima.webserver.http.FilterChain;
-import io.helidon.nima.webserver.http.HttpRouting;
-import io.helidon.nima.webserver.http.RoutingRequest;
-import io.helidon.nima.webserver.http.RoutingResponse;
-import io.helidon.tracing.HeaderProvider;
-import io.helidon.tracing.Scope;
-import io.helidon.tracing.Span;
-import io.helidon.tracing.SpanContext;
-import io.helidon.tracing.Tag;
-import io.helidon.tracing.Tracer;
-
-/**
- * Open Telemetry tracing support.
- */
-public class TracingSupport {
- private final boolean enabled;
- private final Filter filter;
-
- private TracingSupport(Filter filter) {
- this.enabled = true;
- this.filter = filter;
- }
-
- private TracingSupport() {
- this.enabled = false;
- this.filter = null;
- }
-
- /**
- * Create new support from an tracer instance.
- *
- * @param tracer tracer
- * @return new tracing support
- */
- public static TracingSupport create(Tracer tracer) {
- if (tracer.enabled()) {
- return new TracingSupport(new TracingFilter(tracer));
- } else {
- return new TracingSupport();
- }
- }
-
- /**
- * Register tracing support filter with the HTTP routing.
- *
- * @param builder routing builder
- */
- public void register(HttpRouting.Builder builder) {
- if (enabled) {
- builder.addFilter(filter);
- }
- }
-
- private static class TracingFilter implements Filter {
- private final Tracer tracer;
-
- private TracingFilter(Tracer tracer) {
- this.tracer = tracer;
- }
-
- @Override
- public void filter(FilterChain chain, RoutingRequest req, RoutingResponse res) {
- Optional context = tracer.extract(new NimaHeaderProvider(req));
-
- HttpPrologue prologue = req.prologue();
-
- Span span = tracer.spanBuilder(prologue.method().text()
- + " " + req.path().rawPath()
- + " " + prologue.protocol()
- + "/" + prologue.protocolVersion())
- .kind(Span.Kind.SERVER)
- .update(it -> context.ifPresent(it::parent))
- .start();
-
- try (Scope ignored = span.activate()) {
- span.tag(Tag.HTTP_METHOD.create(prologue.method().text()));
- span.tag(Tag.HTTP_URL.create(prologue.protocol() + "://" + req.authority() + "/" + prologue.uriPath().path()));
- span.tag(Tag.HTTP_VERSION.create(prologue.protocolVersion()));
-
- chain.proceed();
- } finally {
- Http.Status status = res.status();
- span.tag(Tag.HTTP_STATUS.create(status.code()));
-
- if (status.code() >= 400) {
- span.status(Span.Status.ERROR);
- } else {
- span.status(Span.Status.OK);
- }
-
- span.end();
- }
- }
-
- private static class NimaHeaderProvider implements HeaderProvider {
- private final RoutingRequest request;
-
- private NimaHeaderProvider(RoutingRequest request) {
- this.request = request;
- }
-
- @Override
- public Iterable keys() {
- List result = new LinkedList<>();
- for (Http.HeaderValue header : request.headers()) {
- result.add(header.headerName().lowerCase());
- }
- return result;
- }
-
- @Override
- public Optional get(String key) {
- return request.headers().first(Http.Header.create(key));
- }
-
- @Override
- public Iterable getAll(String key) {
- return request.headers().all(Http.Header.create(key), List::of);
- }
-
- @Override
- public boolean contains(String key) {
- return request.headers().contains(Http.Header.create(key));
- }
- }
- }
-}
diff --git a/nima/webserver/tracing/src/main/java/module-info.java b/nima/webserver/tracing/src/main/java/module-info.java
index b6c050dc892..a09b7d08595 100644
--- a/nima/webserver/tracing/src/main/java/module-info.java
+++ b/nima/webserver/tracing/src/main/java/module-info.java
@@ -21,6 +21,7 @@
requires io.helidon.common.http;
requires io.helidon.nima.webserver;
requires io.helidon.tracing;
+ requires io.helidon.tracing.config;
exports io.helidon.nima.webserver.tracing;
}
\ No newline at end of file
diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRouting.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRouting.java
index f0f8cf38087..335ec1a202b 100644
--- a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRouting.java
+++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRouting.java
@@ -45,11 +45,10 @@ public final class HttpRouting implements Routing {
private final Filters filters;
private final ServiceRoute rootRoute;
- private final ErrorHandlers errorHandlers;
private final List features;
private HttpRouting(Builder builder) {
- this.errorHandlers = ErrorHandlers.create(builder.errorHandlers);
+ ErrorHandlers errorHandlers = ErrorHandlers.create(builder.errorHandlers);
this.filters = Filters.create(errorHandlers, List.copyOf(builder.filters));
this.rootRoute = builder.rootRules.build();
this.features = List.copyOf(builder.features);
diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/RouteCrawler.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/RouteCrawler.java
index 45ef287eee6..91691d55a7a 100644
--- a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/RouteCrawler.java
+++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/RouteCrawler.java
@@ -190,7 +190,7 @@ public Parameters pathParameters() {
@Override
public RoutedPath absolute() {
- return new CrawlerRoutedPath(path, templateParams);
+ return new CrawlerRoutedPath(path.absolute(), templateParams);
}
}
}
diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1ServerResponse.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1ServerResponse.java
index 62d180be4f4..0a58a02f23b 100644
--- a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1ServerResponse.java
+++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1ServerResponse.java
@@ -312,14 +312,16 @@ public void close() {
}
if (isChunked || forcedChunked) {
- // not optimized, we need to write trailers
- trailers.set(STREAM_STATUS_NAME, String.valueOf(status.get().code()));
- trailers.set(STREAM_RESULT_NAME, streamResult.get());
- BufferData buffer = BufferData.growing(128);
- writeHeaders(trailers, buffer);
- buffer.write('\r'); // "\r\n" - empty line after headers
- buffer.write('\n');
- dataWriter.write(buffer);
+ if (request.headers().contains(HeaderValues.TE_TRAILERS)) {
+ // not optimized, trailers enabled: we need to write trailers
+ trailers.set(STREAM_STATUS_NAME, String.valueOf(status.get().code()));
+ trailers.set(STREAM_RESULT_NAME, streamResult.get());
+ BufferData buffer = BufferData.growing(128);
+ writeHeaders(trailers, buffer);
+ buffer.write('\r'); // "\r\n" - empty line after headers
+ buffer.write('\n');
+ dataWriter.write(buffer);
+ }
}
responseCloseRunnable.run();
@@ -365,8 +367,10 @@ private void write(BufferData buffer) throws IOException {
}
if (firstByte) {
- // proper stream with multiple buffers, write status amd headers
- headers.add(STREAM_TRAILERS);
+ if (request.headers().contains(HeaderValues.TE_TRAILERS)) {
+ // proper stream with multiple buffers, write status amd headers
+ headers.add(STREAM_TRAILERS);
+ }
sendHeadersAndPrepare();
firstByte = false;
BufferData combined = BufferData.create(firstBuffer, buffer);