diff --git a/src/mono/wasm/host/WebServer.cs b/src/mono/wasm/host/WebServer.cs index aed1948470334c..1f448edcb98eab 100644 --- a/src/mono/wasm/host/WebServer.cs +++ b/src/mono/wasm/host/WebServer.cs @@ -1,13 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -21,6 +17,7 @@ public class WebServer internal static async Task<(ServerURLs, IWebHost)> StartAsync(WebServerOptions options, ILogger logger, CancellationToken token) { string[] urls = options.Urls; + TaskCompletionSource realUrlsAvailableTcs = new(); IWebHostBuilder builder = new WebHostBuilder() .UseKestrel() @@ -43,6 +40,7 @@ public class WebServer } services.AddSingleton(logger); services.AddSingleton(Options.Create(options)); + services.AddSingleton(realUrlsAvailableTcs); services.AddRouting(); }) .UseUrls(urls); @@ -53,27 +51,11 @@ public class WebServer IWebHost? host = builder.Build(); await host.StartAsync(token); - ICollection? addresses = host.ServerFeatures - .Get()? - .Addresses; + if (token.CanBeCanceled) + token.Register(async () => await host.StopAsync()); - string? ipAddress = - addresses? - .Where(a => a.StartsWith("http:", StringComparison.InvariantCultureIgnoreCase)) - .Select(a => new Uri(a)) - .Select(uri => uri.ToString()) - .FirstOrDefault(); - - string? ipAddressSecure = - addresses? - .Where(a => a.StartsWith("https:", StringComparison.OrdinalIgnoreCase)) - .Select(a => new Uri(a)) - .Select(uri => uri.ToString()) - .FirstOrDefault(); - - return ipAddress == null || ipAddressSecure == null - ? throw new InvalidOperationException("Failed to determine web server's IP address or port") - : (new ServerURLs(ipAddress, ipAddressSecure), host); + ServerURLs serverUrls = await realUrlsAvailableTcs.Task; + return (serverUrls, host); } } diff --git a/src/mono/wasm/host/WebServerStartup.cs b/src/mono/wasm/host/WebServerStartup.cs index e09cdd61963fb2..933eeb6b6fc196 100644 --- a/src/mono/wasm/host/WebServerStartup.cs +++ b/src/mono/wasm/host/WebServerStartup.cs @@ -7,15 +7,19 @@ using System.IO; using System.Net; using System.Net.WebSockets; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Web; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.WebAssembly.Diagnostics; @@ -28,6 +32,8 @@ internal sealed class WebServerStartup private readonly IWebHostEnvironment _hostingEnvironment; private static readonly object LaunchLock = new object(); private static string LaunchedDebugProxyUrl = ""; + private ILogger? _logger; + public WebServerStartup(IWebHostEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment; public static int StartDebugProxy(string devToolsHost) @@ -52,8 +58,13 @@ public static int StartDebugProxy(string devToolsHost) return generateRandomPort; } - public void Configure(IApplicationBuilder app, IOptions optionsContainer) + public void Configure(IApplicationBuilder app, + IOptions optionsContainer, + TaskCompletionSource realUrlsAvailableTcs, + ILogger logger, + IHostApplicationLifetime applicationLifetime) { + _logger = logger; var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".wasm"] = "application/wasm"; provider.Mappings[".cjs"] = "text/javascript"; @@ -144,5 +155,41 @@ public void Configure(IApplicationBuilder app, IOptions option return Task.CompletedTask; }); }); + + applicationLifetime.ApplicationStarted.Register(() => + { + try + { + ICollection? addresses = app.ServerFeatures + .Get() + ?.Addresses; + + string? ipAddress = null; + string? ipAddressSecure = null; + if (addresses is not null) + { + ipAddress = GetHttpServerAddress(addresses, secure: false); + ipAddressSecure = GetHttpServerAddress(addresses, secure: true); + } + + if (ipAddress == null) + realUrlsAvailableTcs.SetException(new InvalidOperationException("Failed to determine web server's IP address or port")); + else + realUrlsAvailableTcs.SetResult(new ServerURLs(ipAddress, ipAddressSecure)); + } + catch (Exception ex) + { + _logger?.LogError($"Failed to get urls for the webserver: {ex}"); + realUrlsAvailableTcs.TrySetException(ex); + throw; + } + + static string? GetHttpServerAddress(ICollection addresses, bool secure) + => addresses? + .Where(a => a.StartsWith(secure ? "https:" : "http:", StringComparison.InvariantCultureIgnoreCase)) + .Select(a => new Uri(a)) + .Select(uri => uri.ToString()) + .FirstOrDefault(); + }); } }