From 409721034bfc735c388b581d51dde27eb88db709 Mon Sep 17 00:00:00 2001 From: David Wedgbury Date: Fri, 10 Mar 2023 17:12:52 +0000 Subject: [PATCH 1/4] Write the HTTP response only when all output has been formatted --- .../Formatter/ODataOutputFormatterHelper.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs index 57d28ab60..e360e5b15 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs @@ -67,19 +67,20 @@ internal static async Task WriteToStreamAsync( ODataPath path = request.ODataFeature().Path; IEdmNavigationSource targetNavigationSource = path.GetNavigationSource(); - HttpResponse response = request.HttpContext.Response; + var responseBody = new MemoryStream(); + var responseHeaders = request.HttpContext.Response.Headers; // serialize a response string preferHeader = RequestPreferenceHelpers.GetRequestPreferHeader(requestHeaders); string annotationFilter = null; if (!string.IsNullOrEmpty(preferHeader)) { - ODataMessageWrapper messageWrapper = ODataMessageWrapperHelper.Create(response.Body, response.Headers); + ODataMessageWrapper messageWrapper = ODataMessageWrapperHelper.Create(responseBody, responseHeaders); messageWrapper.SetHeader(RequestPreferenceHelpers.PreferHeaderName, preferHeader); annotationFilter = messageWrapper.PreferHeader().AnnotationFilter; } - IODataResponseMessageAsync responseMessage = ODataMessageWrapperHelper.Create(new StreamWrapper(response.Body), response.Headers, request.GetRouteServices()); + IODataResponseMessageAsync responseMessage = ODataMessageWrapperHelper.Create(new StreamWrapper(responseBody), responseHeaders, request.GetRouteServices()); if (annotationFilter != null) { responseMessage.PreferenceAppliedHeader().AnnotationFilter = annotationFilter; @@ -150,6 +151,8 @@ internal static async Task WriteToStreamAsync( } await serializer.WriteObjectAsync(value, type, messageWriter, writeContext).ConfigureAwait(false); + await request.HttpContext.Response.Body.WriteAsync(responseBody.ToArray()); + //await responseBody.CopyToAsync(request.HttpContext.Response.Body); } } From 9a6ac6c386f424fa08ac0baa96734f7c878d8951 Mon Sep 17 00:00:00 2001 From: David Wedgbury Date: Fri, 10 Mar 2023 17:38:41 +0000 Subject: [PATCH 2/4] Remove commented code --- .../Formatter/ODataOutputFormatterHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs index e360e5b15..8f83cdbe8 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs @@ -152,7 +152,6 @@ internal static async Task WriteToStreamAsync( await serializer.WriteObjectAsync(value, type, messageWriter, writeContext).ConfigureAwait(false); await request.HttpContext.Response.Body.WriteAsync(responseBody.ToArray()); - //await responseBody.CopyToAsync(request.HttpContext.Response.Body); } } From 6d61c521804b5cfa765299206b2339ff58de5dbd Mon Sep 17 00:00:00 2001 From: David Wedgbury Date: Mon, 13 Mar 2023 17:41:27 +0000 Subject: [PATCH 3/4] Copy buffer stream to response stream more efficiently --- .../Formatter/ODataOutputFormatterHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs index 8f83cdbe8..ef31c2f22 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs @@ -151,7 +151,8 @@ internal static async Task WriteToStreamAsync( } await serializer.WriteObjectAsync(value, type, messageWriter, writeContext).ConfigureAwait(false); - await request.HttpContext.Response.Body.WriteAsync(responseBody.ToArray()); + responseBody.Position = 0; + await responseBody.CopyToAsync(response.Body); } } From b17ad173a7d247764cbd3951d924d0f65cc45895 Mon Sep 17 00:00:00 2001 From: David Wedgbury Date: Mon, 13 Mar 2023 17:43:37 +0000 Subject: [PATCH 4/4] Use pooled memory stream --- .../Formatter/ODataOutputFormatterHelper.cs | 12 ++++++++---- .../Microsoft.AspNetCore.OData.csproj | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs index ef31c2f22..d49aaee32 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs @@ -21,6 +21,7 @@ using Microsoft.AspNetCore.OData.Formatter.Value; using Microsoft.AspNetCore.OData.Query; using Microsoft.AspNetCore.OData.Routing; +using Microsoft.IO; using Microsoft.Net.Http.Headers; using Microsoft.OData; using Microsoft.OData.Edm; @@ -31,6 +32,8 @@ namespace Microsoft.AspNetCore.OData.Formatter { internal static class ODataOutputFormatterHelper { + private static readonly Lazy recycleableMemoryStreamManager = new Lazy(); + public static ODataSerializerContext BuildSerializerContext(HttpRequest request) { if (request == null) @@ -67,20 +70,20 @@ internal static async Task WriteToStreamAsync( ODataPath path = request.ODataFeature().Path; IEdmNavigationSource targetNavigationSource = path.GetNavigationSource(); - var responseBody = new MemoryStream(); - var responseHeaders = request.HttpContext.Response.Headers; + HttpResponse response = request.HttpContext.Response; + MemoryStream responseBody = recycleableMemoryStreamManager.Value.GetStream(nameof(ODataOutputFormatterHelper.WriteToStreamAsync)); // serialize a response string preferHeader = RequestPreferenceHelpers.GetRequestPreferHeader(requestHeaders); string annotationFilter = null; if (!string.IsNullOrEmpty(preferHeader)) { - ODataMessageWrapper messageWrapper = ODataMessageWrapperHelper.Create(responseBody, responseHeaders); + ODataMessageWrapper messageWrapper = ODataMessageWrapperHelper.Create(responseBody, response.Headers); messageWrapper.SetHeader(RequestPreferenceHelpers.PreferHeaderName, preferHeader); annotationFilter = messageWrapper.PreferHeader().AnnotationFilter; } - IODataResponseMessageAsync responseMessage = ODataMessageWrapperHelper.Create(new StreamWrapper(responseBody), responseHeaders, request.GetRouteServices()); + IODataResponseMessageAsync responseMessage = ODataMessageWrapperHelper.Create(new StreamWrapper(responseBody), response.Headers, request.GetRouteServices()); if (annotationFilter != null) { responseMessage.PreferenceAppliedHeader().AnnotationFilter = annotationFilter; @@ -132,6 +135,7 @@ internal static async Task WriteToStreamAsync( metadataLevel = ODataMediaTypes.GetMetadataLevel(contentType.MediaType.ToString(), parameters); } + using (responseBody) using (ODataMessageWriter messageWriter = new ODataMessageWriter(responseMessage, writerSettings, model)) { ODataSerializerContext writeContext = BuildSerializerContext(request); diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj index f6d8b063b..8bd9fda5b 100644 --- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj +++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj @@ -28,6 +28,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive +