Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle parsing wildcard urls in launch profiles #5588

Merged
merged 7 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 7 additions & 1 deletion src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public EndpointAnnotation(ProtocolType protocol, string? uriScheme = null, strin
/// <summary>
/// Desired port for the service
/// </summary>
public int? Port {
public int? Port
{
// For proxy-less Endpoints the client port and target port should be the same.
// Note that this is just a "sensible default"--the consumer of the EndpointAnnotation is free
// to change Port and TargetPort after the annotation is created, but if the final values are inconsistent,
Expand Down Expand Up @@ -104,6 +105,11 @@ public int? TargetPort
/// </summary>
public string UriScheme { get; set; }

/// <summary>
/// This is the address the resource is listening on. By default it is localhost.
/// </summary>
public string TargetHostAddress { get; set; } = "localhost";

/// <summary>
/// Transport that is being used (e.g. http, http2, http3 etc).
/// </summary>
Expand Down
21 changes: 12 additions & 9 deletions src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,8 @@ private static IResourceBuilder<ProjectResource> WithProjectDefaults(this IResou
e.Port = endpoint.BindingAddress.Port;
}
e.UriScheme = endpoint.BindingAddress.Scheme;
e.TargetHostAddress = endpoint.BindingAddress.Host;

adjustTransport(e, endpoint.Protocols);
// Keep track of the host separately since EndpointAnnotation doesn't have a host property
builder.Resource.KestrelEndpointAnnotationHosts[e] = endpoint.BindingAddress.Host;
Expand Down Expand Up @@ -405,27 +407,28 @@ private static IResourceBuilder<ProjectResource> WithProjectDefaults(this IResou
Dictionary<string, int> endpointCountByScheme = [];
foreach (var url in urlsFromApplicationUrl)
{
var uri = new Uri(url);
var bindingAddress = BindingAddress.Parse(url);

// Keep track of how many endpoints we have for each scheme
endpointCountByScheme.TryGetValue(uri.Scheme, out var count);
endpointCountByScheme[uri.Scheme] = count + 1;
endpointCountByScheme.TryGetValue(bindingAddress.Scheme, out var count);
endpointCountByScheme[bindingAddress.Scheme] = count + 1;

// If we have multiple for the same scheme, we differentiate them by appending a number.
// We only do this starting with the second endpoint, so that the first stays just http/https.
// This allows us to keep the same behavior as "dotnet run".
// Also, note that we only do this in Run mode, as in Publish mode those extra endpoints
// with generic names would not be easily usable.
var endpointName = uri.Scheme;
if (endpointCountByScheme[uri.Scheme] > 1)
var endpointName = bindingAddress.Scheme;
if (endpointCountByScheme[bindingAddress.Scheme] > 1)
{
endpointName += endpointCountByScheme[uri.Scheme];
endpointName += endpointCountByScheme[bindingAddress.Scheme];
}

builder.WithEndpoint(endpointName, e =>
{
e.Port = uri.Port;
e.UriScheme = uri.Scheme;
e.Port = bindingAddress.Port;
e.TargetHostAddress = bindingAddress.Host;
e.UriScheme = bindingAddress.Scheme;
e.FromLaunchProfile = true;
adjustTransport(e);
},
Expand Down Expand Up @@ -640,7 +643,7 @@ private static void SetAspNetCoreUrls(this IResourceBuilder<ProjectResource> bui
processedHttpsPort = true;
}

aspnetCoreUrls.Append($"{e.Property(EndpointProperty.Scheme)}://localhost:{e.Property(EndpointProperty.TargetPort)}");
aspnetCoreUrls.Append($"{e.Property(EndpointProperty.Scheme)}://{e.EndpointAnnotation.TargetHostAddress}:{e.Property(EndpointProperty.TargetPort)}");
first = false;
}

Expand Down
2 changes: 2 additions & 0 deletions src/Aspire.Hosting/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Aspire.Hosting.ApplicationModel.ConnectionStringReference.ConnectionName.get ->
Aspire.Hosting.ApplicationModel.ConnectionStringReference.ConnectionName.set -> void
Aspire.Hosting.ApplicationModel.CustomResourceSnapshot.HealthStatus.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?
Aspire.Hosting.ApplicationModel.CustomResourceSnapshot.HealthStatus.init -> void
Aspire.Hosting.ApplicationModel.EndpointAnnotation.TargetHostAddress.get -> string!
Aspire.Hosting.ApplicationModel.EndpointAnnotation.TargetHostAddress.set -> void
Aspire.Hosting.ApplicationModel.HealthCheckAnnotation
Aspire.Hosting.ApplicationModel.HealthCheckAnnotation.HealthCheckAnnotation(string! key) -> void
Aspire.Hosting.ApplicationModel.HealthCheckAnnotation.Key.get -> string!
Expand Down
49 changes: 49 additions & 0 deletions tests/Aspire.Hosting.Tests/ProjectResourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,34 @@ public async Task AddProjectWithArgs()
arg => Assert.Equal("http://localhost:1234", arg));
}

[Fact]
public async Task AddProjectWithWildcardUrlInLaunchSettings()
{
var appBuilder = CreateBuilder(operation: DistributedApplicationOperation.Run);

appBuilder.AddProject<TestProjectWithWildcardUrlInLaunchSettings>("projectName")
.WithEndpoint("http", e =>
{
e.AllocatedEndpoint = new(e, "localhost", e.Port!.Value, targetPortExpression: "p0");
})
.WithEndpoint("https", e =>
{
e.AllocatedEndpoint = new(e, "localhost", e.Port!.Value, targetPortExpression: "p1");
});

using var app = appBuilder.Build();

var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();
var projectResources = appModel.GetProjectResources();

var resource = Assert.Single(projectResources);

var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance);

Assert.Equal("http://*:p0;https://*:p1", config["ASPNETCORE_URLS"]);
Assert.Equal("5033", config["ASPNETCORE_HTTPS_PORT"]);
}

internal static IDistributedApplicationBuilder CreateBuilder(string[]? args = null, DistributedApplicationOperation operation = DistributedApplicationOperation.Publish)
{
var resolvedArgs = new List<string>();
Expand Down Expand Up @@ -668,4 +696,25 @@ public TestProjectWithManyAppUrlsInLaunchSettings()
};
}
}

private sealed class TestProjectWithWildcardUrlInLaunchSettings : BaseProjectWithProfileAndConfig
{
public TestProjectWithWildcardUrlInLaunchSettings()
{
Profiles = new()
{
["https"] = new()
{
CommandName = "Project",
CommandLineArgs = "arg1 arg2",
LaunchBrowser = true,
ApplicationUrl = "http://*:5031;https://*:5033",
EnvironmentVariables = new()
{
["ASPNETCORE_ENVIRONMENT"] = "Development"
}
}
};
}
}
}