Esempio n. 1
0
    public static void Attach(ISpaBuilder spaBuilder)
    {
        if (spaBuilder == null)
        {
            throw new ArgumentNullException(nameof(spaBuilder));
        }

        var app     = spaBuilder.ApplicationBuilder;
        var options = spaBuilder.Options;

        // Rewrite all requests to the default page
        app.Use((context, next) =>
        {
            // If we have an Endpoint, then this is a deferred match - just noop.
            if (context.GetEndpoint() != null)
            {
                return(next(context));
            }

            context.Request.Path = options.DefaultPage;
            return(next(context));
        });

        // Serve it as a static file
        // Developers who need to host more than one SPA with distinct default pages can
        // override the file provider
        app.UseSpaStaticFilesInternal(
            options.DefaultPageStaticFileOptions ?? new StaticFileOptions(),
            allowFallbackOnServingWebRootFiles: true);

        // If the default file didn't get served as a static file (usually because it was not
        // present on disk), the SPA is definitely not going to work.
        app.Use((context, next) =>
        {
            // If we have an Endpoint, then this is a deferred match - just noop.
            if (context.GetEndpoint() != null)
            {
                return(next(context));
            }

            var message = "The SPA default page middleware could not return the default page " +
                          $"'{options.DefaultPage}' because it was not found, and no other middleware " +
                          "handled the request.\n";

            // Try to clarify the common scenario where someone runs an application in
            // Production environment without first publishing the whole application
            // or at least building the SPA.
            var hostEnvironment = (IWebHostEnvironment?)context.RequestServices.GetService(typeof(IWebHostEnvironment));
            if (hostEnvironment != null && hostEnvironment.IsProduction())
            {
                message += "Your application is running in Production mode, so make sure it has " +
                           "been published, or that you have built your SPA manually. Alternatively you " +
                           "may wish to switch to the Development environment.\n";
            }

            throw new InvalidOperationException(message);
        });
    }
        /// <summary>
        /// Adds middleware for server-side prerendering of a Single Page Application.
        /// </summary>
        /// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
        /// <param name="entryPoint">The path, relative to your application root, of the JavaScript file containing prerendering logic.</param>
        /// <param name="buildOnDemand">Optional. If specified, executes the supplied <see cref="ISpaPrerendererBuilder"/> before looking for the <paramref name="entryPoint"/> file. This is only intended to be used during development.</param>
        public static void UsePrerendering(
            this ISpaBuilder spaBuilder,
            string entryPoint,
            ISpaPrerendererBuilder buildOnDemand = null)
        {
            if (string.IsNullOrEmpty(entryPoint))
            {
                throw new ArgumentException("Cannot be null or empty", nameof(entryPoint));
            }

            // We only want to start one build-on-demand task, but it can't commence until
            // a request comes in (because we need to wait for all middleware to be configured)
            var lazyBuildOnDemandTask = new Lazy <Task>(() => buildOnDemand?.Build(spaBuilder));

            var appBuilder  = spaBuilder.AppBuilder;
            var prerenderer = GetPrerenderer(appBuilder.ApplicationServices);

            // Add the actual middleware that intercepts requests for the SPA default file
            // and invokes the prerendering code
            appBuilder.Use(async(context, next) =>
            {
                // Don't interfere with requests that aren't meant to render the SPA default file
                if (!context.Items.ContainsKey(SpaExtensions.IsSpaFallbackRequestTag))
                {
                    await next();
                    return;
                }

                // If we're building on demand, do that first
                var buildOnDemandTask = lazyBuildOnDemandTask.Value;
                if (buildOnDemandTask != null && !buildOnDemandTask.IsCompleted)
                {
                    await buildOnDemandTask;
                }

                // If we're waiting for other SPA initialization tasks, do that first.
                await spaBuilder.StartupTasks;

                // As a workaround for @angular/cli not emitting the index.html in 'server'
                // builds, pass through a URL that can be used for obtaining it. Longer term,
                // remove this.
                var customData = new
                {
                    templateUrl = GetDefaultFileAbsoluteUrl(spaBuilder, context)
                };

                // TODO: Add an optional "supplyCustomData" callback param so people using
                //       UsePrerendering() can, for example, pass through cookies into the .ts code

                var renderResult = await prerenderer.RenderToString(
                    entryPoint,
                    customDataParameter: customData);
                await ApplyRenderResult(context, renderResult);
            });
        }
