1
1
using System ;
2
2
using System . IO ;
3
+ using System . Threading ;
3
4
using System . Threading . Tasks ;
4
5
using Microsoft . AspNetCore . NodeServices ;
5
6
using Microsoft . AspNetCore . SpaServices . Webpack ;
6
7
using Microsoft . AspNetCore . Builder ;
7
8
using Microsoft . AspNetCore . Hosting ;
8
9
using Microsoft . Extensions . PlatformAbstractions ;
9
10
using Newtonsoft . Json ;
11
+ using Microsoft . AspNetCore . Http ;
10
12
11
13
namespace Microsoft . AspNetCore . Builder
12
14
{
@@ -15,8 +17,6 @@ namespace Microsoft.AspNetCore.Builder
15
17
/// </summary>
16
18
public static class WebpackDevMiddleware
17
19
{
18
- private const string WebpackDevMiddlewareScheme = "http" ;
19
- private const string WebpackHotMiddlewareEndpoint = "/__webpack_hmr" ;
20
20
private const string DefaultConfigFile = "webpack.config.js" ;
21
21
22
22
/// <summary>
@@ -75,12 +75,18 @@ public static void UseWebpackDevMiddleware(
75
75
"/Content/Node/webpack-dev-middleware.js" ) ;
76
76
var nodeScript = new StringAsTempFile ( script ) ; // Will be cleaned up on process exit
77
77
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
+
78
83
// Tell Node to start the server hosting webpack-dev-middleware
79
84
var devServerOptions = new
80
85
{
81
86
webpackConfigPath = Path . Combine ( nodeServicesOptions . ProjectPath , options . ConfigFile ?? DefaultConfigFile ) ,
82
87
suppliedOptions = options ,
83
- understandsMultiplePublicPaths = true
88
+ understandsMultiplePublicPaths = true ,
89
+ hotModuleReplacementEndpointUrl = hmrEndpoint
84
90
} ;
85
91
var devServerInfo =
86
92
nodeServices . InvokeExportAsync < WebpackDevServerInfo > ( nodeScript . FileName , "createWebpackDevServer" ,
@@ -94,33 +100,30 @@ public static void UseWebpackDevMiddleware(
94
100
}
95
101
96
102
// 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
+ {
97
114
// Note that this is hardcoded to make requests to "localhost" regardless of the hostname of the
98
115
// server as far as the client is concerned. This is because ConditionalProxyMiddlewareOptions is
99
116
// the one making the internal HTTP requests, and it's going to be to some port on this machine
100
117
// because aspnet-webpack hosts the dev server there. We can't use the hostname that the client
101
118
// sees, because that could be anything (e.g., some upstream load balancer) and we might not be
102
119
// 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 ) ;
124
127
}
125
128
126
129
#pragma warning disable CS0649
0 commit comments