From 9ac2f52b226917ac0ff88c61774464fba349d7a6 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 24 Apr 2019 09:57:53 -0700 Subject: [PATCH] Specify Content-Length for buffered responses (#9699) --- .../WebUtilities/src/FileBufferingWriteStream.cs | 8 +++----- .../test/FileBufferingWriteStreamTests.cs | 8 ++++++++ .../src/XmlDataContractSerializerOutputFormatter.cs | 3 ++- .../src/XmlSerializerOutputFormatter.cs | 1 + .../src/NewtonsoftJsonOutputFormatter.cs | 5 +++-- .../NewtonsoftJsonOutputFormatterTest.cs | 12 ++++++++++++ .../Mvc.FunctionalTests/XmlOutputFormatterTests.cs | 2 ++ 7 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs index ee03685312..fd9c993bad 100644 --- a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs +++ b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs @@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.WebUtilities public override bool CanWrite => true; /// - public override long Length => throw new NotSupportedException(); + public override long Length => PagedByteBuffer.Length + (FileStream?.Length ?? 0); /// public override long Position @@ -78,8 +78,6 @@ namespace Microsoft.AspNetCore.WebUtilities set => throw new NotSupportedException(); } - internal long BufferedLength => PagedByteBuffer.Length + (FileStream?.Length ?? 0); - internal PagedByteBuffer PagedByteBuffer { get; } internal FileStream FileStream { get; private set; } @@ -103,7 +101,7 @@ namespace Microsoft.AspNetCore.WebUtilities ThrowArgumentException(buffer, offset, count); ThrowIfDisposed(); - if (_bufferLimit.HasValue && _bufferLimit - BufferedLength < count) + if (_bufferLimit.HasValue && _bufferLimit - Length < count) { Dispose(); throw new IOException("Buffer limit exceeded."); @@ -136,7 +134,7 @@ namespace Microsoft.AspNetCore.WebUtilities ThrowArgumentException(buffer, offset, count); ThrowIfDisposed(); - if (_bufferLimit.HasValue && _bufferLimit - BufferedLength < count) + if (_bufferLimit.HasValue && _bufferLimit - Length < count) { Dispose(); throw new IOException("Buffer limit exceeded."); diff --git a/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs b/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs index 14f0c081b5..987c6388e5 100644 --- a/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs +++ b/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs @@ -31,6 +31,8 @@ namespace Microsoft.AspNetCore.WebUtilities bufferingStream.Write(input, 0, input.Length); // Assert + Assert.Equal(input.Length, bufferingStream.Length); + // We should have written content to memory var pagedByteBuffer = bufferingStream.PagedByteBuffer; Assert.Equal(input, ReadBufferedContent(pagedByteBuffer)); @@ -53,6 +55,8 @@ namespace Microsoft.AspNetCore.WebUtilities var pageBuffer = bufferingStream.PagedByteBuffer; var fileStream = bufferingStream.FileStream; + Assert.Equal(input.Length, bufferingStream.Length); + // File should have been created. Assert.Null(fileStream); @@ -238,6 +242,8 @@ namespace Microsoft.AspNetCore.WebUtilities Assert.NotNull(fileStream); var fileBytes = ReadFileContent(fileStream); + Assert.Equal(input.Length, bufferingStream.Length); + Assert.Equal(new byte[] { 1, 2, 3, 4, 5, }, fileBytes); Assert.Equal(new byte[] { 6, 7 }, ReadBufferedContent(pageBuffer)); } @@ -344,6 +350,7 @@ namespace Microsoft.AspNetCore.WebUtilities // Assert Assert.Equal(input, memoryStream.ToArray()); + Assert.Equal(0, bufferingStream.Length); } [Fact] @@ -360,6 +367,7 @@ namespace Microsoft.AspNetCore.WebUtilities // Assert Assert.Equal(input, memoryStream.ToArray()); + Assert.Equal(0, bufferingStream.Length); } public void Dispose() diff --git a/src/Mvc/Mvc.Formatters.Xml/src/XmlDataContractSerializerOutputFormatter.cs b/src/Mvc/Mvc.Formatters.Xml/src/XmlDataContractSerializerOutputFormatter.cs index b2a7d486b9..8f66c2842a 100644 --- a/src/Mvc/Mvc.Formatters.Xml/src/XmlDataContractSerializerOutputFormatter.cs +++ b/src/Mvc/Mvc.Formatters.Xml/src/XmlDataContractSerializerOutputFormatter.cs @@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters public XmlWriterSettings WriterSettings { get; } /// - /// Gets or sets the used to configure the + /// Gets or sets the used to configure the /// . /// public DataContractSerializerSettings SerializerSettings @@ -284,6 +284,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters if (fileBufferingWriteStream != null) { + response.ContentLength = fileBufferingWriteStream.Length; await fileBufferingWriteStream.DrainBufferAsync(response.Body); } } diff --git a/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerOutputFormatter.cs b/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerOutputFormatter.cs index fc845f7564..e6243fcb45 100644 --- a/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerOutputFormatter.cs +++ b/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerOutputFormatter.cs @@ -260,6 +260,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters if (fileBufferingWriteStream != null) { + response.ContentLength = fileBufferingWriteStream.Length; await fileBufferingWriteStream.DrainBufferAsync(response.Body); } } diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs index 80a1fc3b7d..b6889c1c2d 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs @@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// /// Called during serialization to create the .The formatter context - /// that is passed gives an ability to create serializer specific to the context. + /// that is passed gives an ability to create serializer specific to the context. /// /// The used during serialization and deserialization. protected virtual JsonSerializer CreateJsonSerializer() @@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// /// Called during serialization to create the .The formatter context - /// that is passed gives an ability to create serializer specific to the context. + /// that is passed gives an ability to create serializer specific to the context. /// /// A context object for . /// The used during serialization and deserialization. @@ -154,6 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters if (fileBufferingWriteStream != null) { + response.ContentLength = fileBufferingWriteStream.Length; await fileBufferingWriteStream.DrainBufferAsync(response.Body); } } diff --git a/src/Mvc/test/Mvc.FunctionalTests/NewtonsoftJsonOutputFormatterTest.cs b/src/Mvc/test/Mvc.FunctionalTests/NewtonsoftJsonOutputFormatterTest.cs index 58e77ca408..47696e446f 100644 --- a/src/Mvc/test/Mvc.FunctionalTests/NewtonsoftJsonOutputFormatterTest.cs +++ b/src/Mvc/test/Mvc.FunctionalTests/NewtonsoftJsonOutputFormatterTest.cs @@ -4,6 +4,7 @@ using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.NewtonsoftJson; +using FormatterWebSite.Controllers; using Newtonsoft.Json; using Xunit; @@ -41,5 +42,16 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var actualBody = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedBody, actualBody); } + + [Fact] + public async Task JsonOutputFormatter_SetsContentLength() + { + // Act + var response = await Client.GetAsync($"/JsonOutputFormatter/{nameof(JsonOutputFormatterController.SimpleModelResult)}"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.OK); + Assert.Equal(50, response.Content.Headers.ContentLength); + } } } diff --git a/src/Mvc/test/Mvc.FunctionalTests/XmlOutputFormatterTests.cs b/src/Mvc/test/Mvc.FunctionalTests/XmlOutputFormatterTests.cs index 35e406ac6a..f453e84741 100644 --- a/src/Mvc/test/Mvc.FunctionalTests/XmlOutputFormatterTests.cs +++ b/src/Mvc/test/Mvc.FunctionalTests/XmlOutputFormatterTests.cs @@ -41,6 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests "xmlns=\"http://schemas.datacontract.org/2004/07/FormatterWebSite\">" + "10", await response.Content.ReadAsStringAsync()); + Assert.Equal(167, response.Content.Headers.ContentLength); } [Fact] @@ -61,6 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests "10", await response.Content.ReadAsStringAsync()); + Assert.Equal(149, response.Content.Headers.ContentLength); } [ConditionalFact]