11using System ;
22using System . IO ;
3+ using System . Threading ;
34using System . Threading . Tasks ;
45using Microsoft . AspNetCore . NodeServices ;
56using Microsoft . AspNetCore . SpaServices . Webpack ;
67using Microsoft . AspNetCore . Builder ;
78using Microsoft . AspNetCore . Hosting ;
89using Microsoft . Extensions . PlatformAbstractions ;
910using Newtonsoft . Json ;
11+ using Microsoft . AspNetCore . Http ;
1012
1113namespace Microsoft . AspNetCore . Builder
1214{
@@ -15,8 +17,6 @@ namespace Microsoft.AspNetCore.Builder
1517 /// </summary>
1618 public static class WebpackDevMiddleware
1719 {
18- private const string WebpackDevMiddlewareScheme = "http" ;
19- private const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr" ;
2020 private const string DefaultConfigFile = "webpack.config.js" ;
2121
2222 /// <summary>
@@ -75,12 +75,18 @@ public static void UseWebpackDevMiddleware(
7575 "/Content/Node/webpack-dev-middleware.js" ) ;
7676 var nodeScript = new StringAsTempFile ( script ) ; // Will be cleaned up on process exit
7777
78+ // Ideally, this would be relative to the application's PathBase (so it could work in virtual directories)
79+ // but it's not clear that such information exists during application startup, as opposed to within the context
80+ // of a request.
81+ var hmrEndpoint = "/__webpack_hmr" ;
82+
7883 // Tell Node to start the server hosting webpack-dev-middleware
7984 var devServerOptions = new
8085 {
8186 webpackConfigPath = Path . Combine ( nodeServicesOptions . ProjectPath , options . ConfigFile ?? DefaultConfigFile ) ,
8287 suppliedOptions = options ,
83- understandsMultiplePublicPaths = true
88+ understandsMultiplePublicPaths = true ,
89+ hotModuleReplacementEndpointUrl = hmrEndpoint
8490 } ;
8591 var devServerInfo =
8692 nodeServices . InvokeExportAsync < WebpackDevServerInfo > ( nodeScript . FileName , "createWebpackDevServer" ,
@@ -94,33 +100,30 @@ public static void UseWebpackDevMiddleware(
94100 }
95101
96102 // Proxy the corresponding requests through ASP.NET and into the Node listener
103+ // Anything under /<publicpath> (e.g., /dist) is proxied as a normal HTTP request with a typical timeout (100s is the default from HttpClient),
104+ // plus /__webpack_hmr is proxied with infinite timeout, because it's an EventSource (long-lived request).
105+ foreach ( var publicPath in devServerInfo . PublicPaths )
106+ {
107+ appBuilder . UseProxyToLocalWebpackDevMiddleware ( publicPath , devServerInfo . Port , TimeSpan . FromSeconds ( 100 ) ) ;
108+ }
109+ appBuilder . UseProxyToLocalWebpackDevMiddleware ( hmrEndpoint , devServerInfo . Port , Timeout . InfiniteTimeSpan ) ;
110+ }
111+
112+ private static void UseProxyToLocalWebpackDevMiddleware ( this IApplicationBuilder appBuilder , string publicPath , int proxyToPort , TimeSpan requestTimeout )
113+ {
97114 // Note that this is hardcoded to make requests to "localhost" regardless of the hostname of the
98115 // server as far as the client is concerned. This is because ConditionalProxyMiddlewareOptions is
99116 // the one making the internal HTTP requests, and it's going to be to some port on this machine
100117 // because aspnet-webpack hosts the dev server there. We can't use the hostname that the client
101118 // sees, because that could be anything (e.g., some upstream load balancer) and we might not be
102119 // able to make outbound requests to it from here.
103- var proxyOptions = new ConditionalProxyMiddlewareOptions ( WebpackDevMiddlewareScheme ,
104- "localhost" , devServerInfo . Port . ToString ( ) ) ;
105- foreach ( var publicPath in devServerInfo . PublicPaths )
106- {
107- appBuilder . UseMiddleware < ConditionalProxyMiddleware > ( publicPath , proxyOptions ) ;
108- }
109-
110- // While it would be nice to proxy the /__webpack_hmr requests too, these return an EventStream,
111- // and the Microsoft.AspNetCore.Proxy code doesn't handle that entirely - it throws an exception after
112- // a while. So, just serve a 302 for those. But note that we must use the hostname that the client
113- // sees, not "localhost", so that it works even when you're not running on localhost (e.g., Docker).
114- appBuilder . Map ( WebpackHotMiddlewareEndpoint , builder =>
115- {
116- builder . Use ( next => ctx =>
117- {
118- var hostname = ctx . Request . Host . Host ;
119- ctx . Response . Redirect (
120- $ "{ WebpackDevMiddlewareScheme } ://{ hostname } :{ devServerInfo . Port . ToString ( ) } { WebpackHotMiddlewareEndpoint } ") ;
121- return Task . FromResult ( 0 ) ;
122- } ) ;
123- } ) ;
120+ // Also note that the webpack HMR service always uses HTTP, even if your app server uses HTTPS,
121+ // because the HMR service has no need for HTTPS (the client doesn't see it directly - all traffic
122+ // to it is proxied), and the HMR service couldn't use HTTPS anyway (in general it wouldn't have
123+ // the necessary certificate).
124+ var proxyOptions = new ConditionalProxyMiddlewareOptions (
125+ "http" , "localhost" , proxyToPort . ToString ( ) , requestTimeout ) ;
126+ appBuilder . UseMiddleware < ConditionalProxyMiddleware > ( publicPath , proxyOptions ) ;
124127 }
125128
126129#pragma warning disable CS0649
0 commit comments