private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(20); // This is a development-time only feature, so a very long timeout is fine public static void Attach( ISpaBuilder spaBuilder, string npmScriptName) { var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { throw new ArgumentException("Cannot be null or empty", nameof(sourcePath)); } if (string.IsNullOrEmpty(npmScriptName)) { throw new ArgumentException("Cannot be null or empty", nameof(npmScriptName)); } // Start Vue application and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); var portTask = StartVueDevServerAsync(sourcePath, npmScriptName, logger); // Everything we proxy is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote // requests that go directly to the create-react-app server) // - given that, there's no reason to use https, and we couldn't even if we // wanted to, because in general the create-react-app server has no certificate var targetUriTask = portTask.ContinueWith( task => new UriBuilder("http", "localhost", task.Result).Uri); SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, () => { // On each request, we create a separate startup task with its own timeout. That way, even if // the first request times out, subsequent requests could still work. var timeout = spaBuilder.Options.StartupTimeout; return(targetUriTask.WithTimeout(timeout, $"The vue development server did not start listening for requests " + $"within the timeout period of {timeout.Seconds} seconds. " + $"Check the log output for error information.")); }); }
private static TimeSpan RegexMatchTimeout = TimeSpan.FromMinutes(5); // This is a development-time only feature, so a very long timeout is fine public static void Attach( ISpaBuilder spaBuilder, string scriptName, int port = 8080, bool https = false, ScriptRunnerType runner = ScriptRunnerType.Npm, string regex = DefaultRegex, bool forceKill = false) { var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { throw new ArgumentException("Cannot be null or empty", nameof(sourcePath)); } if (string.IsNullOrEmpty(scriptName)) { throw new ArgumentException("Cannot be null or empty", nameof(scriptName)); } // Start vue-cli and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); var portTask = StartVueCliServerAsync(sourcePath, scriptName, logger, port, runner, regex, forceKill); // Everything we proxy is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote // requests that go directly to the vue-cli server) var targetUriTask = portTask.ContinueWith( task => new UriBuilder(https ? "https" : "http", "localhost", task.Result).Uri); SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, () => { // On each request, we create a separate startup task with its own timeout. That way, even if // the first request times out, subsequent requests could still work. var timeout = spaBuilder.Options.StartupTimeout; return(targetUriTask.WithTimeout(timeout, $"The vue-cli server did not start listening for requests " + $"within the timeout period of {timeout.Seconds} seconds. " + $"Check the log output for error information.")); }); }
private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine public static void Attach( ISpaBuilder spaBuilder, string scriptName) { var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand; var sourcePath = spaBuilder.Options.SourcePath; var devServerPort = spaBuilder.Options.DevServerPort; if (string.IsNullOrEmpty(sourcePath)) { throw new ArgumentException("Property 'SourcePath' cannot be null or empty", nameof(spaBuilder)); } if (string.IsNullOrEmpty(scriptName)) { throw new ArgumentException("Cannot be null or empty", nameof(scriptName)); } // Start Angular CLI and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var applicationStoppingToken = appBuilder.ApplicationServices.GetRequiredService <IHostApplicationLifetime>().ApplicationStopping; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); var diagnosticSource = appBuilder.ApplicationServices.GetRequiredService <DiagnosticSource>(); var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, scriptName, pkgManagerCommand, devServerPort, logger, diagnosticSource, applicationStoppingToken); SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, () => { // On each request, we create a separate startup task with its own timeout. That way, even if // the first request times out, subsequent requests could still work. var timeout = spaBuilder.Options.StartupTimeout; return(angularCliServerInfoTask.WithTimeout(timeout, $"The Angular CLI process did not start listening for requests " + $"within the timeout period of {timeout.TotalSeconds} seconds. " + $"Check the log output for error information.")); }); }
private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine public static void Attach( ISpaBuilder spaBuilder, string npmScriptName) { var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { #pragma warning disable CA1303 // Do not pass literals as localized parameters throw new InvalidOperationException("Must set ISpaBuilder.Options.SourcePath before calling this method."); #pragma warning restore CA1303 // Do not pass literals as localized parameters } if (string.IsNullOrEmpty(npmScriptName)) { #pragma warning disable CA1303 // Do not pass literals as localized parameters throw new ArgumentException("Cannot be null or empty", nameof(npmScriptName)); #pragma warning restore CA1303 // Do not pass literals as localized parameters } // Start webpack-dev-server and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); Task <int> portTask = null; Task <Uri> targetUriTask = null; appBuilder.Use(async(context, next) => { if (portTask == null) { // Get port number of webapp first before we start webpack-dev-server, so that // webpack can use the port number of the webapp for the websocket configuration. var request = context.Request; int socketPortNumber = request.Host.Port.Value; portTask = StartWebpackDevServerAsync(sourcePath, npmScriptName, logger, socketPortNumber); // Everything we **proxy** is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote // requests that go directly to the webpack-dev-server server) // - given that, there's no reason to use https, and we couldn't even if we // wanted to, because in general the webpack-dev-server server has no certificate #pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler targetUriTask = portTask.ContinueWith(task => { // "https" here doesn't work as the webpack-dev-server expects request via "http" Uri uri = new UriBuilder("http", "localhost", task.Result).Uri; return(uri); }); #pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler } #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task await next.Invoke(); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task }); SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, () => { // On each request, we create a separate startup task with its own timeout. That way, even if // the first request times out, subsequent requests could still work. var timeout = spaBuilder.Options.StartupTimeout; return(targetUriTask.WithTimeout(timeout, $"The webpack-dev-server did not start listening for requests " + $"within the timeout period of {timeout.Seconds} seconds. " + $"Check the log output for error information.")); }); }
private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine public static void Attach( ISpaBuilder spaBuilder, string npmScriptName) { var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { #pragma warning disable CA1303 // Do not pass literals as localized parameters throw new InvalidOperationException("Must set ISpaBuilder.Options.SourcePath before calling this method."); #pragma warning restore CA1303 // Do not pass literals as localized parameters } if (string.IsNullOrEmpty(npmScriptName)) { #pragma warning disable CA1303 // Do not pass literals as localized parameters throw new ArgumentException("Cannot be null or empty", nameof(npmScriptName)); #pragma warning restore CA1303 // Do not pass literals as localized parameters } var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); // Clear files in distribution directory aka spaStaticFileOptions.RootPath: var spaStaticFileProvider = spaBuilder.ApplicationBuilder.ApplicationServices.GetService(typeof(ISpaStaticFileProvider)) as ISpaStaticFileProvider; ClearSpaRootPath(spaStaticFileProvider.FileProvider, logger); // Start webpack-dev-server once the application has started var hostApplicationLifetime = spaBuilder.ApplicationBuilder.ApplicationServices.GetService(typeof(IHostApplicationLifetime)) as IHostApplicationLifetime; Task <int> portTask = null; Task <Uri> targetUriTask = null; var socketPortNumber = 0; hostApplicationLifetime.ApplicationStarted.Register(() => { // When this is called the request pipeline configuration has completed. Only now the addresses // at which requests are served are available. We use any address/port combination but use HTTPs // if is configured for the project. var addressFeature = spaBuilder.ApplicationBuilder.ServerFeatures.Get <IServerAddressesFeature>(); foreach (var addr in addressFeature.Addresses) { var uri = new Uri(addr); socketPortNumber = uri.Port; if (uri.Scheme == "https") { break; } } portTask = StartWebpackDevServerAsync(sourcePath, npmScriptName, logger, socketPortNumber); // Everything we **proxy** is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote // requests that go directly to the webpack-dev-server) // - given that, there's no reason to use https when forwarding the request to the webpack // dev server, and we couldn't even if we wanted to, because in general the webpack-dev-server // has no certificate #pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler targetUriTask = portTask.ContinueWith(task => { // "https" here doesn't work as the webpack-dev-server expects request via "http" Uri uri = new UriBuilder("http", "localhost", task.Result).Uri; return(uri); }); #pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler }); // Configure proxying. By the time a request comes in, the webpack dev server will be running, // so it is fine to configure proxying before the webpack-dev-server has been started. SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, () => { // On each request, we create a separate startup task with its own timeout. That way, even if // the first request times out, subsequent requests could still work. var timeout = spaBuilder.Options.StartupTimeout; return(targetUriTask.WithTimeout(timeout, $"The webpack-dev-server did not start listening for requests " + $"within the timeout period of {timeout.Seconds} seconds. " + $"Check the log output for error information.")); }); }