Skip to content
Merged
5 changes: 5 additions & 0 deletions src/mono/wasm/build/WasmApp.targets
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@
<WasmDebugLevel Condition="('$(WasmDebugLevel)' == '' or '$(WasmDebugLevel)' == '0') and ('$(DebuggerSupport)' == 'true' or '$(Configuration)' == 'Debug')">-1</WasmDebugLevel>
</PropertyGroup>

<ItemGroup>
<!-- Allow running/debugging from VS -->
<ProjectCapability Include="DotNetCoreWeb"/>
</ItemGroup>

<PropertyGroup Label="Identify app bundle directory to run from">
<!-- Allow running from custom WasmAppDir -->
<_AppBundleDirForRunCommand Condition="'$(WasmAppDir)' != ''">$(WasmAppDir)</_AppBundleDirForRunCommand>
Expand Down
10 changes: 7 additions & 3 deletions src/mono/wasm/host/BrowserHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,13 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke
debugging: _args.CommonConfig.Debugging);
runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json"));

string[] urls = envVars.TryGetValue("ASPNETCORE_URLS", out string? aspnetUrls)
? aspnetUrls.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
: new string[] { $"http://127.0.0.1:{_args.CommonConfig.HostProperties.WebServerPort}", "https://127.0.0.1:0" };

(ServerURLs serverURLs, IWebHost host) = await StartWebServerAsync(_args.CommonConfig.AppPath,
_args.ForwardConsoleOutput ?? false,
_args.CommonConfig.HostProperties.WebServerPort,
urls,
token);

string[] fullUrls = BuildUrls(serverURLs, _args.AppArgs);
Expand All @@ -84,7 +88,7 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke
await host.WaitForShutdownAsync(token);
}

private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, int port, CancellationToken token)
private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, string[] urls, CancellationToken token)
{
WasmTestMessagesProcessor? logProcessor = null;
if (forwardConsole)
Expand All @@ -100,7 +104,7 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke
ContentRootPath: Path.GetFullPath(appPath),
WebServerUseCors: true,
WebServerUseCrossOriginPolicy: true,
Port: port
Urls: urls
);

(ServerURLs serverURLs, IWebHost host) = await WebServer.StartAsync(options, _logger, token);
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/host/WebServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class WebServer
{
internal static async Task<(ServerURLs, IWebHost)> StartAsync(WebServerOptions options, ILogger logger, CancellationToken token)
{
string[]? urls = new string[] { $"http://127.0.0.1:{options.Port}", "https://127.0.0.1:0" };
string[] urls = options.Urls;

IWebHostBuilder builder = new WebHostBuilder()
.UseKestrel()
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/host/WebServerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ internal sealed record WebServerOptions
string? ContentRootPath,
bool WebServerUseCors,
bool WebServerUseCrossOriginPolicy,
int Port,
string [] Urls,
string DefaultFileName = "index.html"
);
77 changes: 72 additions & 5 deletions src/mono/wasm/host/WebServerStartup.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
// 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.Diagnostics;
using System.IO;
using System.Net;
using System.Net.WebSockets;
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.Routing;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using Microsoft.WebAssembly.Diagnostics;

#nullable enable

Expand All @@ -16,9 +26,32 @@ namespace Microsoft.WebAssembly.AppHost;
internal sealed class WebServerStartup
{
private readonly IWebHostEnvironment _hostingEnvironment;

private static readonly object LaunchLock = new object();
private static string LaunchedDebugProxyUrl = "";
public WebServerStartup(IWebHostEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment;

public static int StartDebugProxy(string devToolsHost)
{
//we need to start another process, otherwise it will be running the BrowserDebugProxy in the same process that will be debugged, so pausing in a breakpoint
//on managed code will freeze because it will not be able to continue executing the BrowserDebugProxy to get the locals value
var executablePath = Path.Combine(System.AppContext.BaseDirectory, "BrowserDebugHost.dll");
var ownerPid = Environment.ProcessId;
var generateRandomPort = new Random().Next(5000, 5300);
var processStartInfo = new ProcessStartInfo
{
FileName = "dotnet" + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""),
Arguments = $"exec \"{executablePath}\" --OwnerPid {ownerPid} --DevToolsUrl {devToolsHost} --DevToolsProxyPort {generateRandomPort}",
UseShellExecute = false,
RedirectStandardOutput = true,
};
var debugProxyProcess = Process.Start(processStartInfo);
if (debugProxyProcess is null)
{
throw new InvalidOperationException("Unable to start debug proxy process.");
}
return generateRandomPort;
}

public void Configure(IApplicationBuilder app, IOptions<WebServerOptions> optionsContainer)
{
var provider = new FileExtensionContentTypeProvider();
Expand Down Expand Up @@ -73,9 +106,43 @@ public void Configure(IApplicationBuilder app, IOptions<WebServerOptions> option
});
}

// app.UseEndpoints(endpoints =>
// {
// endpoints.MapFallbackToFile(options.DefaultFileName);
// });
app.Map("/debug", app =>
{
app.Run(async (context) =>
{
//debug from VS
var queryParams = HttpUtility.ParseQueryString(context.Request.QueryString.Value!);
var browserParam = queryParams.Get("browser");
Uri? browserUrl = null;
var devToolsHost = "http://localhost:9222";
if (browserParam != null)
{
browserUrl = new Uri(browserParam);
devToolsHost = $"http://{browserUrl.Host}:{browserUrl.Port}";
}
lock (LaunchLock)
{
if (LaunchedDebugProxyUrl == "")
{
LaunchedDebugProxyUrl = $"http://localhost:{StartDebugProxy(devToolsHost)}";
}
}
var requestPath = context.Request.Path.ToString();
if (requestPath == string.Empty)
{
requestPath = "/";
}
context.Response.Redirect($"{LaunchedDebugProxyUrl}{browserUrl!.PathAndQuery}");
await Task.FromResult(0);
});
});
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", context =>
{
context.Response.Redirect("index.html", permanent: false);
return Task.CompletedTask;
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@
"type": "project"
},
"symbols": {
"kestrelHttpPortGenerated": {
"type": "generated",
"generator": "port",
"parameters": {
"low": 5000,
"high": 5300
},
"replaces": "5000"
},
"kestrelHttpsPortGenerated": {
"type": "generated",
"generator": "port",
"parameters": {
"low": 7000,
"high": 7300
},
"replaces": "5001"
},
"framework": {
"type": "parameter",
"description": "The target framework for the project.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"profiles": {
"browser.0": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}"
}
}
}