From f071590fcefa137919d3d500c0180bf385c93248 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 9 Sep 2016 16:31:15 +0100 Subject: [PATCH] Webpack HMR EventSource requests are now proxied (rather than redirected) to the local HMR server. Fixes #271. --- .../Webpack/ConditionalProxyMiddleware.cs | 1 + .../ConditionalProxyMiddlewareOptions.cs | 6 +++- .../Webpack/WebpackDevMiddleware.cs | 36 +++++++++---------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs index 72b66c3..5a56c81 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddleware.cs @@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.SpaServices.Webpack _pathPrefix = pathPrefix; _options = options; _httpClient = new HttpClient(new HttpClientHandler()); + _httpClient.Timeout = _options.RequestTimeout; } public async Task Invoke(HttpContext context) diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs index 5654007..2c3311a 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/ConditionalProxyMiddlewareOptions.cs @@ -1,16 +1,20 @@ +using System; + namespace Microsoft.AspNetCore.SpaServices.Webpack { internal class ConditionalProxyMiddlewareOptions { - public ConditionalProxyMiddlewareOptions(string scheme, string host, string port) + public ConditionalProxyMiddlewareOptions(string scheme, string host, string port, TimeSpan requestTimeout) { Scheme = scheme; Host = host; Port = port; + RequestTimeout = requestTimeout; } public string Scheme { get; } public string Host { get; } public string Port { get; } + public TimeSpan RequestTimeout { get; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs index dcc5d4f..d046c28 100644 --- a/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs +++ b/src/Microsoft.AspNetCore.SpaServices/Webpack/WebpackDevMiddleware.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.PlatformAbstractions; using Newtonsoft.Json; +using System.Threading; // Putting in this namespace so it's always available whenever MapRoute is @@ -14,8 +15,6 @@ namespace Microsoft.AspNetCore.Builder { public static class WebpackDevMiddleware { - private const string WebpackDevMiddlewareScheme = "http"; - private const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr"; private const string DefaultConfigFile = "webpack.config.js"; public static void UseWebpackDevMiddleware( @@ -62,30 +61,27 @@ namespace Microsoft.AspNetCore.Builder JsonConvert.SerializeObject(devServerOptions)).Result; // Proxy the corresponding requests through ASP.NET and into the Node listener + // Anything under / (e.g., /dist) is proxied as a normal HTTP request with a typical timeout (100s is the default from HttpClient), + // plus /__webpack_hmr is proxied with infinite timeout, because it's an EventSource (long-lived request). + appBuilder.UseProxyToLocalWebpackDevMiddleware(devServerInfo.PublicPath, devServerInfo.Port, TimeSpan.FromSeconds(100)); + appBuilder.UseProxyToLocalWebpackDevMiddleware("/__webpack_hmr", devServerInfo.Port, Timeout.InfiniteTimeSpan); + } + + private static void UseProxyToLocalWebpackDevMiddleware(this IApplicationBuilder appBuilder, string publicPath, int proxyToPort, TimeSpan requestTimeout) + { // Note that this is hardcoded to make requests to "localhost" regardless of the hostname of the // server as far as the client is concerned. This is because ConditionalProxyMiddlewareOptions is // the one making the internal HTTP requests, and it's going to be to some port on this machine // because aspnet-webpack hosts the dev server there. We can't use the hostname that the client // sees, because that could be anything (e.g., some upstream load balancer) and we might not be // able to make outbound requests to it from here. - var proxyOptions = new ConditionalProxyMiddlewareOptions(WebpackDevMiddlewareScheme, - "localhost", devServerInfo.Port.ToString()); - appBuilder.UseMiddleware(devServerInfo.PublicPath, proxyOptions); - - // While it would be nice to proxy the /__webpack_hmr requests too, these return an EventStream, - // and the Microsoft.AspNetCore.Proxy code doesn't handle that entirely - it throws an exception after - // a while. So, just serve a 302 for those. But note that we must use the hostname that the client - // sees, not "localhost", so that it works even when you're not running on localhost (e.g., Docker). - appBuilder.Map(WebpackHotMiddlewareEndpoint, builder => - { - builder.Use(next => ctx => - { - var hostname = ctx.Request.Host.Host; - ctx.Response.Redirect( - $"{WebpackDevMiddlewareScheme}://{hostname}:{devServerInfo.Port.ToString()}{WebpackHotMiddlewareEndpoint}"); - return Task.FromResult(0); - }); - }); + // Also note that the webpack HMR service always uses HTTP, even if your app server uses HTTPS, + // because the HMR service has no need for HTTPS (the client doesn't see it directly - all traffic + // to it is proxied), and the HMR service couldn't use HTTPS anyway (in general it wouldn't have + // the necessary certificate). + var proxyOptions = new ConditionalProxyMiddlewareOptions( + "http", "localhost", proxyToPort.ToString(), requestTimeout); + appBuilder.UseMiddleware(publicPath, proxyOptions); } #pragma warning disable CS0649