Esempio n. 3
0
 private static void ConfigureSpaPrerendering(IHostingEnvironment env, ISpaBuilder spa)
 {
     spa.UseSpaPrerendering(options =>
     {
         options.BootModulePath    = $"{spa.Options.SourcePath}/dist-server/main.js";
         options.BootModuleBuilder = env.IsDevelopment()
   ? new AngularCliBuilder(npmScript: "build:ssr")
   : null;
         options.ExcludeUrls = new[] { "/sockjs-node" };
     });
 }
Esempio n. 4
0
 public static void UseVueDevelopmentServer(this ISpaBuilder spa)
 {
     spa.UseProxyToSpaDevelopmentServer(async() =>
     {
         if (IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners().Select(x => x.Port).Contains(DEV_PORT))
         {
             return(new Uri($"http://localhost:{DEV_PORT}"));
         }
         throw new InvalidOperationException($"Please serve the port {DEV_PORT}");
     });
 }
Esempio n. 5
0
        /// <summary>
        /// Replacement for the UseReactDevelopmentServer for .NET Core 3.1.x
        /// </summary>
        /// <param name="spa">Single Page Application Builder</param>
        public static void UseReactDevelopmentServer(this ISpaBuilder spa)
        {
            CheckIsNotNull(nameof(spa), spa);
            string sourcePath = spa.Options.SourcePath;

            CheckIsNotNullOrWhitespace(nameof(spa.Options.SourcePath), sourcePath);

            if (CanRunNpm(sourcePath))
            {
                spa.UseReactDevelopmentServer(npmScript: "start");
            }
        }
 /// <summary>
 /// Handles requests by passing them through to an instance of the Angular CLI server.
 /// This means you can always serve up-to-date CLI-built resources without having
 /// to run the Angular CLI server manually.
 ///
 /// This feature should only be used in development. For production deployments, be
 /// sure not to enable the Angular CLI server.
 /// </summary>
 /// <param name="spaBuilder">The <see cref="T:AngularDemo.SpaServices.ISpaBuilder" />.</param>
 /// <param name="npmScript">The name of the script in your package.json file that launches the Angular CLI process.</param>
 public static void UseAngularCliServer(this ISpaBuilder spaBuilder, string npmScript)
 {
     if (spaBuilder == null)
     {
         throw new ArgumentNullException(nameof(spaBuilder));
     }
     if (string.IsNullOrEmpty(spaBuilder.Options.SourcePath))
     {
         throw new InvalidOperationException("To use UseAngularCliServer, you must supply a non-empty value for the SourcePath property of SpaOptions when calling UseSpa.");
     }
     AngularCliMiddleware.Attach(spaBuilder, npmScript);
 }
