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

[wasm] Add a new wasm webserver command #1001

Merged
merged 2 commits into from
Mar 1, 2023
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
Original file line number Diff line number Diff line change
@@ -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)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ internal interface IWebServerArguments
WebServerUseHttpsArguments WebServerUseHttps { get; }
WebServerUseCorsArguments WebServerUseCors { get; }
WebServerUseCrossOriginPolicyArguments WebServerUseCrossOriginPolicy { get; }
WebServerUseDefaultFilesArguments WebServerUseDefaultFiles { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Argument> GetArguments() => new Argument[]
{
Expand Down Expand Up @@ -63,6 +64,7 @@ internal class WasmTestBrowserCommandArguments : XHarnessCommandArguments, IWebS
WebServerUseHttps,
WebServerUseCors,
WebServerUseCrossOriginPolicy,
WebServerUseDefaultFiles,
};

public override void Validate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Argument> GetArguments() => new Argument[]
{
Expand All @@ -49,5 +50,6 @@ internal class WasmTestCommandArguments : XHarnessCommandArguments, IWebServerAr
WebServerUseHttps,
WebServerUseCors,
WebServerUseCrossOriginPolicy,
WebServerUseDefaultFiles,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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 WebServerUseDefaultFilesArguments WebServerUseDefaultFiles { get; } = new();

public TimeoutArgument Timeout { get; } = new(TimeSpan.FromMinutes(15));

protected override IEnumerable<Argument> GetArguments() => new Argument[]
{
AppPackagePath,
Timeout,
WebServerMiddlewarePathsAndTypes,
WebServerHttpEnvironmentVariables,
WebServerHttpsEnvironmentVariables,
WebServerUseHttps,
WebServerUseCors,
WebServerUseCrossOriginPolicy,
WebServerUseDefaultFiles,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -58,11 +59,13 @@ public async Task<ExitCode> RunTestsWithWebDriver(DriverService driverService, I
{
var consolePumpTcs = new TaskCompletionSource<bool>();
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,7 @@ protected override async Task<ExitCode> InvokeInternal(ILogger logger)
{
serverURLs = await WebServer.Start(
Arguments,
null,
logger,
null,
cts.Token);
cts.CancelAfter(Arguments.Timeout);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ public WasmCommandSet() : base("wasm")
{
Add(new WasmTestCommand());
Add(new WasmTestBrowserCommand());
Add(new WebServerCommand());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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.CLI.Commands;
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<WebServerCommandArguments>
{
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<ExitCode> InvokeInternal(ILogger logger)
{
var cts = new CancellationTokenSource();
var webServerOptions = WebServer.TestWebServerOptions.FromArguments(Arguments);
webServerOptions.ContentRoot = Arguments.AppPackagePath;
ServerURLs serverURLs = await WebServer.Start(
webServerOptions,
logger,
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;
}
}
}
75 changes: 56 additions & 19 deletions src/Microsoft.DotNet.XHarness.CLI/Commands/WebServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@ namespace Microsoft.DotNet.XHarness.CLI.Commands;

public class WebServer
{
internal static async Task<ServerURLs> Start(IWebServerArguments arguments, string? contentRoot, ILogger logger, Func<WebSocket, Task>? onConsoleConnected, CancellationToken token)
internal static Task<ServerURLs> Start(IWebServerArguments arguments, ILogger logger, CancellationToken token, Func<WebSocket, Task>? onConsoleConnected = null)
{
var urls = arguments.WebServerUseHttps
var options = TestWebServerOptions.FromArguments(arguments);
options.OnConsoleConnected = onConsoleConnected;
return Start(options, logger, token);
}

internal static async Task<ServerURLs> 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" };

Expand All @@ -41,7 +48,7 @@ internal static async Task<ServerURLs> Start(IWebServerArguments arguments, stri
})
.ConfigureServices((ctx, services) =>
{
if (arguments.WebServerUseCors)
if (webServerOptions.UseCors)
{
services.AddCors(o => o.AddPolicy("AnyCors", builder =>
{
Expand All @@ -56,20 +63,14 @@ internal static async Task<ServerURLs> Start(IWebServerArguments arguments, stri
services.Configure<TestWebServerOptions>(ctx.Configuration);
services.Configure<TestWebServerOptions>(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();
Expand All @@ -84,7 +85,7 @@ internal static async Task<ServerURLs> Start(IWebServerArguments arguments, stri
.Select(uri => $"{uri.Host}:{uri.Port}")
.FirstOrDefault();

var ipAddressSecure = arguments.WebServerUseHttps
var ipAddressSecure = webServerOptions.UseHttps
? host.ServerFeatures
.Get<IServerAddressesFeature>()?
.Addresses
Expand All @@ -94,7 +95,7 @@ internal static async Task<ServerURLs> 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");
}
Expand Down Expand Up @@ -127,7 +128,7 @@ public void Configure(IApplicationBuilder app, IOptionsMonitor<TestWebServerOpti

var options = optionsAccessor.CurrentValue;

if (options.WebServerUseCrossOriginPolicy)
if (options.UseCrossOriginPolicy)
{
app.Use((context, next) =>
{
Expand All @@ -137,14 +138,22 @@ public void Configure(IApplicationBuilder app, IOptionsMonitor<TestWebServerOpti
});
}

if (options.UseDefaultFiles)
{
app.UseDefaultFiles(new DefaultFilesOptions
{
FileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath)
});
}

app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath),
ContentTypeProvider = provider,
ServeUnknownFileTypes = true
});

if (options.WebServerUseCors)
if (options.UseCors)
{
app.UseCors("AnyCors");
}
Expand Down Expand Up @@ -175,12 +184,40 @@ public void Configure(IApplicationBuilder app, IOptionsMonitor<TestWebServerOpti
}
}

private class TestWebServerOptions
internal class TestWebServerOptions
{
public Func<WebSocket, Task>? OnConsoleConnected { get; set; }
public IList<Type> EchoServerMiddlewares { get; set; } = new List<Type>();
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;
}
}
}

Expand Down