From 19507513cbb7cd4762600e470a514724055c1b02 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 28 Feb 2023 18:18:45 -0500 Subject: [PATCH 1/2] [wasm] Add a `wasm webserver` command - ends after a timeout, or can be killed. --- .../WASM/WebServerCommandArguments.cs | 34 ++++++++++++ .../Commands/WASM/WasmCommandSet.cs | 1 + .../Commands/WASM/WebServerCommand.cs | 55 +++++++++++++++++++ .../Commands/WebServer.cs | 4 ++ 4 files changed, 94 insertions(+) create mode 100644 src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WebServerCommandArguments.cs create mode 100644 src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WebServerCommand.cs diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WebServerCommandArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WebServerCommandArguments.cs new file mode 100644 index 000000000..397db601d --- /dev/null +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WebServerCommandArguments.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; + +internal class WebServerCommandArguments : XHarnessCommandArguments, IWebServerArguments +{ + public AppPathArgument AppPackagePath { get; } = new(); + + public WebServerMiddlewareArgument WebServerMiddlewarePathsAndTypes { get; } = new(); + public WebServerHttpEnvironmentVariables WebServerHttpEnvironmentVariables { get; } = new(); + public WebServerHttpsEnvironmentVariables WebServerHttpsEnvironmentVariables { get; } = new(); + public WebServerUseHttpsArguments WebServerUseHttps { get; } = new(); + public WebServerUseCorsArguments WebServerUseCors { get; } = new(); + public WebServerUseCrossOriginPolicyArguments WebServerUseCrossOriginPolicy { get; } = new(); + + public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); + + protected override IEnumerable GetArguments() => new Argument[] + { + AppPackagePath, + Timeout, + WebServerMiddlewarePathsAndTypes, + WebServerHttpEnvironmentVariables, + WebServerHttpsEnvironmentVariables, + WebServerUseHttps, + WebServerUseCors, + WebServerUseCrossOriginPolicy, + }; +} diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WasmCommandSet.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WasmCommandSet.cs index d06a7d090..c32bc849d 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WasmCommandSet.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WasmCommandSet.cs @@ -15,5 +15,6 @@ public WasmCommandSet() : base("wasm") { Add(new WasmTestCommand()); Add(new WasmTestBrowserCommand()); + Add(new WebServerCommand()); } } diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WebServerCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WebServerCommand.cs new file mode 100644 index 000000000..a8a9650cf --- /dev/null +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WebServerCommand.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; +using Microsoft.DotNet.XHarness.Common; +using Microsoft.DotNet.XHarness.Common.CLI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.DotNet.XHarness.CLI.Commands.Wasm; + +internal class WebServerCommand : XHarnessCommand +{ + private const string CommandHelp = "Starts a webserver"; + + protected override string CommandUsage { get; } = "wasm webserver [OPTIONS]"; + protected override string CommandDescription { get; } = CommandHelp; + + protected override WebServerCommandArguments Arguments { get; } = new(); + + public WebServerCommand() + : base(TargetPlatform.WASM, "webserver", allowsExtraArgs: true, new ServiceCollection(), CommandHelp) + { + } + + protected override async Task InvokeInternal(ILogger logger) + { + var cts = new CancellationTokenSource(); + ServerURLs serverURLs = await WebServer.Start( + Arguments, + Arguments.AppPackagePath, + logger, + null, + cts.Token); + + logger.LogInformation($"Now listening on: http://{serverURLs.Http}"); + if (!string.IsNullOrEmpty(serverURLs.Https)) + logger.LogInformation($"Now listening on: https://{serverURLs.Https}"); + + await Task.Delay(Arguments.Timeout, cts.Token); + if (cts.Token.IsCancellationRequested) + { + logger.LogError("Token cancelled for unknown reasons, exiting."); + return ExitCode.GENERAL_FAILURE; + } + else + { + logger.LogInformation($"Stopping the webserver after the timeout of {Arguments.Timeout}"); + return ExitCode.SUCCESS; + } + } +} diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs index 1bef6b408..7bb8ddb87 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs @@ -137,6 +137,10 @@ public void Configure(IApplicationBuilder app, IOptionsMonitor Date: Wed, 1 Mar 2023 00:29:13 +0000 Subject: [PATCH 2/2] Simplify WebServer api - Convert all the command line arguments to TestWebServerOptions. This also helps to avoid having to add new method arguments to WebServer to add more options. - And add a --web-server-use-default-files option that serves files like `index.html` and `default.html` by default. --- .../Arguments/WebServerUseDefaultFiles.cs | 13 ++++ .../WASM/IWebServerArguments.cs | 1 + .../WASM/WasmTestBrowserCommandArguments.cs | 2 + .../WASM/WasmTestCommandArguments.cs | 2 + .../WASM/WebServerCommandArguments.cs | 18 +++-- .../WASM/Browser/WasmBrowserTestRunner.cs | 9 ++- .../Commands/WASM/JS/WasmTestCommand.cs | 2 - .../Commands/WASM/WebServerCommand.cs | 7 +- .../Commands/WebServer.cs | 77 +++++++++++++------ 9 files changed, 93 insertions(+), 38 deletions(-) create mode 100644 src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerUseDefaultFiles.cs diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerUseDefaultFiles.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerUseDefaultFiles.cs new file mode 100644 index 000000000..1816088a9 --- /dev/null +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Arguments/WebServerUseDefaultFiles.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.DotNet.XHarness.CLI.CommandArguments; + +internal class WebServerUseDefaultFilesArguments : SwitchArgument +{ + public WebServerUseDefaultFilesArguments() + : base("web-server-use-default-files", "Enable default files like index.html", false) + { + } +} diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/IWebServerArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/IWebServerArguments.cs index b1efbbbe1..461983cb1 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/IWebServerArguments.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/IWebServerArguments.cs @@ -12,4 +12,5 @@ internal interface IWebServerArguments WebServerUseHttpsArguments WebServerUseHttps { get; } WebServerUseCorsArguments WebServerUseCors { get; } WebServerUseCrossOriginPolicyArguments WebServerUseCrossOriginPolicy { get; } + WebServerUseDefaultFilesArguments WebServerUseDefaultFiles { get; } } diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs index 72aad1ef5..6a438113b 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestBrowserCommandArguments.cs @@ -36,6 +36,7 @@ internal class WasmTestBrowserCommandArguments : XHarnessCommandArguments, IWebS public WebServerUseHttpsArguments WebServerUseHttps { get; } = new(); public WebServerUseCorsArguments WebServerUseCors { get; } = new(); public WebServerUseCrossOriginPolicyArguments WebServerUseCrossOriginPolicy { get; } = new(); + public WebServerUseDefaultFilesArguments WebServerUseDefaultFiles { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { @@ -63,6 +64,7 @@ internal class WasmTestBrowserCommandArguments : XHarnessCommandArguments, IWebS WebServerUseHttps, WebServerUseCors, WebServerUseCrossOriginPolicy, + WebServerUseDefaultFiles, }; public override void Validate() diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestCommandArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestCommandArguments.cs index b14e96b78..7108f697d 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestCommandArguments.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WasmTestCommandArguments.cs @@ -28,6 +28,7 @@ internal class WasmTestCommandArguments : XHarnessCommandArguments, IWebServerAr public WebServerUseHttpsArguments WebServerUseHttps { get; } = new(); public WebServerUseCorsArguments WebServerUseCors { get; } = new(); public WebServerUseCrossOriginPolicyArguments WebServerUseCrossOriginPolicy { get; } = new(); + public WebServerUseDefaultFilesArguments WebServerUseDefaultFiles { get; } = new(); protected override IEnumerable GetArguments() => new Argument[] { @@ -49,5 +50,6 @@ internal class WasmTestCommandArguments : XHarnessCommandArguments, IWebServerAr WebServerUseHttps, WebServerUseCors, WebServerUseCrossOriginPolicy, + WebServerUseDefaultFiles, }; } diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WebServerCommandArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WebServerCommandArguments.cs index 397db601d..d405d8fdc 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WebServerCommandArguments.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/WASM/WebServerCommandArguments.cs @@ -17,18 +17,20 @@ internal class WebServerCommandArguments : XHarnessCommandArguments, IWebServerA public WebServerUseHttpsArguments WebServerUseHttps { get; } = new(); public WebServerUseCorsArguments WebServerUseCors { get; } = new(); public WebServerUseCrossOriginPolicyArguments WebServerUseCrossOriginPolicy { get; } = new(); + public WebServerUseDefaultFilesArguments WebServerUseDefaultFiles { get; } = new(); public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15)); protected override IEnumerable GetArguments() => new Argument[] { - AppPackagePath, - Timeout, - WebServerMiddlewarePathsAndTypes, - WebServerHttpEnvironmentVariables, - WebServerHttpsEnvironmentVariables, - WebServerUseHttps, - WebServerUseCors, - WebServerUseCrossOriginPolicy, + AppPackagePath, + Timeout, + WebServerMiddlewarePathsAndTypes, + WebServerHttpEnvironmentVariables, + WebServerHttpsEnvironmentVariables, + WebServerUseHttps, + WebServerUseCors, + WebServerUseCrossOriginPolicy, + WebServerUseDefaultFiles, }; } diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs index 514a28b30..b5449eb14 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/Browser/WasmBrowserTestRunner.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using System.Web; +using Microsoft.DotNet.XHarness.CLI.Commands; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.Logging; @@ -58,11 +59,13 @@ public async Task RunTestsWithWebDriver(DriverService driverService, I { var consolePumpTcs = new TaskCompletionSource(); var logProcessorTask = Task.Run(() => _messagesProcessor.RunAsync(cts.Token)); + + var webServerOptions = WebServer.TestWebServerOptions.FromArguments(_arguments); + webServerOptions.ContentRoot = _arguments.AppPackagePath; + webServerOptions.OnConsoleConnected = socket => RunConsoleMessagesPump(socket, cts.Token); ServerURLs serverURLs = await WebServer.Start( - _arguments, - _arguments.AppPackagePath, + webServerOptions, _logger, - socket => RunConsoleMessagesPump(socket, cts.Token), cts.Token); string testUrl = BuildUrl(serverURLs); diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs index 8e935746e..90eaa88c6 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/JS/WasmTestCommand.cs @@ -73,9 +73,7 @@ protected override async Task InvokeInternal(ILogger logger) { serverURLs = await WebServer.Start( Arguments, - null, logger, - null, cts.Token); cts.CancelAfter(Arguments.Timeout); } diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WebServerCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WebServerCommand.cs index a8a9650cf..bd198525e 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WebServerCommand.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WASM/WebServerCommand.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XHarness.CLI.CommandArguments.Wasm; +using Microsoft.DotNet.XHarness.CLI.Commands; using Microsoft.DotNet.XHarness.Common; using Microsoft.DotNet.XHarness.Common.CLI; using Microsoft.Extensions.DependencyInjection; @@ -29,11 +30,11 @@ public WebServerCommand() protected override async Task InvokeInternal(ILogger logger) { var cts = new CancellationTokenSource(); + var webServerOptions = WebServer.TestWebServerOptions.FromArguments(Arguments); + webServerOptions.ContentRoot = Arguments.AppPackagePath; ServerURLs serverURLs = await WebServer.Start( - Arguments, - Arguments.AppPackagePath, + webServerOptions, logger, - null, cts.Token); logger.LogInformation($"Now listening on: http://{serverURLs.Http}"); diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs index 7bb8ddb87..3a42365f9 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs @@ -26,9 +26,16 @@ namespace Microsoft.DotNet.XHarness.CLI.Commands; public class WebServer { - internal static async Task Start(IWebServerArguments arguments, string? contentRoot, ILogger logger, Func? onConsoleConnected, CancellationToken token) + internal static Task Start(IWebServerArguments arguments, ILogger logger, CancellationToken token, Func? onConsoleConnected = null) { - var urls = arguments.WebServerUseHttps + var options = TestWebServerOptions.FromArguments(arguments); + options.OnConsoleConnected = onConsoleConnected; + return Start(options, logger, token); + } + + internal static async Task Start(TestWebServerOptions webServerOptions, ILogger logger, CancellationToken token) + { + var urls = webServerOptions.UseHttps ? new string[] { "http://127.0.0.1:0", "https://127.0.0.1:0" } : new string[] { "http://127.0.0.1:0" }; @@ -41,7 +48,7 @@ internal static async Task Start(IWebServerArguments arguments, stri }) .ConfigureServices((ctx, services) => { - if (arguments.WebServerUseCors) + if (webServerOptions.UseCors) { services.AddCors(o => o.AddPolicy("AnyCors", builder => { @@ -56,20 +63,14 @@ internal static async Task Start(IWebServerArguments arguments, stri services.Configure(ctx.Configuration); services.Configure(options => { - options.WebServerUseCors = arguments.WebServerUseCors; - options.WebServerUseCrossOriginPolicy = arguments.WebServerUseCrossOriginPolicy; - options.OnConsoleConnected = onConsoleConnected; - foreach (var middlewareType in arguments.WebServerMiddlewarePathsAndTypes.GetLoadedTypes()) - { - options.EchoServerMiddlewares.Add(middlewareType); - } + webServerOptions.CopyTo(options); }); }) .UseUrls(urls); - if (contentRoot != null) + if (webServerOptions.ContentRoot != null) { - builder.UseContentRoot(contentRoot); + builder.UseContentRoot(webServerOptions.ContentRoot); } var host = builder.Build(); @@ -84,7 +85,7 @@ internal static async Task Start(IWebServerArguments arguments, stri .Select(uri => $"{uri.Host}:{uri.Port}") .FirstOrDefault(); - var ipAddressSecure = arguments.WebServerUseHttps + var ipAddressSecure = webServerOptions.UseHttps ? host.ServerFeatures .Get()? .Addresses @@ -94,7 +95,7 @@ internal static async Task Start(IWebServerArguments arguments, stri .FirstOrDefault() : null; - if (ipAddress == null || (arguments.WebServerUseHttps && ipAddressSecure == null)) + if (ipAddress == null || (webServerOptions.UseHttps && ipAddressSecure == null)) { throw new InvalidOperationException("Failed to determine web server's IP address or port"); } @@ -127,7 +128,7 @@ public void Configure(IApplicationBuilder app, IOptionsMonitor { @@ -137,10 +138,14 @@ public void Configure(IApplicationBuilder app, IOptionsMonitor? OnConsoleConnected { get; set; } public IList EchoServerMiddlewares { get; set; } = new List(); - public bool WebServerUseCors { get; set; } - public bool WebServerUseCrossOriginPolicy { get; set; } + public bool UseCors { get; set; } + public bool UseHttps { get; set; } + public bool UseCrossOriginPolicy { get; set; } + public bool UseDefaultFiles { get; set; } + public string? ContentRoot { get; set; } + + public void CopyTo(TestWebServerOptions otherOptions) + { + otherOptions.OnConsoleConnected = OnConsoleConnected; + otherOptions.EchoServerMiddlewares = EchoServerMiddlewares; + otherOptions.UseCors = UseCors; + otherOptions.UseHttps = UseHttps; + otherOptions.UseCrossOriginPolicy = UseCrossOriginPolicy; + otherOptions.UseDefaultFiles = UseDefaultFiles; + otherOptions.ContentRoot = ContentRoot; + } + + public static TestWebServerOptions FromArguments(IWebServerArguments arguments) + { + TestWebServerOptions options = new(); + options.UseCors = arguments.WebServerUseCors; + options.UseHttps = arguments.WebServerUseHttps; + options.UseCrossOriginPolicy = arguments.WebServerUseCrossOriginPolicy; + options.UseDefaultFiles = arguments.WebServerUseDefaultFiles; + foreach (var middlewareType in arguments.WebServerMiddlewarePathsAndTypes.GetLoadedTypes()) + { + options.EchoServerMiddlewares.Add(middlewareType); + } + return options; + } } }