diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.cs index 4aa114c2ffb28a..992150a4f8f15a 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpClientJsonExtensions.cs @@ -112,7 +112,7 @@ public static partial class HttpClientJsonExtensions private static Uri? CreateUri(string? uri) => string.IsNullOrEmpty(uri) ? null : new Uri(uri, UriKind.RelativeOrAbsolute); - private static async Task GetHttpResponseStreamAsync( + private static ValueTask GetHttpResponseStreamAsync( HttpClient client, HttpResponseMessage response, bool usingResponseHeadersRead, @@ -126,16 +126,17 @@ private static async Task GetHttpResponseStreamAsync( LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit); } - Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync(response.Content, cancellationToken) - .ConfigureAwait(false); + ValueTask task = HttpContentJsonExtensions.GetContentStreamAsync(response.Content, cancellationToken); // If ResponseHeadersRead wasn't used, HttpClient will have already buffered the whole response upfront. // No need to check the limit again. - Stream readStream = usingResponseHeadersRead - ? new LengthLimitReadStream(contentStream, (int)client.MaxResponseContentBufferSize) - : contentStream; + return usingResponseHeadersRead ? GetLengthLimitReadStreamAsync(client, task) : task; + } - return readStream; + private static async ValueTask GetLengthLimitReadStreamAsync(HttpClient client, ValueTask task) + { + Stream contentStream = await task.ConfigureAwait(false); + return new LengthLimitReadStream(contentStream, (int)client.MaxResponseContentBufferSize); } } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.cs index c165a09a54d5f8..d283e104681902 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.cs @@ -141,17 +141,21 @@ public static partial class HttpContentJsonExtensions } } - internal static async ValueTask GetContentStreamAsync(HttpContent content, CancellationToken cancellationToken) + internal static ValueTask GetContentStreamAsync(HttpContent content, CancellationToken cancellationToken) { - Stream contentStream = await ReadHttpContentStreamAsync(content, cancellationToken).ConfigureAwait(false); + Task task = ReadHttpContentStreamAsync(content, cancellationToken); - // Wrap content stream into a transcoding stream that buffers the data transcoded from the sourceEncoding to utf-8. - if (JsonHelpers.GetEncoding(content) is Encoding sourceEncoding && sourceEncoding != Encoding.UTF8) - { - contentStream = GetTranscodingStream(contentStream, sourceEncoding); - } + return JsonHelpers.GetEncoding(content) is Encoding sourceEncoding && sourceEncoding != Encoding.UTF8 + ? GetTranscodingStreamAsync(task, sourceEncoding) + : new(task); + } - return contentStream; + private static async ValueTask GetTranscodingStreamAsync(Task task, Encoding sourceEncoding) + { + Stream contentStream = await task.ConfigureAwait(false); + + // Wrap content stream into a transcoding stream that buffers the data transcoded from the sourceEncoding to utf-8. + return GetTranscodingStream(contentStream, sourceEncoding); } } } diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs index 07a4b4c1c35cb2..539df6d3d43f22 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.cs @@ -94,7 +94,7 @@ public static JsonContent Create(object? inputValue, JsonTypeInfo jsonTypeInfo, } protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) - => SerializeToStreamAsyncCore(stream, async: true, CancellationToken.None); + => SerializeToStreamAsyncCore(stream, CancellationToken.None); protected override bool TryComputeLength(out long length) { @@ -102,67 +102,56 @@ protected override bool TryComputeLength(out long length) return false; } - private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, CancellationToken cancellationToken) + private Task SerializeToStreamAsyncCore(Stream targetStream, CancellationToken cancellationToken) { Encoding? targetEncoding = JsonHelpers.GetEncoding(this); + return targetEncoding != null && targetEncoding != Encoding.UTF8 + ? SerializeToStreamAsyncTranscoding(targetStream, async: true, targetEncoding, cancellationToken) + : JsonSerializer.SerializeAsync(targetStream, Value, _typeInfo, cancellationToken); + } + + private async Task SerializeToStreamAsyncTranscoding(Stream targetStream, bool async, Encoding targetEncoding, CancellationToken cancellationToken) + { // Wrap provided stream into a transcoding stream that buffers the data transcoded from utf-8 to the targetEncoding. - if (targetEncoding != null && targetEncoding != Encoding.UTF8) - { #if NETCOREAPP - Stream transcodingStream = Encoding.CreateTranscodingStream(targetStream, targetEncoding, Encoding.UTF8, leaveOpen: true); - try - { - if (async) - { - await JsonSerializer.SerializeAsync(transcodingStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false); - } - else - { - JsonSerializer.Serialize(transcodingStream, Value, _typeInfo); - } - } - finally + Stream transcodingStream = Encoding.CreateTranscodingStream(targetStream, targetEncoding, Encoding.UTF8, leaveOpen: true); + try + { + if (async) { - // Dispose/DisposeAsync will flush any partial write buffers. In practice our partial write - // buffers should be empty as we expect JsonSerializer to emit only well-formed UTF-8 data. - if (async) - { - await transcodingStream.DisposeAsync().ConfigureAwait(false); - } - else - { - transcodingStream.Dispose(); - } + await JsonSerializer.SerializeAsync(transcodingStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false); } -#else - Debug.Assert(async, "HttpContent synchronous serialization is only supported since .NET 5.0"); - - using (TranscodingWriteStream transcodingStream = new TranscodingWriteStream(targetStream, targetEncoding)) + else { - await JsonSerializer.SerializeAsync(transcodingStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false); - // The transcoding streams use Encoders and Decoders that have internal buffers. We need to flush these - // when there is no more data to be written. Stream.FlushAsync isn't suitable since it's - // acceptable to Flush a Stream (multiple times) prior to completion. - await transcodingStream.FinalWriteAsync(cancellationToken).ConfigureAwait(false); + JsonSerializer.Serialize(transcodingStream, Value, _typeInfo); } -#endif } - else + finally { + // Dispose/DisposeAsync will flush any partial write buffers. In practice our partial write + // buffers should be empty as we expect JsonSerializer to emit only well-formed UTF-8 data. if (async) { - await JsonSerializer.SerializeAsync(targetStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false); + await transcodingStream.DisposeAsync().ConfigureAwait(false); } else { -#if NETCOREAPP - JsonSerializer.Serialize(targetStream, Value, _typeInfo); -#else - Debug.Fail("HttpContent synchronous serialization is only supported since .NET 5.0"); -#endif + transcodingStream.Dispose(); } } +#else + Debug.Assert(async, "HttpContent synchronous serialization is only supported since .NET 5.0"); + + using (TranscodingWriteStream transcodingStream = new TranscodingWriteStream(targetStream, targetEncoding)) + { + await JsonSerializer.SerializeAsync(transcodingStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false); + // The transcoding streams use Encoders and Decoders that have internal buffers. We need to flush these + // when there is no more data to be written. Stream.FlushAsync isn't suitable since it's + // acceptable to Flush a Stream (multiple times) prior to completion. + await transcodingStream.FinalWriteAsync(cancellationToken).ConfigureAwait(false); + } +#endif } private static void EnsureTypeCompatibility(object? inputValue, Type inputType) diff --git a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs index 7520cd74ec5aeb..c3a6a28e5aa08d 100644 --- a/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs +++ b/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/JsonContent.netcoreapp.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; +using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -10,9 +12,18 @@ namespace System.Net.Http.Json public sealed partial class JsonContent { protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) - => SerializeToStreamAsyncCore(stream, async: false, cancellationToken).GetAwaiter().GetResult(); + { + if (JsonHelpers.GetEncoding(this) is Encoding targetEncoding && targetEncoding != Encoding.UTF8) + { + SerializeToStreamAsyncTranscoding(stream, async: false, targetEncoding, cancellationToken).GetAwaiter().GetResult(); + } + else + { + JsonSerializer.Serialize(stream, Value, _typeInfo); + } + } protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) - => SerializeToStreamAsyncCore(stream, async: true, cancellationToken); + => SerializeToStreamAsyncCore(stream, cancellationToken); } }