| File: SpaProxyMiddleware.cs | Web Access |
| Project: src\aspnetcore\src\Middleware\Spa\SpaProxy\src\Microsoft.AspNetCore.SpaProxy.csproj (Microsoft.AspNetCore.SpaProxy) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.SpaProxy; /// <summary> /// Middleware to display a page while the SPA proxy is launching and redirect to the proxy url once the proxy is /// ready or we have given up trying to start it. /// This is to help Visual Studio work well in several scenarios by allowing VS to: /// 1) Launch on the URL configured for the backend (we handle the redirect to the proxy when ready). /// 2) Ensure that the server is up and running quickly instead of waiting for the proxy to be ready to start the /// server which causes Visual Studio to think the app failed to launch. /// </summary> internal sealed class SpaProxyMiddleware { private readonly RequestDelegate _next; private readonly SpaProxyLaunchManager _spaProxyLaunchManager; private readonly IOptions<SpaDevelopmentServerOptions> _options; private readonly IHostApplicationLifetime _hostLifetime; private readonly ILogger<SpaProxyMiddleware> _logger; public SpaProxyMiddleware( RequestDelegate next, SpaProxyLaunchManager spaProxyLaunchManager, IOptions<SpaDevelopmentServerOptions> options, IHostApplicationLifetime hostLifetime, ILogger<SpaProxyMiddleware> logger) { _next = next ?? throw new ArgumentNullException(nameof(next)); _spaProxyLaunchManager = spaProxyLaunchManager ?? throw new ArgumentNullException(nameof(spaProxyLaunchManager)); _options = options ?? throw new ArgumentNullException(nameof(options)); _hostLifetime = hostLifetime ?? throw new ArgumentNullException(nameof(hostLifetime)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public Task Invoke(HttpContext context) { if (context.Request.Path.Equals(new Uri(_options.Value.ServerUrl).LocalPath)) { return InvokeCore(context); } return _next(context); } private async Task InvokeCore(HttpContext context) { context.Response.Headers[HeaderNames.CacheControl] = "no-cache, no-store, must-revalidate, max-age=0"; if (!await _spaProxyLaunchManager.IsSpaProxyRunning(context.RequestAborted)) { _spaProxyLaunchManager.StartInBackground(_hostLifetime.ApplicationStopping); _logger.LogInformation("SPA proxy is not ready. Returning temporary landing page."); context.Response.ContentType = "text/html"; await using var writer = new StreamWriter(context.Response.Body, Encoding.UTF8); await writer.WriteAsync(GenerateSpaLaunchPage(_options.Value)); } else { if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation($"SPA proxy is ready. Redirecting to {_options.Value.GetRedirectUrl()}."); } context.Response.Redirect(_options.Value.GetRedirectUrl()); } static string GenerateSpaLaunchPage(SpaDevelopmentServerOptions options) { return $@" <!DOCTYPE html> <html lang=""en""> <head> <meta charset = ""UTF-8"" > <meta http-equiv=""X-UA-Compatible"" content=""IE=edge""> <meta http-equiv=""refresh"" content=""3""> <meta name=""viewport"" content=""width=device-width, initial-scale=1.0""> <title>SPA proxy launch page</title> </head> <body> <h1>Launching the SPA proxy...</h1> <p>This page will automatically redirect to <a href=""{HtmlEncoder.Default.Encode(options.GetRedirectUrl())}"">{HtmlEncoder.Default.Encode(options.GetRedirectUrl())}</a> when the SPA proxy is ready.</p> </body> </html>"; } } }