[Fixes #3016] Disable response buffering in places where the content is already built/available
This commit is contained in:
parent
7a3f34089f
commit
a0e0df87de
|
|
@ -4,7 +4,7 @@
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Mvc.Actions;
|
using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.AspNet.Mvc.Internal;
|
using Microsoft.AspNet.Mvc.Internal;
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
@ -56,8 +56,12 @@ namespace Microsoft.AspNet.Mvc.ActionResults
|
||||||
|
|
||||||
if (Content != null)
|
if (Content != null)
|
||||||
{
|
{
|
||||||
|
var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
|
||||||
|
bufferingFeature?.DisableResponseBuffering();
|
||||||
|
|
||||||
return response.WriteAsync(Content, contentTypeHeader?.Encoding ?? DefaultContentType.Encoding);
|
return response.WriteAsync(Content, contentTypeHeader?.Encoding ?? DefaultContentType.Encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TaskCache.CompletedTask;
|
return TaskCache.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
|
@ -66,6 +67,9 @@ namespace Microsoft.AspNet.Mvc.ActionResults
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)
|
protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)
|
||||||
{
|
{
|
||||||
|
var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
|
||||||
|
bufferingFeature?.DisableResponseBuffering();
|
||||||
|
|
||||||
return response.Body.WriteAsync(FileContents, 0, FileContents.Length, cancellation);
|
return response.Body.WriteAsync(FileContents, 0, FileContents.Length, cancellation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
|
@ -74,6 +75,9 @@ namespace Microsoft.AspNet.Mvc.ActionResults
|
||||||
|
|
||||||
using (FileStream)
|
using (FileStream)
|
||||||
{
|
{
|
||||||
|
var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
|
||||||
|
bufferingFeature?.DisableResponseBuffering();
|
||||||
|
|
||||||
await FileStream.CopyToAsync(outputStream, BufferSize, cancellation);
|
await FileStream.CopyToAsync(outputStream, BufferSize, cancellation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
|
@ -38,6 +39,9 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
response.ContentType = context.SelectedContentType.ToString();
|
response.ContentType = context.SelectedContentType.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bufferingFeature = context.HttpContext.Features.Get<IHttpBufferingFeature>();
|
||||||
|
bufferingFeature?.DisableResponseBuffering();
|
||||||
|
|
||||||
await valueAsStream.CopyToAsync(response.Body);
|
await valueAsStream.CopyToAsync(response.Body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Actions;
|
using Microsoft.AspNet.Mvc.Actions;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
|
|
@ -79,6 +81,35 @@ namespace Microsoft.AspNet.Mvc.ActionResults
|
||||||
Assert.Equal("text/plain; charset=utf-7", httpContext.Response.ContentType);
|
Assert.Equal("text/plain; charset=utf-7", httpContext.Response.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ContentResult_DisablesResponseBuffering_IfBufferingFeatureAvailable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var data = "Test Content";
|
||||||
|
var contentResult = new ContentResult
|
||||||
|
{
|
||||||
|
Content = data,
|
||||||
|
ContentType = new MediaTypeHeaderValue("text/plain")
|
||||||
|
{
|
||||||
|
Encoding = Encoding.ASCII
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Features.Set<IHttpBufferingFeature>(new TestBufferingFeature());
|
||||||
|
var memoryStream = new MemoryStream();
|
||||||
|
httpContext.Response.Body = memoryStream;
|
||||||
|
var actionContext = GetActionContext(httpContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await contentResult.ExecuteResultAsync(actionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("text/plain; charset=us-ascii", httpContext.Response.ContentType);
|
||||||
|
Assert.Equal(Encoding.ASCII.GetString(memoryStream.ToArray()), data);
|
||||||
|
var bufferingFeature = (TestBufferingFeature)httpContext.Features.Get<IHttpBufferingFeature>();
|
||||||
|
Assert.True(bufferingFeature.DisableResponseBufferingInvoked);
|
||||||
|
}
|
||||||
|
|
||||||
public static TheoryData<MediaTypeHeaderValue, string, string, string, byte[]> ContentResultContentTypeData
|
public static TheoryData<MediaTypeHeaderValue, string, string, string, byte[]> ContentResultContentTypeData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Actions;
|
using Microsoft.AspNet.Mvc.Actions;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
|
|
@ -71,5 +72,48 @@ namespace Microsoft.AspNet.Mvc.ActionResults
|
||||||
Assert.Equal(buffer, outStream.ToArray());
|
Assert.Equal(buffer, outStream.ToArray());
|
||||||
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
|
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DisablesResponseBuffering_IfBufferingFeatureAvailable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedContentType = "text/foo; charset=us-ascii";
|
||||||
|
var buffer = new byte[] { 1, 2, 3, 4, 5 };
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var bufferingFeature = new TestBufferingFeature();
|
||||||
|
httpContext.Features.Set<IHttpBufferingFeature>(bufferingFeature);
|
||||||
|
var outStream = new MemoryStream();
|
||||||
|
httpContext.Response.Body = outStream;
|
||||||
|
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
var result = new FileContentResult(buffer, MediaTypeHeaderValue.Parse(expectedContentType));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(buffer, outStream.ToArray());
|
||||||
|
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
|
||||||
|
Assert.True(bufferingFeature.DisableResponseBufferingInvoked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestBufferingFeature : IHttpBufferingFeature
|
||||||
|
{
|
||||||
|
public bool DisableResponseBufferingInvoked { get; private set; }
|
||||||
|
|
||||||
|
public bool DisableRequestBufferingInvoked { get; private set; }
|
||||||
|
|
||||||
|
public void DisableRequestBuffering()
|
||||||
|
{
|
||||||
|
DisableRequestBufferingInvoked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisableResponseBuffering()
|
||||||
|
{
|
||||||
|
DisableResponseBufferingInvoked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,8 +3,10 @@
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Actions;
|
using Microsoft.AspNet.Mvc.Actions;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
|
|
@ -118,5 +120,33 @@ namespace Microsoft.AspNet.Mvc.ActionResults
|
||||||
Assert.True(originalBytes.SequenceEqual(outBytes));
|
Assert.True(originalBytes.SequenceEqual(outBytes));
|
||||||
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
|
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DisablesResponseBuffering_IfBufferingFeatureAvailable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedContentType = "text/foo; charset=us-ascii";
|
||||||
|
var expected = Encoding.ASCII.GetBytes("Test data");
|
||||||
|
|
||||||
|
var originalStream = new MemoryStream(expected);
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var bufferingFeature = new TestBufferingFeature();
|
||||||
|
httpContext.Features.Set<IHttpBufferingFeature>(bufferingFeature);
|
||||||
|
var outStream = new MemoryStream();
|
||||||
|
httpContext.Response.Body = outStream;
|
||||||
|
|
||||||
|
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
var result = new FileStreamResult(originalStream, MediaTypeHeaderValue.Parse(expectedContentType));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(actionContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var outBytes = outStream.ToArray();
|
||||||
|
Assert.Equal(expected, outBytes);
|
||||||
|
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
|
||||||
|
Assert.True(bufferingFeature.DisableResponseBufferingInvoked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Actions;
|
using Microsoft.AspNet.Mvc.Actions;
|
||||||
using Microsoft.AspNet.Mvc.Formatters;
|
using Microsoft.AspNet.Mvc.Formatters;
|
||||||
|
|
@ -891,6 +892,7 @@ namespace Microsoft.AspNet.Mvc.ActionResults
|
||||||
request.ContentType = requestContentType;
|
request.ContentType = requestContentType;
|
||||||
request.Body = new MemoryStream(contentBytes);
|
request.Body = new MemoryStream(contentBytes);
|
||||||
|
|
||||||
|
httpContext.Setup(o => o.Features).Returns(new FeatureCollection());
|
||||||
httpContext.Setup(o => o.Request).Returns(request);
|
httpContext.Setup(o => o.Request).Returns(request);
|
||||||
httpContext.Setup(o => o.RequestServices).Returns(GetServiceProvider());
|
httpContext.Setup(o => o.RequestServices).Returns(GetServiceProvider());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.ActionResults;
|
using Microsoft.AspNet.Mvc.ActionResults;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -97,6 +101,29 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
||||||
Assert.Same(contentType, context.SelectedContentType);
|
Assert.Same(contentType, context.SelectedContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DisablesResponseBuffering_IfBufferingFeatureAvailable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = Encoding.UTF8.GetBytes("Test data");
|
||||||
|
var formatter = new StreamOutputFormatter();
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
httpContext.Response.Body = ms;
|
||||||
|
var bufferingFeature = new TestBufferingFeature();
|
||||||
|
httpContext.Features.Set<IHttpBufferingFeature>(bufferingFeature);
|
||||||
|
var context = new OutputFormatterContext();
|
||||||
|
context.Object = new MemoryStream(expected);
|
||||||
|
context.HttpContext = httpContext;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await formatter.WriteAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, ms.ToArray());
|
||||||
|
Assert.True(bufferingFeature.DisableResponseBufferingInvoked);
|
||||||
|
}
|
||||||
|
|
||||||
private class SimplePOCO
|
private class SimplePOCO
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Http.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
internal class TestBufferingFeature : IHttpBufferingFeature
|
||||||
|
{
|
||||||
|
public bool DisableResponseBufferingInvoked { get; private set; }
|
||||||
|
|
||||||
|
public bool DisableRequestBufferingInvoked { get; private set; }
|
||||||
|
|
||||||
|
public void DisableRequestBuffering()
|
||||||
|
{
|
||||||
|
DisableRequestBufferingInvoked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisableResponseBuffering()
|
||||||
|
{
|
||||||
|
DisableResponseBufferingInvoked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue