Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

package io.opentelemetry.exporter.internal;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

/**
* Utilities for validating exporter endpoints.
Expand All @@ -30,12 +32,29 @@ public static URI validateEndpoint(String endpoint) {
throw new IllegalArgumentException(
"Invalid endpoint, must start with http:// or https://: " + uri);
}
if (uri.getHost() == null) {
if (!hasHost(uri, endpoint)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve a usable host for accepted endpoints

When this fallback is taken, including for the three new test cases, the method still returns the original URI, whose getHost() is null and whose getPort() is -1. Supported consumers cannot use that representation: JdkHttpSender.java:198 passes it to HttpRequest.Builder.uri, which throws IllegalArgumentException: unsupported URI, while UpstreamGrpcSenderProvider.java:71 passes the null host and invalid port to ManagedChannelBuilder.forAddress. Consequently, these newly accepted endpoints still fail whenever the JDK HTTP or managed-channel gRPC sender is selected; the parsed host and port must be preserved or those consumers must use a parser that supports these names.

Useful? React with 👍 / 👎.

throw new IllegalArgumentException(
"Invalid endpoint, must start with http:// or https://: " + uri);
}
return uri;
}

/**
* {@link URI#getHost()} follows RFC 2396 and returns {@code null} for some valid DNS names
* (JDK-8188305), for example a label that starts with a digit. {@link URL#getHost()} accepts
* those names, matching {@code OtlpConfigUtil.validateEndpoint}.
*/
private static boolean hasHost(URI uri, String endpoint) {
if (uri.getHost() != null) {
return true;
}
try {
String host = new URL(endpoint).getHost();
return host != null && !host.isEmpty();
} catch (MalformedURLException e) {
return false;
}
}

private EndpointUtil() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ private static Stream<Arguments> validEndpoints() {
Arguments.argumentSet("http", "http://localhost:4318"),
Arguments.argumentSet("https", "https://localhost:4317"),
Arguments.argumentSet("path", "http://localhost:4318/v1/traces"),
Arguments.argumentSet("userinfo", "http://foo:bar@localhost:4317/path"));
Arguments.argumentSet("userinfo", "http://foo:bar@localhost:4317/path"),
// URI.getHost() is null for these (JDK-8188305); they are valid DNS names.
Arguments.argumentSet(
"dns label starting with digit", "http://otlp.1234-k8s-namespace:4318"),
Arguments.argumentSet(
"dns label starting with digit, path",
"http://otlp-collector.14014-mosaik:4318/v1/metrics"),
Arguments.argumentSet(
"dns label starting with digit, userinfo", "http://foo:bar@otlp.1234-ns:4317/path"));
}

@ParameterizedTest
Expand All @@ -42,6 +50,7 @@ private static Stream<Arguments> invalidEndpoints() {
return Stream.of(
Arguments.argumentSet("opaque, no host", "http:localhost:4317"),
Arguments.argumentSet("single slash, no host", "https:/foo"),
Arguments.argumentSet("empty host with port", "http://:4318"),
Arguments.argumentSet("no scheme", "localhost"),
Arguments.argumentSet("wrong scheme", "gopher://localhost"));
}
Expand Down
Loading