Skip to content
This repository was archived by the owner on Nov 22, 2018. It is now read-only.

Commit bd2884d

Browse files
committed
Add shim for IHttpSendFileFeature
1 parent dc4212c commit bd2884d

File tree

6 files changed

+103
-22
lines changed

6 files changed

+103
-22
lines changed

src/Microsoft.AspNetCore.ResponseCaching/Internal/DefaultResponseCacheEntrySerializer.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Globalization;
65
using System.IO;
76
using Microsoft.AspNetCore.Http;
87

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Http.Features;
7+
8+
namespace Microsoft.AspNetCore.ResponseCaching.Internal
9+
{
10+
internal class SendFileFeatureWrapper : IHttpSendFileFeature
11+
{
12+
private readonly IHttpSendFileFeature _originalSendFileFeature;
13+
private readonly ResponseCacheStream _responseCacheStream;
14+
15+
public SendFileFeatureWrapper(IHttpSendFileFeature originalSendFileFeature, ResponseCacheStream responseCacheStream)
16+
{
17+
_originalSendFileFeature = originalSendFileFeature;
18+
_responseCacheStream = responseCacheStream;
19+
}
20+
21+
// Flush and disable the buffer if anyone tries to call the SendFile feature.
22+
public async Task SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
23+
{
24+
_responseCacheStream.DisableBuffering();
25+
await _originalSendFileFeature.SendFileAsync(path, offset, length, cancellation);
26+
}
27+
}
28+
}

src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingContext.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public ResponseCachingContext(HttpContext httpContext, IResponseCache cache)
3434
{
3535
}
3636

37+
// Internal for testing
3738
internal ResponseCachingContext(HttpContext httpContext, IResponseCache cache, ISystemClock clock)
3839
{
3940
if (cache == null)
@@ -81,6 +82,8 @@ internal bool CacheResponse
8182

8283
private ResponseCacheStream ResponseCacheStream { get; set; }
8384

85+
private IHttpSendFileFeature OriginalSendFileFeature { get; set; }
86+
8487
private RequestHeaders RequestHeaders
8588
{
8689
get
@@ -261,12 +264,6 @@ internal bool ResponseIsCacheable()
261264
return false;
262265
}
263266

264-
// Do not cache responses that may bypass the response body
265-
if (HttpContext.Features.Get<IHttpSendFileFeature>() != null)
266-
{
267-
return false;
268-
}
269-
270267
// TODO: public MAY override the cacheability checks for private and status codes
271268

272269
// Check private
@@ -489,14 +486,27 @@ internal void OnResponseStarting()
489486
internal void HookResponseStream()
490487
{
491488
// TODO: Consider caching large responses on disk and serving them from there.
489+
490+
// Shim response stream
492491
OriginalResponseStream = HttpContext.Response.Body;
493492
ResponseCacheStream = new ResponseCacheStream(OriginalResponseStream);
494493
HttpContext.Response.Body = ResponseCacheStream;
494+
495+
// Shim IHttpSendFileFeature
496+
OriginalSendFileFeature = HttpContext.Features.Get<IHttpSendFileFeature>();
497+
if (OriginalSendFileFeature != null)
498+
{
499+
HttpContext.Features.Set<IHttpSendFileFeature>(new SendFileFeatureWrapper(OriginalSendFileFeature, ResponseCacheStream));
500+
}
495501
}
496502

497503
internal void UnhookResponseStream()
498504
{
505+
// Unshim response stream
499506
HttpContext.Response.Body = OriginalResponseStream;
507+
508+
// Unshim IHttpSendFileFeature
509+
HttpContext.Features.Set(OriginalSendFileFeature);
500510
}
501511

502512
private enum ResponseType

src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingMiddleware.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Threading.Tasks;
66
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Http.Features;
78

89
namespace Microsoft.AspNetCore.ResponseCaching
910
{
@@ -67,6 +68,7 @@ public async Task Invoke(HttpContext context)
6768
finally
6869
{
6970
cachingContext.UnhookResponseStream();
71+
7072
}
7173
}
7274
else

test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCachingContextTests.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -189,20 +189,6 @@ public void ResponseIsCacheable_VaryByStar_NotAllowed()
189189
Assert.False(context.ResponseIsCacheable());
190190
}
191191

