Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Optimize JsonContent fast path
  • Loading branch information
pentp committed Oct 19, 2023
commit 64a0b121c22b0221264a842601479eb033872da5
Original file line number Diff line number Diff line change
Expand Up @@ -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<Stream> GetHttpResponseStreamAsync(
private static ValueTask<Stream> GetHttpResponseStreamAsync(
HttpClient client,
HttpResponseMessage response,
bool usingResponseHeadersRead,
Expand All @@ -126,16 +126,17 @@ private static async Task<Stream> GetHttpResponseStreamAsync(
LengthLimitReadStream.ThrowExceededBufferLimit(contentLengthLimit);
}

Stream contentStream = await HttpContentJsonExtensions.GetContentStreamAsync(response.Content, cancellationToken)
.ConfigureAwait(false);
ValueTask<Stream> 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<Stream> GetLengthLimitReadStreamAsync(HttpClient client, ValueTask<Stream> task)
{
Stream contentStream = await task.ConfigureAwait(false);
return new LengthLimitReadStream(contentStream, (int)client.MaxResponseContentBufferSize);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,21 @@ public static partial class HttpContentJsonExtensions
}
}

internal static async ValueTask<Stream> GetContentStreamAsync(HttpContent content, CancellationToken cancellationToken)
internal static ValueTask<Stream> GetContentStreamAsync(HttpContent content, CancellationToken cancellationToken)
{
Stream contentStream = await ReadHttpContentStreamAsync(content, cancellationToken).ConfigureAwait(false);
Task<Stream> 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<Stream> GetTranscodingStreamAsync(Task<Stream> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,75 +94,48 @@ 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)
{
length = 0;
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, targetEncoding, cancellationToken)
: JsonSerializer.SerializeAsync(targetStream, Value, _typeInfo, cancellationToken);
}

private async Task SerializeToStreamAsyncTranscoding(Stream targetStream, 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
{
// 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();
}
}
#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
Stream transcodingStream = Encoding.CreateTranscodingStream(targetStream, targetEncoding, Encoding.UTF8, leaveOpen: true);
try
{
await JsonSerializer.SerializeAsync(transcodingStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false);
}
else
finally
{
if (async)
{
await JsonSerializer.SerializeAsync(targetStream, Value, _typeInfo, cancellationToken).ConfigureAwait(false);
}
else
{
#if NETCOREAPP
JsonSerializer.Serialize(targetStream, Value, _typeInfo);
// 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.
await transcodingStream.DisposeAsync().ConfigureAwait(false);
}
#else
Debug.Fail("HttpContent synchronous serialization is only supported since .NET 5.0");
#endif
}
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -10,9 +12,23 @@ 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();
{
Encoding? targetEncoding = JsonHelpers.GetEncoding(this);
if (targetEncoding != null && targetEncoding != Encoding.UTF8)
{
// Wrap provided stream into a transcoding stream that buffers the data transcoded from utf-8 to the targetEncoding.
using Stream transcodingStream = Encoding.CreateTranscodingStream(stream, targetEncoding, Encoding.UTF8, leaveOpen: true);
JsonSerializer.Serialize(transcodingStream, Value, _typeInfo);
// Dispose 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.
}
else
{
JsonSerializer.Serialize(stream, Value, _typeInfo);
}
}

protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken)
=> SerializeToStreamAsyncCore(stream, async: true, cancellationToken);
=> SerializeToStreamAsyncCore(stream, cancellationToken);
}
}