/// <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="IMulitSpaBuilder"/>.</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 IMulitSpaBuilder 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(); // 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) => { if (context.Request.Path.StartsWithSegments(PathString.FromUriComponent(spaBuilder.Options.PublicPath))) { var didProxyRequest = await SpaProxy.PerformProxyRequest( context, neverTimeOutHttpClient, baseUriTaskFactory(), applicationStoppingToken, proxy404s: true); } else { await next(); } }); }
public static void UseProxyToSpaDevelopmentServer( this IMulitSpaBuilder spaBuilder, Uri baseUri) { UseProxyToSpaDevelopmentServer( spaBuilder, () => Task.FromResult(baseUri)); }
public static void UseProxyToSpaDevelopmentServer( this IMulitSpaBuilder spaBuilder, string baseUri) { UseProxyToSpaDevelopmentServer( spaBuilder, new Uri(baseUri)); }
public static void UseVueCliServer( this IMulitSpaBuilder 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(UseVueCliServer)}, you must supply a non-empty value for the {nameof(MulitSpaOptions.SourcePath)} property of {nameof(MulitSpaOptions)} when calling {nameof(MulitSpaApplicationBuilderExtensions.UseMulitSpa)}."); } VueCliMiddleware.Attach(spaBuilder, npmScript); }
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( IMulitSpaBuilder 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 Angular CLI and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); var angularCliServerInfoTask = StartAngularCliServerAsync(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 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 => { spaBuilder.Options.PublicPath = task.Result.PublicPath; return(new UriBuilder("http", "localhost", task.Result.Port).Uri); }); VueSpaProxyingExtensions.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.Seconds} seconds. " + $"Check the log output for error information.")); }); }
public static void Attach(IMulitSpaBuilder spaBuilder) { if (spaBuilder == null) { throw new ArgumentNullException(nameof(spaBuilder)); } var app = spaBuilder.ApplicationBuilder; var options = spaBuilder.Options; var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); logger.LogInformation($"Public path:{options.PublicPath}"); // 先处理静态资源文件, 未找到的重置为index.html来处理 app.UseSpaStaticFilesInternal(options.PublicPath, spaBuilder.Options.DistPath, options.DefaultPageStaticFileOptions, allowFallbackOnServingWebRootFiles: true); // 剩余的请求全部转发到默认页面 app.Use((context, next) => { var loggerFactory = context.RequestServices.GetRequiredService <ILoggerFactory>(); var localLogger = loggerFactory.CreateLogger($"{LogCategoryName}.Request"); localLogger.LogInformation($"Request {context.Request.Path}"); if (!context.Request.Path.StartsWithSegments(PathString.FromUriComponent(options.PublicPath))) { return(next()); } // If we have an Endpoint, then this is a deferred match - just noop. if (context.GetEndpoint() != null) { return(next()); } context.Request.Path = options.DefaultPage; localLogger.LogInformation($"Reset request path to {options.DefaultPage}"); return(next()); }); // 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.PublicPath, spaBuilder.Options.DistPath, 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) => { var loggerFactory = context.RequestServices.GetRequiredService <ILoggerFactory>(); var localLogger = loggerFactory.CreateLogger($"{LogCategoryName}.2ndRequest"); if (!context.Request.Path.StartsWithSegments(PathString.FromUriComponent(options.PublicPath))) { localLogger.LogInformation("Not public path, Move to next"); return(next()); } // If we have an Endpoint, then this is a deferred match - just noop. if (context.GetEndpoint() != null) { localLogger.LogInformation("context.GetEndpoint() != null, Move to next"); return(next()); } 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); }); }