Esempio n. 7
0
        /// <inheritdoc />
        public async Task Build(ISpaBuilder spaBuilder)
        {
            var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand;
            var sourcePath        = spaBuilder.Options.SourcePath;

            if (string.IsNullOrEmpty(sourcePath))
            {
                throw new InvalidOperationException($"To use {nameof(AngularCliBuilder)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
            }

            var appBuilder = spaBuilder.ApplicationBuilder;
            var applicationStoppingToken = appBuilder.ApplicationServices.GetRequiredService <IHostApplicationLifetime>().ApplicationStopping;
            var logger = LoggerFinder.GetOrCreateLogger(
                appBuilder,
                nameof(AngularCliBuilder));
            var diagnosticSource = appBuilder.ApplicationServices.GetRequiredService <DiagnosticSource>();
            var scriptRunner     = new NodeScriptRunner(
                sourcePath,
                _scriptName,
                "--watch",
                null,
                pkgManagerCommand,
                diagnosticSource,
                applicationStoppingToken);

            scriptRunner.AttachToLogger(logger);

            using (var stdOutReader = new EventedStreamStringReader(scriptRunner.StdOut))
                using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr))
                {
                    try
                    {
                        await scriptRunner.StdOut.WaitForMatch(
                            new Regex("Date", RegexOptions.None, RegexMatchTimeout));
                    }
                    catch (EndOfStreamException ex)
                    {
                        throw new InvalidOperationException(
                                  $"The {pkgManagerCommand} script '{_scriptName}' exited without indicating success.\n" +
                                  $"Output was: {stdOutReader.ReadAsString()}\n" +
                                  $"Error output was: {stdErrReader.ReadAsString()}", ex);
                    }
                    catch (OperationCanceledException ex)
                    {
                        throw new InvalidOperationException(
                                  $"The {pkgManagerCommand} script '{_scriptName}' timed out without indicating success. " +
                                  $"Output was: {stdOutReader.ReadAsString()}\n" +
                                  $"Error output was: {stdErrReader.ReadAsString()}", ex);
                    }
                }
        }
        /// <inheritdoc />
        public Task Build(ISpaBuilder spaBuilder)
        {
            // Locate the AngularCliMiddleware within the provided ISpaBuilder
            var angularCliMiddleware = spaBuilder
                                       .Properties.Keys.OfType <AngularCliMiddleware>().FirstOrDefault();

            if (angularCliMiddleware == null)
            {
                throw new Exception(
                          $"Cannot use {nameof (AngularCliBuilder)} unless you are also using {nameof(AngularCliMiddleware)}.");
            }

            return(angularCliMiddleware.StartAngularCliBuilderAsync(_cliAppName));
        }
Esempio n. 9
0
        public static void UseYarn(this ISpaBuilder spaBuilder, string directory, string command = "dev")
        {
            var pid         = Process.GetCurrentProcess().Id;
            var processInfo = new ProcessStartInfo()
            {
                UseShellExecute  = false,
                FileName         = "node",
                Arguments        = $"config/phandler.js {pid} yarn {command}",
                WorkingDirectory = directory
            };
            var process          = Process.Start(processInfo);
            var processContainer = spaBuilder.ApplicationBuilder.ApplicationServices.GetService <ChildProcessHandlerContainer>();

            processContainer.RegisterChildProcess(process);
        }
Esempio n. 10
0
 /// <summary>
 /// 在spa闭包中使用此函数来启动一个vue服务
 /// </summary>
 /// <param name="spaBuilder">Spa服务构建器</param>
 /// <param name="npmScript">npm脚本名称</param>
 /// <param name="compiledSuccessfullyString">用于判断服务启动完成的字符串</param>
 /// <exception cref="ArgumentNullException"></exception>
 /// <exception cref="InvalidOperationException"></exception>
 public static void UseVueDevelopmentServer(
     this ISpaBuilder spaBuilder,
     string npmScript,
     string compiledSuccessfullyString = null)
 {
     if (spaBuilder == null)
     {
         throw new ArgumentNullException(nameof(spaBuilder));
     }
     if (string.IsNullOrEmpty(spaBuilder.Options.SourcePath))
     {
         throw new InvalidOperationException("在使用UseVueDevelopmentServer之前必须在UseSpa闭包中为SpaOptions.SourcePath属性设置一个非空的值.");
     }
     VueDevelopmentServerMiddleware.Attach(spaBuilder, npmScript, compiledSuccessfullyString);
 }
