@@ -17,23 +17,24 @@ namespace Microsoft.AspNetCore.ResponseCompression
17
17
/// </summary>
18
18
internal class BodyWrapperStream : Stream , IHttpBufferingFeature , IHttpSendFileFeature
19
19
{
20
- private readonly HttpResponse _response ;
20
+ private readonly HttpContext _context ;
21
21
private readonly Stream _bodyOriginalStream ;
22
22
private readonly IResponseCompressionProvider _provider ;
23
- private readonly ICompressionProvider _compressionProvider ;
24
23
private readonly IHttpBufferingFeature _innerBufferFeature ;
25
24
private readonly IHttpSendFileFeature _innerSendFileFeature ;
26
25
26
+ private ICompressionProvider _compressionProvider = null ;
27
27
private bool _compressionChecked = false ;
28
28
private Stream _compressionStream = null ;
29
+ private bool _providerCreated = false ;
30
+ private bool _autoFlush = false ;
29
31
30
- internal BodyWrapperStream ( HttpResponse response , Stream bodyOriginalStream , IResponseCompressionProvider provider , ICompressionProvider compressionProvider ,
32
+ internal BodyWrapperStream ( HttpContext context , Stream bodyOriginalStream , IResponseCompressionProvider provider ,
31
33
IHttpBufferingFeature innerBufferFeature , IHttpSendFileFeature innerSendFileFeature )
32
34
{
33
- _response = response ;
35
+ _context = context ;
34
36
_bodyOriginalStream = bodyOriginalStream ;
35
37
_provider = provider ;
36
- _compressionProvider = compressionProvider ;
37
38
_innerBufferFeature = innerBufferFeature ;
38
39
_innerSendFileFeature = innerSendFileFeature ;
39
40
}
@@ -125,6 +126,10 @@ public override void Write(byte[] buffer, int offset, int count)
125
126
if ( _compressionStream != null )
126
127
{
127
128
_compressionStream . Write ( buffer , offset , count ) ;
129
+ if ( _autoFlush )
130
+ {
131
+ _compressionStream . Flush ( ) ;
132
+ }
128
133
}
129
134
else
130
135
{
@@ -133,67 +138,101 @@ public override void Write(byte[] buffer, int offset, int count)
133
138
}
134
139
135
140
#if NET451
136
- public override IAsyncResult BeginWrite ( byte [ ] buffer , int offset , int count , AsyncCallback callback , object state )
141
+ public override IAsyncResult BeginWrite ( byte [ ] buffer , int offset , int count , AsyncCallback callback , Object state )
137
142
{
138
- OnWrite ( ) ;
139
-
140
- if ( _compressionStream != null )
141
- {
142
- return _compressionStream . BeginWrite ( buffer , offset , count , callback , state ) ;
143
- }
144
- return _bodyOriginalStream . BeginWrite ( buffer , offset , count , callback , state ) ;
143
+ var tcs = new TaskCompletionSource < object > ( state ) ;
144
+ InternalWriteAsync ( buffer , offset , count , callback , tcs ) ;
145
+ return tcs . Task ;
145
146
}
146
147
147
- public override void EndWrite ( IAsyncResult asyncResult )
148
+ private async void InternalWriteAsync ( byte [ ] buffer , int offset , int count , AsyncCallback callback , TaskCompletionSource < object > tcs )
148
149
{
149
- if ( ! _compressionChecked )
150
+ try
150
151
{
151
- throw new InvalidOperationException ( "BeginWrite was not called before EndWrite" ) ;
152
+ await WriteAsync ( buffer , offset , count ) ;
153
+ tcs . TrySetResult ( null ) ;
154
+ }
155
+ catch ( Exception ex )
156
+ {
157
+ tcs . TrySetException ( ex ) ;
152
158
}
153
159
154
- if ( _compressionStream != null )
160
+ if ( callback != null )
155
161
{
156
- _compressionStream . EndWrite ( asyncResult ) ;
162
+ // Offload callbacks to avoid stack dives on sync completions.
163
+ var ignored = Task . Run ( ( ) =>
164
+ {
165
+ try
166
+ {
167
+ callback ( tcs . Task ) ;
168
+ }
169
+ catch ( Exception )
170
+ {
171
+ // Suppress exceptions on background threads.
172
+ }
173
+ } ) ;
157
174
}
158
- else
175
+ }
176
+
177
+ public override void EndWrite ( IAsyncResult asyncResult )
178
+ {
179
+ if ( asyncResult == null )
159
180
{
160
- _bodyOriginalStream . EndWrite ( asyncResult ) ;
181
+ throw new ArgumentNullException ( nameof ( asyncResult ) ) ;
161
182
}
183
+
184
+ var task = ( Task ) asyncResult ;
185
+ task . GetAwaiter ( ) . GetResult ( ) ;
162
186
}
163
187
#endif
164
188
165
- public override Task WriteAsync ( byte [ ] buffer , int offset , int count , CancellationToken cancellationToken )
189
+ public override async Task WriteAsync ( byte [ ] buffer , int offset , int count , CancellationToken cancellationToken )
166
190
{
167
191
OnWrite ( ) ;
168
192
169
193
if ( _compressionStream != null )
170
194
{
171
- return _compressionStream . WriteAsync ( buffer , offset , count , cancellationToken ) ;
195
+ await _compressionStream . WriteAsync ( buffer , offset , count , cancellationToken ) ;
196
+ if ( _autoFlush )
197
+ {
198
+ await _compressionStream . FlushAsync ( cancellationToken ) ;
199
+ }
200
+ }
201
+ else
202
+ {
203
+ await _bodyOriginalStream . WriteAsync ( buffer , offset , count , cancellationToken ) ;
172
204
}
173
- return _bodyOriginalStream . WriteAsync ( buffer , offset , count , cancellationToken ) ;
174
205
}
175
206
176
207
private void OnWrite ( )
177
208
{
178
209
if ( ! _compressionChecked )
179
210
{
180
211
_compressionChecked = true ;
181
-
182
- if ( IsCompressable ( ) )
212
+ if ( _provider . ShouldCompressResponse ( _context ) )
183
213
{
184
- _response . Headers . Append ( HeaderNames . ContentEncoding , _compressionProvider . EncodingName ) ;
185
- _response . Headers . Remove ( HeaderNames . ContentMD5 ) ; // Reset the MD5 because the content changed.
186
- _response . Headers . Remove ( HeaderNames . ContentLength ) ;
187
-
188
- _compressionStream = _compressionProvider . CreateStream ( _bodyOriginalStream ) ;
214
+ var compressionProvider = ResolveCompressionProvider ( ) ;
215
+ if ( compressionProvider != null )
216
+ {
217
+ _context . Response . Headers . Append ( HeaderNames . ContentEncoding , compressionProvider . EncodingName ) ;
218
+ _context . Response . Headers . Remove ( HeaderNames . ContentMD5 ) ; // Reset the MD5 because the content changed.
219
+ _context . Response . Headers . Remove ( HeaderNames . ContentLength ) ;
220
+
221
+ _compressionStream = compressionProvider . CreateStream ( _bodyOriginalStream ) ;
222
+ }
189
223
}
190
224
}
191
225
}
192
226
193
- private bool IsCompressable ( )
227
+ private ICompressionProvider ResolveCompressionProvider ( )
194
228
{
195
- return ! _response . Headers . ContainsKey ( HeaderNames . ContentRange ) && // The response is not partial
196
- _provider . ShouldCompressResponse ( _response . HttpContext ) ;
229
+ if ( ! _providerCreated )
230
+ {
231
+ _providerCreated = true ;
232
+ _compressionProvider = _provider . GetCompressionProvider ( _context ) ;
233
+ }
234
+
235
+ return _compressionProvider ;
197
236
}
198
237
199
238
public void DisableRequestBuffering ( )
@@ -205,13 +244,16 @@ public void DisableRequestBuffering()
205
244
// For this to be effective it needs to be called before the first write.
206
245
public void DisableResponseBuffering ( )
207
246
{
208
- if ( ! _compressionProvider . SupportsFlush )
247
+ if ( ResolveCompressionProvider ( ) ? . SupportsFlush == false )
209
248
{
210
249
// Don't compress, some of the providers don't implement Flush (e.g. .NET 4.5.1 GZip/Deflate stream)
211
250
// which would block real-time responses like SignalR.
212
251
_compressionChecked = true ;
213
252
}
214
-
253
+ else
254
+ {
255
+ _autoFlush = true ;
256
+ }
215
257
_innerBufferFeature ? . DisableResponseBuffering ( ) ;
216
258
}
217
259
@@ -257,6 +299,11 @@ private async Task InnerSendFileAsync(string path, long offset, long? count, Can
257
299
{
258
300
fileStream . Seek ( offset , SeekOrigin . Begin ) ;
259
301
await StreamCopyOperation . CopyToAsync ( fileStream , _compressionStream , count , cancellation ) ;
302
+
303
+ if ( _autoFlush )
304
+ {
305
+ await _compressionStream . FlushAsync ( cancellation ) ;
306
+ }
260
307
}
261
308
}
262
309
}
0 commit comments