/// <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);
        }
Beispiel #5
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(
            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);
            });
        }