192-
[Fact]
193-
public void ResponseIsCacheable_IHttpSendFileFeature_NotAllowed()
194-
{
195-
var httpContext = new DefaultHttpContext();
196-
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
197-
{
198-
Public = true
199-
};
200-
httpContext.Features.Set<IHttpSendFileFeature>(new TestHttpSendFileFeature());
201-
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
202-
203-
Assert.False(context.ResponseIsCacheable());
204-
}
205-
206192
[Fact]
207193
public void ResponseIsCacheable_Private_NotAllowed()
208194
{

test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCachingTests.cs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
57
using Microsoft.AspNetCore.Builder;
68
using Microsoft.AspNetCore.Hosting;
79
using Microsoft.AspNetCore.Http;
10+
using Microsoft.AspNetCore.Http.Features;
811
using Microsoft.AspNetCore.TestHost;
912
using Microsoft.Extensions.DependencyInjection;
1013
using Microsoft.Net.Http.Headers;
@@ -259,7 +262,51 @@ public async void ServesCachedContentWithoutSetCookie()
259262
}
260263
}
261264

262-
private static IWebHostBuilder CreateBuilderWithResponseCaching(RequestDelegate requestDelegate)
265+
[Fact]
266+
public async void ServesFreshContentIfIHttpSendFileFeatureUsed()
267+
{
268+
var builder = CreateBuilderWithResponseCaching(
269+
app =>
270+
{
271+
app.Use(async (context, next) =>
272+
{
273+
context.Features.Set<IHttpSendFileFeature>(new DummySendFileFeature());
274+
await next.Invoke();
275+
});
276+
},
277+
async (context) =>
278+
{
279+
var uniqueId = Guid.NewGuid().ToString();
280+
var headers = context.Response.GetTypedHeaders();
281+
headers.CacheControl = new CacheControlHeaderValue()
282+
{
283+
Public = true,
284+
MaxAge = TimeSpan.FromSeconds(10)
285+
};
286+
headers.Date = DateTimeOffset.UtcNow;
287+
headers.Headers["X-Value"] = uniqueId;
288+
await context.Features.Get<IHttpSendFileFeature>().SendFileAsync("dummy", 0, 0, CancellationToken.None);
289+
await context.Response.WriteAsync(uniqueId);
290+
});
291+
292+
using (var server = new TestServer(builder))
293+
{
294+
var client = server.CreateClient();
295+
var initialResponse = await client.GetAsync("");
296+
var subsequentResponse = await client.GetAsync("");
297+
298+
initialResponse.EnsureSuccessStatusCode();
299+
subsequentResponse.EnsureSuccessStatusCode();
300+
301+
Assert.False(subsequentResponse.Headers.Contains(HeaderNames.Age));
302+
Assert.NotEqual(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
303+
}
304+
}
305+
306+
private static IWebHostBuilder CreateBuilderWithResponseCaching(RequestDelegate requestDelegate) =>
307+
CreateBuilderWithResponseCaching(app => { }, requestDelegate);
308+
309+
private static IWebHostBuilder CreateBuilderWithResponseCaching(Action<IApplicationBuilder> configureDelegate, RequestDelegate requestDelegate)
263310
{
264311
return new WebHostBuilder()
265312
.ConfigureServices(services =>
@@ -268,9 +315,18 @@ private static IWebHostBuilder CreateBuilderWithResponseCaching(RequestDelegate
268315
})
269316
.Configure(app =>
270317
{
318+
configureDelegate(app);
271319
app.UseResponseCaching();
272320
app.Run(requestDelegate);
273321
});
274322
}
323+
324+
private class DummySendFileFeature : IHttpSendFileFeature
325+
{
326+
public Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellation)
327+
{
328+
return Task.FromResult(0);
329+
}
330+
}
275331
}
276332
}

0 commit comments

Comments
 (0)