Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/Aspire.Hosting/Dcp/DcpExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,7 @@ private static (string, EndpointBindingMode) NormalizeTargetHost(string targetHo
{
null or "" => ("localhost", EndpointBindingMode.SingleAddress), // Default is localhost
var s when string.Equals(s, "localhost", StringComparison.OrdinalIgnoreCase) => ("localhost", EndpointBindingMode.SingleAddress), // Explicitly set to localhost
var s when s.Length > 10 && s.EndsWith(".localhost", StringComparison.OrdinalIgnoreCase) => ("localhost", EndpointBindingMode.SingleAddress), // Explicitly set to localhost when using .localhost subdomain
var s when IPAddress.TryParse(s, out var ipAddress) => ipAddress switch // The host is an IP address
{
var ip when IPAddress.Any.Equals(ip) => ("localhost", EndpointBindingMode.IPv4AnyAddresses), // 0.0.0.0 (IPv4 all addresses)
Expand Down
17 changes: 16 additions & 1 deletion src/Aspire.Hosting/Orchestrator/ApplicationOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,23 @@ private async Task ProcessResourceUrlCallbacks(IResource resource, CancellationT
Debug.Assert(endpoint.AllocatedEndpoint is not null, "Endpoint should be allocated at this point as we're calling this from ResourceEndpointsAllocatedEvent handler.");
if (endpoint.AllocatedEndpoint is { } allocatedEndpoint)
{
var url = new ResourceUrlAnnotation { Url = allocatedEndpoint.UriString, Endpoint = new EndpointReference(resourceWithEndpoints, endpoint) };
var endpointReference = new EndpointReference(resourceWithEndpoints, endpoint);
var url = new ResourceUrlAnnotation { Url = allocatedEndpoint.UriString, Endpoint = endpointReference };
urls.Add(url);
if (allocatedEndpoint.BindingMode != EndpointBindingMode.SingleAddress && (endpoint.TargetHost is not "localhost" or "127.0.0.1"))
{
// Endpoint is listening on multiple addresses so add another URL based on the declared target hostname
// For endpoints targeting all external addresses (IPv4 0.0.0.0 or IPv6 ::) use the machine name
var address = endpoint.TargetHost is "0.0.0.0" or "::" ? Environment.MachineName : endpoint.TargetHost;
url = new ResourceUrlAnnotation { Url = $"{allocatedEndpoint.UriScheme}://{address}:{allocatedEndpoint.Port}", Endpoint = endpointReference };
urls.Add(url);
}
else if (endpoint.TargetHost.Length > 10 && endpoint.TargetHost.EndsWith(".localhost", StringComparison.OrdinalIgnoreCase))
{
// Add the originally declared *.localhost URL
url = new ResourceUrlAnnotation { Url = $"{allocatedEndpoint.UriScheme}://{endpoint.TargetHost}:{allocatedEndpoint.Port}", Endpoint = endpointReference };
urls.Add(url);
}
}
}
}
Expand Down
66 changes: 66 additions & 0 deletions tests/Aspire.Hosting.Tests/WithEndpointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,72 @@ public void WithEndpoint_WithAllArguments_ForwardsAllArguments()
Assert.Equal(System.Net.Sockets.ProtocolType.Tcp, endpoint.Protocol);
}

[Fact]
public async Task LocalhostTopLevelDomainSetsAnnotationValues()
{
using var builder = TestDistributedApplicationBuilder.Create();

var tcs = new TaskCompletionSource();
var projectA = builder.AddProject<ProjectA>("projecta")
.WithHttpsEndpoint()
.WithEndpoint("https", e => e.TargetHost = "example.localhost", createIfNotExists: false)
.OnBeforeResourceStarted((_, _, _) =>
{
tcs.SetResult();
return Task.CompletedTask;
});

var app = await builder.BuildAsync();
await app.StartAsync();
await tcs.Task;

var urls = projectA.Resource.Annotations.OfType<ResourceUrlAnnotation>();
Assert.Collection(urls,
url => Assert.StartsWith("https://localhost:", url.Url),
url => Assert.StartsWith("https://example.localhost:", url.Url));

EndpointAnnotation endpoint = Assert.Single(projectA.Resource.Annotations.OfType<EndpointAnnotation>());
Assert.NotNull(endpoint.AllocatedEndpoint);
Assert.Equal(EndpointBindingMode.SingleAddress, endpoint.AllocatedEndpoint.BindingMode);
Assert.Equal("localhost", endpoint.AllocatedEndpoint.Address);

await app.StopAsync();
}

[Theory]
[InlineData("0.0.0.0", EndpointBindingMode.IPv4AnyAddresses)]
//[InlineData("::", EndpointBindingMode.IPv6AnyAddresses)] // Need to figure out a good way to check that Ipv6 binding is supported
public async Task TopLevelDomainSetsAnnotationValues(string host, EndpointBindingMode endpointBindingMode)
{
using var builder = TestDistributedApplicationBuilder.Create();

var tcs = new TaskCompletionSource();
var projectA = builder.AddProject<ProjectA>("projecta")
.WithHttpsEndpoint()
.WithEndpoint("https", e => e.TargetHost = host, createIfNotExists: false)
.OnBeforeResourceStarted((_, _, _) =>
{
tcs.SetResult();
return Task.CompletedTask;
});

var app = await builder.BuildAsync();
await app.StartAsync();
await tcs.Task;

var urls = projectA.Resource.Annotations.OfType<ResourceUrlAnnotation>();
Assert.Collection(urls,
url => Assert.StartsWith("https://localhost:", url.Url),
url => Assert.StartsWith($"https://{Environment.MachineName}:", url.Url));

EndpointAnnotation endpoint = Assert.Single(projectA.Resource.Annotations.OfType<EndpointAnnotation>());
Assert.NotNull(endpoint.AllocatedEndpoint);
Assert.Equal(endpointBindingMode, endpoint.AllocatedEndpoint.BindingMode);
Assert.Equal("localhost", endpoint.AllocatedEndpoint.Address);

await app.StopAsync();
}

private sealed class TestProject : IProjectMetadata
{
public string ProjectPath => "projectpath";
Expand Down
Loading