Esempio n. 11
0
        public static void UseVueDevelopmentServer(this ISpaBuilder spaBuilder, string npmScript)
        {
            if (spaBuilder == null)
            {
                throw new ArgumentNullException(nameof(spaBuilder));
            }

            var spaOptions = spaBuilder.Options;

            if (string.IsNullOrEmpty(spaOptions.SourcePath))
            {
                throw new InvalidOperationException($"To use {nameof(UseVueDevelopmentServer)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
            }

            VueDevelopmentServerMiddleware.Attach(spaBuilder, npmScript);
        }
        /// <inheritdoc />
        public async Task Build(ISpaBuilder spaBuilder)
        {
            //var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand; // todo: use this for .netcore >3.x
            var pkgManagerCommand = "npm";
            var sourcePath        = spaBuilder.Options.SourcePath;

            if (string.IsNullOrEmpty(sourcePath))
            {
                throw new InvalidOperationException($"To use {nameof(ChikoAngularCliBuilder)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
            }

            var logger = spaBuilder.ApplicationBuilder.ApplicationServices.GetService <ILoggerFactory>()
                         ?.CreateLogger <ChikoAngularCliBuilder>();
            var scriptRunner = new NodeScriptRunner(
                sourcePath,
                _scriptName,
                "",
                // "--watch", // todo: make it configurable
                null,
                pkgManagerCommand);

            scriptRunner.AttachToLogger(logger);

            using (var stdOutReader = new EventedStreamStringReader(scriptRunner.StdOut))
                using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr))
                {
                    try
                    {
                        await scriptRunner.StdOut.WaitForMatch(
                            new Regex("chunk", RegexOptions.None, RegexMatchTimeout));
                    }
                    catch (EndOfStreamException ex)
                    {
                        throw new InvalidOperationException(
                                  $"The {pkgManagerCommand} script '{_scriptName}' exited without indicating success.\n" +
                                  $"Output was: {stdOutReader.ReadAsString()}\n" +
                                  $"Error output was: {stdErrReader.ReadAsString()}", ex);
                    }
                    catch (OperationCanceledException ex)
                    {
                        throw new InvalidOperationException(
                                  $"The {pkgManagerCommand} script '{_scriptName}' timed out without indicating success. " +
                                  $"Output was: {stdOutReader.ReadAsString()}\n" +
                                  $"Error output was: {stdErrReader.ReadAsString()}", ex);
                    }
                }
        }
