using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using System; using System.IO; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.SpaServices; using MisIngredientesVue.Middleware.Util; namespace MisIngredientesVue.Middleware { internal static class NuxtDevelopmentServerMiddleware { private const string LogCategoryName = "Microsoft.AspNetCore.SpaServices"; 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", 3000).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."); //}); SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, "http://localhost:8080"); } private static async Task StartVueDevServerAsync( string sourcePath, string npmScriptName, ILogger logger) { logger.LogInformation($"Starting Vue dev server..."); var npmScriptRunner = new NpmScriptRunner( sourcePath, npmScriptName, $"", null); npmScriptRunner.AttachToLogger(logger); Match startDevelopmentServerLine; using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr)) { try { // Although the Vue dev server may eventually tell us the URL it's listening on, // it doesn't do so until it's finished compiling, and even then only if there were // no compiler warnings. So instead of waiting for that, consider it ready as soon // as it starts listening for requests. //startDevelopmentServerLine = await npmScriptRunner.StdOut.WaitForMatch( // new Regex(" - Local: (http\\S+)", RegexOptions.None, RegexMatchTimeout)); startDevelopmentServerLine = await npmScriptRunner.StdOut.WaitForMatch( new Regex("Waiting for file changes", RegexOptions.None, RegexMatchTimeout)); } catch (EndOfStreamException ex) { throw new InvalidOperationException( $"The NPM script '{npmScriptName}' exited without indicating that the " + $"vue development server was listening for requests. The error output was: " + $"{stdErrReader.ReadAsString()}", ex); } } } } }