[Fixes #3016] Disable response buffering in places where the content is already built/available

This commit is contained in:
Kiran Challa 2015-09-01 09:33:29 -07:00
parent 7a3f34089f
commit a0e0df87de
10 changed files with 183 additions and 9 deletions

View File

@ -4,7 +4,7 @@
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Actions;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Mvc.Internal;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.ActionResults
contentTypeHeader = contentTypeHeader.Copy();
contentTypeHeader.Encoding = Encoding.UTF8;
}
response.ContentType = contentTypeHeader?.ToString()
?? response.ContentType
?? DefaultContentType.ToString();
@ -56,8 +56,12 @@ namespace Microsoft.AspNet.Mvc.ActionResults
if (Content != null)
{
var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
bufferingFeature?.DisableResponseBuffering();
return response.WriteAsync(Content, contentTypeHeader?.Encoding ?? DefaultContentType.Encoding);
}
return TaskCache.CompletedTask;
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
@ -66,6 +67,9 @@ namespace Microsoft.AspNet.Mvc.ActionResults
/// <inheritdoc />
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);
}
}

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
@ -74,6 +75,9 @@ namespace Microsoft.AspNet.Mvc.ActionResults
using (FileStream)
{
var bufferingFeature = response.HttpContext.Features.Get<IHttpBufferingFeature>();
bufferingFeature?.DisableResponseBuffering();
await FileStream.CopyToAsync(outputStream, BufferSize, cancellation);
}
}

View File

@ -3,6 +3,7 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
@ -38,6 +39,9 @@ namespace Microsoft.AspNet.Mvc.Formatters
response.ContentType = context.SelectedContentType.ToString();
}
var bufferingFeature = context.HttpContext.Features.Get<IHttpBufferingFeature>();
bufferingFeature?.DisableResponseBuffering();
await valueAsStream.CopyToAsync(response.Body);
}
}

View File

@ -1,10 +1,12 @@
// 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 System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Actions;
using Microsoft.AspNet.Routing;
@ -79,6 +81,35 @@ namespace Microsoft.AspNet.Mvc.ActionResults
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
{
get

View File

@ -3,6 +3,7 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Actions;
using Microsoft.AspNet.Routing;
@ -71,5 +72,48 @@ namespace Microsoft.AspNet.Mvc.ActionResults
Assert.Equal(buffer, outStream.ToArray());
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;
}
}
}
}

View File

@ -3,8 +3,10 @@
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Actions;
using Microsoft.AspNet.Routing;
@ -118,5 +120,33 @@ namespace Microsoft.AspNet.Mvc.ActionResults
Assert.True(originalBytes.SequenceEqual(outBytes));
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);
}
}
}

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Actions;
using Microsoft.AspNet.Mvc.Formatters;
@ -119,7 +120,7 @@ namespace Microsoft.AspNet.Mvc.ActionResults
var httpResponse = new DefaultHttpContext().Response;
httpResponse.Body = new MemoryStream();
var actionContext = CreateMockActionContext(
outputFormatters: new IOutputFormatter[]
outputFormatters: new IOutputFormatter[]
{
new HttpNotAcceptableOutputFormatter(),
new JsonOutputFormatter()
@ -172,7 +173,7 @@ namespace Microsoft.AspNet.Mvc.ActionResults
var actionContext = CreateMockActionContext(
new[] { formatter.Object },
setupActionBindingContext: false);
// Set the content type property explicitly to a single value.
var result = new ObjectResult("someValue");
@ -383,7 +384,7 @@ namespace Microsoft.AspNet.Mvc.ActionResults
// Assert
Assert.Equal(mockCountingFormatter.Object, formatter);
mockCountingFormatter.Verify(v => v.CanWriteResult(context, null), Times.Once());
// CanWriteResult is invoked for the following cases:
// 1. For each accept header present
// 2. Request Content-Type
@ -695,7 +696,7 @@ namespace Microsoft.AspNet.Mvc.ActionResults
// Arrange
var expectedData = "Hello World!";
var objectResult = new ObjectResult(expectedData);
var outputFormatters = new IOutputFormatter[]
var outputFormatters = new IOutputFormatter[]
{
new HttpNotAcceptableOutputFormatter(),
new StringOutputFormatter(),
@ -727,7 +728,7 @@ namespace Microsoft.AspNet.Mvc.ActionResults
// Arrange
var objectResult = new ObjectResult(new Person() { Name = "John" });
objectResult.ContentTypes.Add(new MediaTypeHeaderValue("application/json"));
var outputFormatters = new IOutputFormatter[]
var outputFormatters = new IOutputFormatter[]
{
new HttpNotAcceptableOutputFormatter(),
new JsonOutputFormatter()
@ -760,7 +761,7 @@ namespace Microsoft.AspNet.Mvc.ActionResults
var objectResult = new ObjectResult(new Person() { Name = "John" });
objectResult.ContentTypes.Add(new MediaTypeHeaderValue("application/foo"));
objectResult.ContentTypes.Add(new MediaTypeHeaderValue("application/json"));
var outputFormatters = new IOutputFormatter[]
var outputFormatters = new IOutputFormatter[]
{
new HttpNotAcceptableOutputFormatter(),
new JsonOutputFormatter()
@ -891,6 +892,7 @@ namespace Microsoft.AspNet.Mvc.ActionResults
request.ContentType = requestContentType;
request.Body = new MemoryStream(contentBytes);
httpContext.Setup(o => o.Features).Returns(new FeatureCollection());
httpContext.Setup(o => o.Request).Returns(request);
httpContext.Setup(o => o.RequestServices).Returns(GetServiceProvider());
@ -911,7 +913,7 @@ namespace Microsoft.AspNet.Mvc.ActionResults
{
actionBindingContext = new ActionBindingContext { OutputFormatters = outputFormatters.ToList() };
}
httpContext.Setup(o => o.RequestServices.GetService(typeof(IActionBindingContextAccessor)))
.Returns(new ActionBindingContextAccessor() { ActionBindingContext = actionBindingContext });

View File

@ -3,6 +3,10 @@
using System;
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.Net.Http.Headers;
using Xunit;
@ -97,6 +101,29 @@ namespace Microsoft.AspNet.Mvc.Formatters
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
{
public int Id { get; set; }

View File

@ -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;
}
}
}