Esempio n. 13
0
        /// <summary>
        /// Handles requests by passing them through to an instance of the node server.
        /// This means you can always serve up-to-date CLI-built resources without having
        /// to run the node server manually.
        /// </summary>
        /// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
        /// <param name="npmScript">The name of the script in your package.json file that launches the node server.</param>
        /// <param name="startupMessage">CLI message to indicate startup</param>
        public static void UseNodeServer(this ISpaBuilder spaBuilder, string npmScript, string portArgPrefix, string startupMessage)
        {
            if (spaBuilder == null)
            {
                throw new ArgumentNullException(nameof(spaBuilder));
            }

            var spaOptions = spaBuilder.Options;

            if (string.IsNullOrEmpty(spaOptions.SourcePath))
            {
                throw new InvalidOperationException(
                          $"To use {nameof(UseNodeServer)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
            }

            Attach(spaBuilder, npmScript, portArgPrefix, startupMessage);
        }
Esempio n. 14
0
        public static ISpaBuilder CheckAndStartAngular(this ISpaBuilder spa, string url = "http://localhost:4200/")
        {
            try
            {
                using (HttpClient client = new HttpClient())
                {
                    var response = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result;
                    spa.UseProxyToSpaDevelopmentServer(url);
                }
            }
            catch (Exception)
            {
                spa.UseAngularCliServer(npmScript: "start");
            }

            return(spa);
        }
Esempio n. 15
0
        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, ScriptRunnerType runner = ScriptRunnerType.Npm, string regex = DefaultRegex, bool forceKill = false, bool useProxy = true)
        {
            string sourcePath = spaBuilder.Options.SourcePath;

            Console.WriteLine("sourcePath", 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   = StartSpaCliServerAsync(sourcePath, scriptName, logger, port, runner, regex, forceKill);

            if (!useProxy)
            {
                return;
            }

            // 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)
            // - given that, there's no reason to use https, and we couldn't even if we
            //   wanted to, because in general the vue-cli 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 svelte-cli server did not start listening for requests " +
                                                 $"within the timeout period of {timeout.Seconds} seconds. " +
                                                 $"Check the log output for error information."));
            });
        }
            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))
            {
                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 create-react-app and attach to middleware pipeline
            var appBuilder = spaBuilder.ApplicationBuilder;
            var logger     = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName);
            var portTask   = StartCreateVueAppServerAsync(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
                );

            spaBuilder.UseProxyToSpaDevelopmentServer(
                () =>
            {
                // 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."
                           ));
            }
                );
        }
        /// <inheritdoc />
        public async Task Build(ISpaBuilder spaBuilder)
        {
            var sourcePath = spaBuilder.Options.SourcePath;

            if (string.IsNullOrEmpty(sourcePath))
            {
                throw new InvalidOperationException($"To use {nameof(AngularCliBuilder)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
            }

            var logger = LoggerFinder.GetOrCreateLogger(
                spaBuilder.ApplicationBuilder,
                nameof(AngularCliBuilder));
            var npmScriptRunner = new NpmScriptRunner(
                sourcePath,
                _npmScriptName,
                "--watch",
                null);

            npmScriptRunner.AttachToLogger(logger);

            using (var stdOutReader = new EventedStreamStringReader(npmScriptRunner.StdOut))
                using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr))
                {
                    try
                    {
                        await npmScriptRunner.StdOut.WaitForMatch(
                            new Regex("Date", RegexOptions.None, RegexMatchTimeout),
                            BuildTimeout);
                    }
                    catch (EndOfStreamException ex)
                    {
                        throw new InvalidOperationException(
                                  $"The NPM script '{_npmScriptName}' exited without indicating success.\n" +
                                  $"Output was: {stdOutReader.ReadAsString()}\n" +
                                  $"Error output was: {stdErrReader.ReadAsString()}", ex);
                    }
                    catch (OperationCanceledException ex)
                    {
                        throw new InvalidOperationException(
                                  $"The NPM script '{_npmScriptName}' timed out without indicating success. " +
                                  $"Output was: {stdOutReader.ReadAsString()}\n" +
                                  $"Error output was: {stdErrReader.ReadAsString()}", ex);
                    }
                }
        }
        /// <summary>
        /// Builder and script are expected to be non-null, as parameters are expected to be checked by the calling function.
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="script"></param>
        public static void Attach(ISpaBuilder builder, string script)
        {
            string sourcePath = builder.Options.SourcePath;

            IApplicationBuilder appBuilder = builder.ApplicationBuilder;
            ILogger             logger     = GetOrCreateLogger(appBuilder, LogCategoryName);
            Task <int>          portTask   = StartPreactServerAsync(sourcePath, script, logger);

            Task <Uri> targetUriTask = portTask.ContinueWith(task => new UriBuilder("http", "localhost", task.Result).Uri);

            SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(builder, () =>
            {
                TimeSpan timeout = builder.Options.StartupTimeout;
                return(targetUriTask.WithTimeout(timeout,
                                                 $"The preact development server didn't start within {timeout.Seconds} s.\nCheck the log output for " +
                                                 $"more information"));
            });
        }
Esempio n. 19
0
        public static void Attach(ISpaBuilder spaBuilder)
        {
            string sourcePath = spaBuilder.Options.SourcePath;

            if (string.IsNullOrEmpty(sourcePath))
            {
                throw new ArgumentException("Cannot be null or empty", "sourcePath");
            }
            ILogger logger =
                spaBuilder.ApplicationBuilder.ApplicationServices.GetService <ILogger <AngularCliServerInfo> >();
            Task <Uri> targetUriTask = AngularCliMiddleware.StartAngularCliServerAsync(sourcePath, logger).ContinueWith <Uri>((Func <Task <AngularCliMiddleware.AngularCliServerInfo>, Uri>)(task => new UriBuilder("http", "localhost", task.Result.Port).Uri));

            spaBuilder.UseProxyToSpaDevelopmentServer((Func <Task <Uri> >)(() =>
            {
                TimeSpan startupTimeout = spaBuilder.Options.StartupTimeout;
                return(targetUriTask.WithTimeout <Uri>(startupTimeout, "The Angular CLI process did not start listening for requests " + string.Format("within the timeout period of {0} seconds. ", (object)startupTimeout.Seconds) + "Check the log output for error information."));
            }));
        }
