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); }); }
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" }; }); }
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}"); }); }
/// <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); }
/// <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)); }
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); }
/// <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); }
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); } } }
/// <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); }
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); }
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")); }); }
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.")); })); }
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 < 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); }
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); }
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); }
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.")); }); }
/// <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); } }); }