3
3
4
4
using System ;
5
5
using System . Collections . Generic ;
6
+ using System . Diagnostics ;
7
+ using System . IO ;
6
8
using System . Threading . Tasks ;
7
9
using Microsoft . AspNetCore . Http ;
8
10
using Microsoft . AspNetCore . Http . Features ;
@@ -26,10 +28,10 @@ public async Task InvokeAsync(HttpContext context)
26
28
// We only need to support this for requests that could be initiated by a browser.
27
29
if ( IsBrowserDocumentRequest ( context ) )
28
30
{
29
- // Use a custom StreamWrapper to rewrite output on Write/WriteAsync
30
- using var responseStreamWrapper = new ResponseStreamWrapper ( context , _logger ) ;
31
+ // Use a custom stream to buffer the response body for rewriting.
32
+ using var memoryStream = new MemoryStream ( ) ;
31
33
var originalBodyFeature = context . Features . Get < IHttpResponseBodyFeature > ( ) ;
32
- context . Features . Set < IHttpResponseBodyFeature > ( new StreamResponseBodyFeature ( responseStreamWrapper ) ) ;
34
+ context . Features . Set < IHttpResponseBodyFeature > ( new StreamResponseBodyFeature ( memoryStream ) ) ;
33
35
34
36
try
35
37
{
@@ -40,15 +42,35 @@ public async Task InvokeAsync(HttpContext context)
40
42
context . Features . Set ( originalBodyFeature ) ;
41
43
}
42
44
43
- if ( responseStreamWrapper . IsHtmlResponse && _logger . IsEnabled ( LogLevel . Debug ) )
45
+ if ( memoryStream . TryGetBuffer ( out var buffer ) && buffer . Count > 0 )
44
46
{
45
- if ( responseStreamWrapper . ScriptInjectionPerformed )
47
+ var response = context . Response ;
48
+ var baseStream = response . Body ;
49
+
50
+ if ( IsHtmlResponse ( response ) )
46
51
{
47
- Log . BrowserConfiguredForRefreshes ( _logger ) ;
52
+ Log . SetupResponseForBrowserRefresh ( _logger ) ;
53
+
54
+ // Since we're changing the markup content, reset the content-length
55
+ response . Headers . ContentLength = null ;
56
+
57
+ var scriptInjectionPerformed = await WebSocketScriptInjection . TryInjectLiveReloadScriptAsync ( baseStream , buffer ) ;
58
+ if ( scriptInjectionPerformed )
59
+ {
60
+ Log . BrowserConfiguredForRefreshes ( _logger ) ;
61
+ }
62
+ else if ( response . Headers . TryGetValue ( HeaderNames . ContentEncoding , out var contentEncodings ) )
63
+ {
64
+ Log . ResponseCompressionDetected ( _logger , contentEncodings ) ;
65
+ }
66
+ else
67
+ {
68
+ Log . FailedToConfiguredForRefreshes ( _logger ) ;
69
+ }
48
70
}
49
71
else
50
72
{
51
- Log . FailedToConfiguredForRefreshes ( _logger ) ;
73
+ await baseStream . WriteAsync ( buffer ) ;
52
74
}
53
75
}
54
76
}
@@ -92,26 +114,41 @@ internal static bool IsBrowserDocumentRequest(HttpContext context)
92
114
return false ;
93
115
}
94
116
117
+ private bool IsHtmlResponse ( HttpResponse response )
118
+ => ( response . StatusCode == StatusCodes . Status200OK || response . StatusCode == StatusCodes . Status500InternalServerError ) &&
119
+ MediaTypeHeaderValue . TryParse ( response . ContentType , out var mediaType ) &&
120
+ mediaType . IsSubsetOf ( _textHtmlMediaType ) &&
121
+ ( ! mediaType . Charset . HasValue || mediaType . Charset . Equals ( "utf-8" , StringComparison . OrdinalIgnoreCase ) ) ;
122
+
95
123
internal static class Log
96
124
{
97
125
private static readonly Action < ILogger , Exception ? > _setupResponseForBrowserRefresh = LoggerMessage . Define (
98
- LogLevel . Debug ,
126
+ LogLevel . Debug ,
99
127
new EventId ( 1 , "SetUpResponseForBrowserRefresh" ) ,
100
- "Response markup is scheduled to include browser refresh script injection." ) ;
128
+ "Response markup is scheduled to include browser refresh script injection." ) ;
101
129
102
130
private static readonly Action < ILogger , Exception ? > _browserConfiguredForRefreshes = LoggerMessage . Define (
103
- LogLevel . Debug ,
131
+ LogLevel . Debug ,
104
132
new EventId ( 2 , "BrowserConfiguredForRefreshes" ) ,
105
- "Response markup was updated to include browser refresh script injection." ) ;
133
+ "Response markup was updated to include browser refresh script injection." ) ;
106
134
107
135
private static readonly Action < ILogger , Exception ? > _failedToConfigureForRefreshes = LoggerMessage . Define (
108
- LogLevel . Debug ,
136
+ LogLevel . Warning ,
109
137
new EventId ( 3 , "FailedToConfiguredForRefreshes" ) ,
110
- "Unable to configure browser refresh script injection on the response." ) ;
138
+ "Unable to configure browser refresh script injection on the response. " +
139
+ $ "Consider manually adding '{ WebSocketScriptInjection . InjectedScript } ' to the body of the page.") ;
140
+
141
+ private static readonly Action < ILogger , StringValues , Exception ? > _responseCompressionDetected = LoggerMessage . Define < StringValues > (
142
+ LogLevel . Warning ,
143
+ new EventId ( 4 , "ResponseCompressionDetected" ) ,
144
+ "Unable to configure browser refresh script injection on the response. " +
145
+ $ "This may have been caused by the response's { HeaderNames . ContentEncoding } : '{{encoding}}'. " +
146
+ "Consider disabling response compression." ) ;
111
147
112
148
public static void SetupResponseForBrowserRefresh ( ILogger logger ) => _setupResponseForBrowserRefresh ( logger , null ) ;
113
149
public static void BrowserConfiguredForRefreshes ( ILogger logger ) => _browserConfiguredForRefreshes ( logger , null ) ;
114
150
public static void FailedToConfiguredForRefreshes ( ILogger logger ) => _failedToConfigureForRefreshes ( logger , null ) ;
151
+ public static void ResponseCompressionDetected ( ILogger logger , StringValues encoding ) => _responseCompressionDetected ( logger , encoding , null ) ;
115
152
}
116
153
}
117
154
}
0 commit comments