Esempio n. 20
0
        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 scriptName)
        {
            var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand;
            var sourcePath        = spaBuilder.Options.SourcePath;
            var devServerPort     = spaBuilder.Options.DevServerPort;

            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 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);

            // 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 Angular CLI middleware server)
            // - given that, there's no reason to use https, and we couldn't even if we
            //   wanted to, because in general the Angular CLI server has no certificate
            var targetUriTask = angularCliServerInfoTask.ContinueWith(
                task => new UriBuilder("http", "localhost", task.Result.Port).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 Angular CLI process did not start listening for requests " +
                                                 $"within the timeout period of {timeout.TotalSeconds} seconds. " +
                                                 $"Check the log output for error information."));
            });
        }
        /// <summary>
        /// Handles requests by passing them through to an instance of the node dev server.
        /// This means you don't need to start node dev server manually.
        ///
        /// </summary>
        /// <param name="spaBuilder">The <see cref="ApplicationBuilder"/>.</param>
        /// <param name="command">The command or file name to start dev server.</param>
        /// <param name="arguments">Arguments to start dev server.</param>
        /// <param name="workingDirectory">WorkingDirectory for node dev  server</param>
        /// <param name="envVars">Environment variables for node dev  server</param>
        /// <param name="timeout">Timeout for node process waiting</param>
        /// <param name="timeoutExceedMessage">Message when timeout is exceeded</param>
        /// <param name="logInformation">Log node process output</param>
        /// <param name="logError">Log node js process error</param>
        /// <param name="unsubscribeWhenReady">Stop logging when nodejs process is ready</param>
        public static void UseAspSpaDevelopmentServer(
            this ISpaBuilder spaBuilder,
            string command,
            string arguments,
            string workingDirectory,
            Dictionary<string,string> envVars,
            TimeSpan timeout,
            string timeoutExceedMessage = "Timeout has been exceeded ",
            bool logInformation = true,
            bool logError = false,
            bool unsubscribeWhenReady = true)
        {
            if (spaBuilder == null)
            {
                throw new ArgumentNullException(nameof(spaBuilder));
            }
            var logger = GetOrCreateLogger(spaBuilder.ApplicationBuilder, LogCategoryName);
            NodeRunner runner = GetNodeRunner(spaBuilder.ApplicationBuilder);
            if (runner == null)
            {
                runner = new NodeRunner();
            }
            runner.Command = command;
            runner.Arguments = arguments;
            runner.WorkingDirectory = workingDirectory;
            runner.EnvVars = envVars;
            runner.Timeout = timeout;
            runner.TimeoutExceedMessage = timeoutExceedMessage;
            runner.Launch(logger);
            if (runner.Uri != null)
            {
                if (unsubscribeWhenReady)
                {
                    runner.UnsubscribeLog(logger);
                }
                spaBuilder.UseProxyToSpaDevelopmentServer(runner.Uri);
            }
            else
            {

            }
       }
        private static TimeSpan RegexMatchTimeout = TimeSpan.FromMinutes(5); // 这是仅开发时间的功能, 因此很长时间的超时是可以的

        public static void Attach(
            ISpaBuilder spaBuilder,
            string scriptName, int port = 0, ScriptRunnerType runner = ScriptRunnerType.Npm, string regex = DefaultRegex)
        {
            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));
            }

            // 启动vue-cli并连接到中间件管道
            var appBuilder = spaBuilder.ApplicationBuilder;
            var logger     = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName);
            var portTask   = StartVueCliServerAsync(sourcePath, scriptName, logger, port, runner, regex);

            // 我们代理的所有内容都硬编码为目标http://localhost,因为:
            // - 请求总是来自本地机器(我们不接受远程
            //   直接转到vue-cli服务器的请求)
            // - 鉴于此,没有理由使用https,即使我们也不行
            //   想要,因为通常vue-cli服务器没有证书
            var targetUriTask = portTask.ContinueWith(
                task =>
                new UriBuilder("http", "localhost", task.Result).Uri);

            SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, () =>
            {
                // 在每个请求中,我们创建一个具有自己超时的单独启动任务。 就这样,即使
                // 第一个请求超时,后续请求仍然有效。
                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."));
            });
        }
        /// <summary>
        /// Handles requests by passing them through to an instance of the vue-cli server.
        /// This means you can always serve up-to-date CLI-built resources without having
        /// to run the vue-cli server manually.
        ///
        /// This feature should only be used in development. For production deployments, be
        /// sure not to enable the vue-cli server.
        /// </summary>
        /// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
        /// <param name="npmScript">The name of the script in your package.json file that launches the vue-cli server.</param>
        /// <param name="port">Specify vue cli server port number. If &lt; 80, uses random port. </param>
        /// <param name="runner">Specify the runner, Npm and Yarn are valid options. Yarn support is HIGHLY experimental.</param>
        /// <param name="regex">Specify a custom regex string to search for in the log indicating proxied server is running. VueCli: "running at", QuasarCli: "Compiled successfully"</param>
        public static void UseVueCli(
            this ISpaBuilder spaBuilder,
            string npmScript        = "serve",
            int port                = 8080,
            ScriptRunnerType runner = ScriptRunnerType.Npm,
            string regex            = VueCliMiddleware.DefaultRegex)
        {
            if (spaBuilder == null)
            {
                throw new ArgumentNullException(nameof(spaBuilder));
            }

            var spaOptions = spaBuilder.Options;

            if (string.IsNullOrEmpty(spaOptions.SourcePath))
            {
                throw new InvalidOperationException($"To use {nameof(UseVueCli)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
            }

            VueCliMiddleware.Attach(spaBuilder, npmScript, port, runner: runner, regex: regex);
        }
Esempio n. 24
0
        public static void UseAureliaCliServer(
            this ISpaBuilder spaBuilder,
            string npmScript = "au run",
            //PackageManager packageManager = PackageManager.Npm
            bool hotModuleReload = true
            )
        {
            if (spaBuilder == null)
            {
                throw new ArgumentNullException(nameof(spaBuilder));
            }

            var spaOptions = spaBuilder.Options;

            if (string.IsNullOrEmpty(spaOptions.SourcePath))
            {
                throw new InvalidOperationException($"To use {nameof(AureliaMiddlewareExtensions)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
            }

            AureliaCliDevelopmentServer.Attach(spaBuilder, npmScript, PackageManager.Npm, hotModuleReload);
        }
Esempio n. 25
0
        public static void UseVueDevelopmentServer(this ISpaBuilder spaBuilder)
        {
            spaBuilder.UseProxyToSpaDevelopmentServer(async() =>
            {
                if (IsRunning())
                {
                    return(DevelopmentServerEndpoint);
                }

                _ = getRunnedTaskOutput(getProcess(), getLogger(spaBuilder), taskCompletionSource);

                _ = getRunnedTaskError(getProcess(), getLogger(spaBuilder), taskCompletionSource);

                if (await Task.WhenAny(Task.Delay(Timeout), taskCompletionSource.Task) == Task.Delay(Timeout))
                {
                    throw new TimeoutException();
                }

                return(DevelopmentServerEndpoint);
            });
        }
        public static void UsePreactDevelopmentServer(
            this ISpaBuilder builder, string script)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }
            SpaOptions options = builder.Options;

            if (string.IsNullOrEmpty(options.SourcePath))
            {
                throw new InvalidOperationException($"To use {nameof(UsePreactDevelopmentServer)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
            }

            if (string.IsNullOrEmpty(script))
            {
                throw new InvalidOperationException(
                          $"You must specify a valid npm script name to start the development server");
            }

            PreactDevelopmentServerMiddleware.Attach(builder, script);
        }
        /// <summary>
        /// Handles requests by passing them through to an instance of the vue-cli-service server.
        /// This means you can always serve up-to-date CLI-built resources without having
        /// to run the vue-cli-service server manually.
        ///
        /// This feature should only be used in development. For production deployments, be
        /// sure not to enable the vue-cli-service server.
        /// </summary>
        /// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
        /// <param name="npmScript">The name of the script in your package.json file that launches the vue-cli-service server.</param>
        public static void UseVueCliServer(
            this ISpaBuilder spaBuilder,
            string npmScript = "serve")
        {
            if (spaBuilder == null)
            {
                throw new ArgumentNullException(nameof(spaBuilder));
            }

            var spaOptions = spaBuilder.Options;

            if (string.IsNullOrEmpty(spaOptions.SourcePath))
            {
                spaOptions.SourcePath = "ClientApp";
            }

            if (string.IsNullOrEmpty(spaOptions.PackageManagerCommand))
            {
                spaOptions.PackageManagerCommand = "npm";
            }

            VueCliServerMiddleware.Attach(spaBuilder, npmScript);
        }
        public static void UseWebpackDevelopmentServer(
            this ISpaBuilder spaBuilder,
            string npmScriptName
            )
        {
            if (spaBuilder == null)
            {
#pragma warning disable CA1303 // Do not pass literals as localized parameters
                throw new ArgumentException($"Parameter {nameof(spaBuilder)} cannot be null.", nameof(spaBuilder));
#pragma warning restore CA1303 // Do not pass literals as localized parameters
            }

            var spaOptions = spaBuilder.Options;

            if (string.IsNullOrEmpty(spaOptions.SourcePath))
            {
#pragma warning disable CA1303 // Do not pass literals as localized parameters
                throw new InvalidOperationException($"To use {nameof(UseWebpackDevelopmentServer)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
#pragma warning restore CA1303 // Do not pass literals as localized parameters
            }

            WebpackDevelopmentServerMiddleware.Attach(spaBuilder, npmScriptName);
        }
Esempio n. 29
0
    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."));
        });
    }
Esempio n. 30
0
        /// <summary>
        /// Configures the application to forward incoming requests to a local Single Page
        /// Application (SPA) development server. This is only intended to be used during
        /// development. Do not enable this middleware in production applications.
        /// </summary>
        /// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
        /// <param name="baseUriTaskFactory">A callback that will be invoked on each request to supply a <see cref="Task"/> that resolves with the target base URI to which requests should be proxied.</param>
        public static void UseProxyToSpaDevelopmentServer(this ISpaBuilder spaBuilder,
                                                          Func <Task <Uri> > baseUriTaskFactory)
        {
            var applicationBuilder = spaBuilder.ApplicationBuilder;

            //var applicationStoppingToken = GetStoppingToken(applicationBuilder);

            // Since we might want to proxy WebSockets requests (e.g., by default, AngularCliMiddleware
            // requires it), enable it for the app
            applicationBuilder.UseWebSockets(WebSocketEcho);

            // It's important not to time out the requests, as some of them might be to
            // server-sent event endpoints or similar, where it's expected that the response
            // takes an unlimited time and never actually completes
            var neverTimeOutHttpClient = SpaProxy.CreateHttpClientForProxy(Timeout.InfiniteTimeSpan);

            // Proxy all requests to the SPA development server
            applicationBuilder.Use(async(context, next) =>
            {
                var routeValues  = HttpContext.Current.Request.RequestContext.RouteData.Values;
                var isController = routeValues.ContainsKey("controller") &&
                                   !routeValues.Values.Any(x => x.ToString().Contains("."));
                var isAction = routeValues.ContainsKey("action");

                if (context.Request.Path.StartsWithSegments(spaBuilder.Options.DefaultApi) ||
                    (isController && isAction))
                {
                    await next();
                }
                else
                {
                    var didProxyRequest = await SpaProxy.PerformProxyRequest(HttpContext.Current,
                                                                             neverTimeOutHttpClient, baseUriTaskFactory(), context.Request.CallCancelled, proxy404s: true);
                }
            });
        }