diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Decompression.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Decompression.cs index 28091e679d710..7ffddeadb45f0 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Decompression.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Decompression.cs @@ -64,6 +64,9 @@ public static IEnumerable DecompressedResponse_MethodSpecified_Decompr } } + private HttpRequestMessage CreateRequest(HttpMethod method, Uri uri, Version version) => + new HttpRequestMessage(method, uri) { Version = version }; + [Theory] [MemberData(nameof(DecompressedResponse_MethodSpecified_DecompressedContentReturned_MemberData))] public async Task DecompressedResponse_MethodSpecified_DecompressedContentReturned( @@ -162,11 +165,17 @@ await server.AcceptConnectionAsync(async connection => [Theory, MemberData(nameof(RemoteServersAndCompressionUris))] public async Task GetAsync_SetAutomaticDecompression_ContentDecompressed(Configuration.Http.RemoteServer remoteServer, Uri uri) { + // Sync API supported only up to HTTP/1.1 + if (!TestAsync && remoteServer.HttpVersion.Major >= 2) + { + return; + } + HttpClientHandler handler = CreateHttpClientHandler(); handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler)) { - using (HttpResponseMessage response = await client.GetAsync(uri)) + using (HttpResponseMessage response = await client.SendAsync(TestAsync, CreateRequest(HttpMethod.Get, uri, remoteServer.HttpVersion))) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); @@ -184,10 +193,16 @@ public async Task GetAsync_SetAutomaticDecompression_ContentDecompressed(Configu [Theory, MemberData(nameof(RemoteServersAndCompressionUris))] public async Task GetAsync_SetAutomaticDecompression_HeadersRemoved(Configuration.Http.RemoteServer remoteServer, Uri uri) { + // Sync API supported only up to HTTP/1.1 + if (!TestAsync && remoteServer.HttpVersion.Major >= 2) + { + return; + } + HttpClientHandler handler = CreateHttpClientHandler(); handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler)) - using (HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead)) + using (HttpResponseMessage response = await client.SendAsync(TestAsync, CreateRequest(HttpMethod.Get, uri, remoteServer.HttpVersion), HttpCompletionOption.ResponseHeadersRead)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -236,7 +251,7 @@ await LoopbackServer.CreateServerAsync(async (server, url) => client.DefaultRequestHeaders.Add("Accept-Encoding", manualAcceptEncodingHeaderValues); } - Task clientTask = client.GetAsync(url); + Task clientTask = client.SendAsync(TestAsync, CreateRequest(HttpMethod.Get, url, UseVersion)); Task> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(); await TaskTimeoutExtensions.WhenAllOrAnyFailed(new Task[] { clientTask, serverTask }); diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs index 79605ba336662..9d0e62ea4f443 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs @@ -109,6 +109,25 @@ public VersionCheckerHttpHandler(HttpMessageHandler innerHandler, Version expect _expectedVersion = expectedVersion; } +#if NETCOREAPP + protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request.Version != _expectedVersion) + { + throw new Exception($"Unexpected request version: expected {_expectedVersion}, saw {request.Version}"); + } + + HttpResponseMessage response = base.Send(request, cancellationToken); + + if (response.Version != _expectedVersion) + { + throw new Exception($"Unexpected response version: expected {_expectedVersion}, saw {response.Version}"); + } + + return response; + } +#endif + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (request.Version != _expectedVersion) diff --git a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs index 49131cd7c3e38..5dae7f86200cf 100644 --- a/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs +++ b/src/libraries/System.Net.Http.Json/ref/System.Net.Http.Json.netcoreapp.cs @@ -6,6 +6,7 @@ namespace System.Net.Http.Json { public sealed partial class JsonContent : System.Net.Http.HttpContent { + protected override void SerializeToStream(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { } protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { throw null; } } } 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 89623cca29180..3e28569417acc 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 @@ -51,7 +51,7 @@ public static JsonContent Create(object? inputValue, Type inputType, MediaTypeHe => new JsonContent(inputValue, inputType, mediaType, options); protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) - => SerializeToStreamAsyncCore(stream, CancellationToken.None); + => SerializeToStreamAsyncCore(stream, async: true, CancellationToken.None); protected override bool TryComputeLength(out long length) { @@ -59,7 +59,7 @@ protected override bool TryComputeLength(out long length) return false; } - private async Task SerializeToStreamAsyncCore(Stream targetStream, CancellationToken cancellationToken) + private async Task SerializeToStreamAsyncCore(Stream targetStream, bool async, CancellationToken cancellationToken) { Encoding? targetEncoding = GetEncoding(Headers.ContentType?.CharSet); @@ -70,15 +70,34 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, CancellationT Stream transcodingStream = Encoding.CreateTranscodingStream(targetStream, targetEncoding, Encoding.UTF8, leaveOpen: true); try { - await JsonSerializer.SerializeAsync(transcodingStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); + if (async) + { + await JsonSerializer.SerializeAsync(transcodingStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); + } + else + { + // Have to use Utf8JsonWriter because JsonSerializer doesn't support sync serialization into stream directly. + // ToDo: Remove Utf8JsonWriter usage after https://github.com/dotnet/runtime/issues/1574 + using var writer = new Utf8JsonWriter(transcodingStream); + JsonSerializer.Serialize(writer, Value, ObjectType, _jsonSerializerOptions); + } } finally { - // DisposeAsync will flush any partial write buffers. In practice our partial write + // 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. - await transcodingStream.DisposeAsync().ConfigureAwait(false); + if (async) + { + await transcodingStream.DisposeAsync().ConfigureAwait(false); + } + else + { + transcodingStream.Dispose(); + } } #else + Debug.Assert(async); + using (TranscodingWriteStream transcodingStream = new TranscodingWriteStream(targetStream, targetEncoding)) { await JsonSerializer.SerializeAsync(transcodingStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); @@ -91,7 +110,21 @@ private async Task SerializeToStreamAsyncCore(Stream targetStream, CancellationT } else { - await JsonSerializer.SerializeAsync(targetStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); + if (async) + { + await JsonSerializer.SerializeAsync(targetStream, Value, ObjectType, _jsonSerializerOptions, cancellationToken).ConfigureAwait(false); + } + else + { +#if NETCOREAPP + // Have to use Utf8JsonWriter because JsonSerializer doesn't support sync serialization into stream directly. + // ToDo: Remove Utf8JsonWriter usage after https://github.com/dotnet/runtime/issues/1574 + using var writer = new Utf8JsonWriter(targetStream); + JsonSerializer.Serialize(writer, Value, ObjectType, _jsonSerializerOptions); +#else + Debug.Fail("Synchronous serialization is only supported since .NET 5.0"); +#endif + } } } 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 306029ecee67c..7520cd74ec5ae 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 @@ -9,7 +9,10 @@ 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(); + protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) - => SerializeToStreamAsyncCore(stream, cancellationToken); + => SerializeToStreamAsyncCore(stream, async: true, cancellationToken); } } diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs index 0bc97ea26f92d..7ad9f4cd38995 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpClientJsonExtensionsTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Threading.Tasks; @@ -13,17 +13,10 @@ namespace System.Net.Http.Json.Functional.Tests { public class HttpClientJsonExtensionsTests { - private static readonly JsonSerializerOptions s_defaultSerializerOptions - = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - [Fact] public async Task TestGetFromJsonAsync() { - const string json = @"{""Name"":""David"",""Age"":24}"; + string json = Person.Create().Serialize(); HttpHeaderData header = new HttpHeaderData("Content-Type", "application/json"); List headers = new List { header }; @@ -89,7 +82,7 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( async server => { HttpRequestData request = await server.HandleRequestAsync(); ValidateRequest(request); - Person per = JsonSerializer.Deserialize(request.Body, s_defaultSerializerOptions); + Person per = JsonSerializer.Deserialize(request.Body, JsonOptions.DefaultSerializerOptions); per.Validate(); }); } @@ -121,7 +114,7 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( async server => { HttpRequestData request = await server.HandleRequestAsync(); ValidateRequest(request); - Person obj = JsonSerializer.Deserialize(request.Body, s_defaultSerializerOptions); + Person obj = JsonSerializer.Deserialize(request.Body, JsonOptions.DefaultSerializerOptions); obj.Validate(); }); } diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpContentJsonExtensionsTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpContentJsonExtensionsTests.cs index c0edaba0b94d4..99d5f1ba32ce8 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpContentJsonExtensionsTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/HttpContentJsonExtensionsTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; @@ -154,7 +154,7 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( [Fact] public async Task TestGetFromJsonAsyncTextPlainUtf16Async() { - const string json = @"{""Name"":""David"",""Age"":24}"; + string json = Person.Create().Serialize(); await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( async (handler, uri) => { diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs index 95fcb80e2cc7f..a23a7c803fbdd 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.cs @@ -1,8 +1,9 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Http.Headers; using System.Net.Test.Common; +using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -10,8 +11,9 @@ namespace System.Net.Http.Json.Functional.Tests { - public class JsonContentTests + public abstract class JsonContentTestsBase { + protected abstract Task SendAsync(HttpClient client, HttpRequestMessage request); private class Foo { } private class Bar { } @@ -80,7 +82,7 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( var request = new HttpRequestMessage(HttpMethod.Post, uri); request.Content = content; - await client.SendAsync(request); + await SendAsync(client, request); } }, async server => { @@ -112,7 +114,7 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( var request = new HttpRequestMessage(HttpMethod.Post, uri); MediaTypeHeaderValue mediaType = MediaTypeHeaderValue.Parse("foo/bar; charset=utf-8"); request.Content = JsonContent.Create(Person.Create(), mediaType: mediaType); - await client.SendAsync(request); + await SendAsync(client, request); } }, async server => { @@ -159,7 +161,7 @@ public void JsonContentThrowsOnIncompatibleTypeAsync() } [Fact] - public static async Task ValidateUtf16IsTranscodedAsync() + public async Task ValidateUtf16IsTranscodedAsync() { await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( async (handler, uri) => @@ -170,7 +172,7 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( MediaTypeHeaderValue mediaType = MediaTypeHeaderValue.Parse("application/json; charset=utf-16"); // Pass new options to avoid using the Default Web Options that use camelCase. request.Content = JsonContent.Create(Person.Create(), mediaType: mediaType, options: new JsonSerializerOptions()); - await client.SendAsync(request); + await SendAsync(client, request); } }, async server => { @@ -193,7 +195,7 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( EnsureDefaultOptions dummyObj = new EnsureDefaultOptions(); var request = new HttpRequestMessage(HttpMethod.Post, uri); request.Content = JsonContent.Create(dummyObj); - await client.SendAsync(request); + await SendAsync(client, request); } }, server => server.HandleRequestAsync()); @@ -213,7 +215,7 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( content.Headers.ContentType = null; request.Content = content; - await client.SendAsync(request); + await SendAsync(client, request); } }, async server => { @@ -222,4 +224,9 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync( }); } } + + public class JsonContentTests_Async : JsonContentTestsBase + { + protected override Task SendAsync(HttpClient client, HttpRequestMessage request) => client.SendAsync(request); + } } diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.netcoreapp.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.netcoreapp.cs new file mode 100644 index 0000000000000..dcbece5f6aec4 --- /dev/null +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContentTests.netcoreapp.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Net.Http.Headers; +using System.Net.Test.Common; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; + +namespace System.Net.Http.Json.Functional.Tests +{ + public class JsonContentTests_Sync : JsonContentTestsBase + { + protected override Task SendAsync(HttpClient client, HttpRequestMessage request) => Task.Run(() => client.Send(request)); + + [Fact] + public void JsonContent_CopyTo_Succeeds() + { + Person person = Person.Create(); + using JsonContent content = JsonContent.Create(person); + using MemoryStream stream = new MemoryStream(); + // HttpContent.CopyTo internally calls overriden JsonContent.SerializeToStream, which is the targeted method of this test. + content.CopyTo(stream, context: null, cancellationToken: default); + stream.Seek(0, SeekOrigin.Begin); + using StreamReader reader = new StreamReader(stream); + string json = reader.ReadToEnd(); + Assert.Equal(person.Serialize(JsonOptions.DefaultSerializerOptions), json); + } + } +} diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/System.Net.Http.Json.Functional.Tests.csproj b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/System.Net.Http.Json.Functional.Tests.csproj index 8d3e902f6a656..2653fcb9dc524 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/System.Net.Http.Json.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/System.Net.Http.Json.Functional.Tests.csproj @@ -8,6 +8,9 @@ + + + diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/TestClasses.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/TestClasses.cs index 2318193fb7fdc..2ca9ed419e607 100644 --- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/TestClasses.cs +++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/TestClasses.cs @@ -13,25 +13,37 @@ internal class Person public int Age { get; set; } public string Name { get; set; } public Person Parent { get; set; } + public string PlaceOfBirth { get; set; } public void Validate() { - Assert.Equal("David", Name); - Assert.Equal(24, Age); + Assert.Equal("R. Daneel Olivaw", Name); + Assert.Equal(19_230, Age); + Assert.Equal("Horní Dolní", PlaceOfBirth); Assert.Null(Parent); } public static Person Create() { - return new Person { Name = "David", Age = 24 }; + return new Person { Name = "R. Daneel Olivaw", Age = 19_230, PlaceOfBirth = "Horní Dolní"}; } - public string Serialize() + public string Serialize(JsonSerializerOptions options = null) { - return JsonSerializer.Serialize(this); + return JsonSerializer.Serialize(this, options); } } + internal static class JsonOptions + { + public static readonly JsonSerializerOptions DefaultSerializerOptions + = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + } + internal class EnsureDefaultOptionsConverter : JsonConverter { public override EnsureDefaultOptions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index e3d9247ba8797..f65651424593f 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -10,6 +10,7 @@ public partial class ByteArrayContent : System.Net.Http.HttpContent { public ByteArrayContent(byte[] content) { } public ByteArrayContent(byte[] content, int offset, int count) { } + protected override System.IO.Stream CreateContentReadStream(System.Threading.CancellationToken cancellationToken) { throw null; } protected override System.Threading.Tasks.Task CreateContentReadStreamAsync() { throw null; } protected override void SerializeToStream(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { } protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context) { throw null; } @@ -251,6 +252,7 @@ public MultipartContent() { } public MultipartContent(string subtype) { } public MultipartContent(string subtype, string boundary) { } public virtual void Add(System.Net.Http.HttpContent content) { } + protected override System.IO.Stream CreateContentReadStream(System.Threading.CancellationToken cancellationToken) { throw null; } protected override System.Threading.Tasks.Task CreateContentReadStreamAsync() { throw null; } protected override System.Threading.Tasks.Task CreateContentReadStreamAsync(System.Threading.CancellationToken cancellationToken) { throw null; } protected override void Dispose(bool disposing) { } @@ -273,6 +275,7 @@ public void Add(System.Net.Http.HttpContent content, string name, string fileNam public sealed partial class ReadOnlyMemoryContent : System.Net.Http.HttpContent { public ReadOnlyMemoryContent(System.ReadOnlyMemory content) { } + protected override System.IO.Stream CreateContentReadStream(System.Threading.CancellationToken cancellationToken) { throw null; } protected override System.Threading.Tasks.Task CreateContentReadStreamAsync() { throw null; } protected override void SerializeToStream(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { } protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context) { throw null; } @@ -312,6 +315,7 @@ public partial class StreamContent : System.Net.Http.HttpContent { public StreamContent(System.IO.Stream content) { } public StreamContent(System.IO.Stream content, int bufferSize) { } + protected override System.IO.Stream CreateContentReadStream(System.Threading.CancellationToken cancellationToken) { throw null; } protected override System.Threading.Tasks.Task CreateContentReadStreamAsync() { throw null; } protected override void Dispose(bool disposing) { } protected override void SerializeToStream(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/DecompressionHandler.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/DecompressionHandler.cs index 1380b9cb14bf1..2fcd31d58a508 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/DecompressionHandler.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/DecompressionHandler.cs @@ -121,6 +121,12 @@ public DecompressedContent(HttpContent originalContent) protected abstract Stream GetDecompressedStream(Stream originalStream); + protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) + { + using Stream decompressedStream = CreateContentReadStream(cancellationToken); + decompressedStream.CopyTo(stream); + } + protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) => SerializeToStreamAsync(stream, context, CancellationToken.None); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SyncHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SyncHttpHandlerTest.cs index 8eea9e950f084..7ecfee6a16232 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SyncHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SyncHttpHandlerTest.cs @@ -41,6 +41,12 @@ public SyncHttpHandlerTest_AutoRedirect(ITestOutputHelper output) : base(output) protected override bool TestAsync => false; } + public sealed class SyncHttpHandler_HttpClientHandler_Decompression_Tests : HttpClientHandler_Decompression_Test + { + public SyncHttpHandler_HttpClientHandler_Decompression_Tests(ITestOutputHelper output) : base(output) { } + protected override bool TestAsync => false; + } + public sealed class SyncHttpHandler_IdnaProtocolTests : IdnaProtocolTests { public SyncHttpHandler_IdnaProtocolTests(ITestOutputHelper output) : base(output) { } diff --git a/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.net5.0.cs b/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.net5.0.cs index fdccb75cb99b8..85f2598f8de55 100644 --- a/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.net5.0.cs +++ b/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.net5.0.cs @@ -19,6 +19,8 @@ namespace System.Net.Http { public sealed partial class Utf8StringContent : System.Net.Http.HttpContent { + protected override System.IO.Stream CreateContentReadStream(System.Threading.CancellationToken cancellationToken) { throw null; } + protected override void SerializeToStream(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { } protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { throw null; } } } diff --git a/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj b/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj index 3c93df383c00f..4496422a0ce30 100644 --- a/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj +++ b/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj @@ -107,6 +107,9 @@ + + + diff --git a/src/libraries/System.Utf8String.Experimental/src/System/Net/Http/Utf8StringContent.cs b/src/libraries/System.Utf8String.Experimental/src/System/Net/Http/Utf8StringContent.cs index a6e3452c6c570..3efd25fa24023 100644 --- a/src/libraries/System.Utf8String.Experimental/src/System/Net/Http/Utf8StringContent.cs +++ b/src/libraries/System.Utf8String.Experimental/src/System/Net/Http/Utf8StringContent.cs @@ -10,7 +10,7 @@ namespace System.Net.Http { - public sealed class Utf8StringContent : HttpContent + public sealed partial class Utf8StringContent : HttpContent { private const string DefaultMediaType = "text/plain"; @@ -42,7 +42,7 @@ protected override Task CreateContentReadStreamAsync() => Task.FromResult(new Utf8StringStream(_content)); #if NETSTANDARD2_0 - protected async override Task SerializeToStreamAsync(Stream stream, TransportContext? context) + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context) { ReadOnlyMemory buffer = _content.AsMemoryBytes(); if (MemoryMarshal.TryGetArray(buffer, out ArraySegment array)) diff --git a/src/libraries/System.Utf8String.Experimental/src/System/Net/Http/Utf8StringContent.netcoreapp.cs b/src/libraries/System.Utf8String.Experimental/src/System/Net/Http/Utf8StringContent.netcoreapp.cs new file mode 100644 index 0000000000000..d8a7b51f26712 --- /dev/null +++ b/src/libraries/System.Utf8String.Experimental/src/System/Net/Http/Utf8StringContent.netcoreapp.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Buffers; +using System.IO; +using System.Net.Http.Headers; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http +{ + public sealed partial class Utf8StringContent + { + protected override Stream CreateContentReadStream(CancellationToken cancellationToken) => + new Utf8StringStream(_content); + + protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) => + stream.Write(_content.AsBytes()); + } +} diff --git a/src/libraries/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj b/src/libraries/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj index 73e52762cdf01..1edb4b7d68e52 100644 --- a/src/libraries/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj +++ b/src/libraries/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj @@ -37,6 +37,7 @@ + diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Net/Http/Utf8StringContentTests.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Net/Http/Utf8StringContentTests.cs index eedac5826ef52..9ea0a4ccb6321 100644 --- a/src/libraries/System.Utf8String.Experimental/tests/System/Net/Http/Utf8StringContentTests.cs +++ b/src/libraries/System.Utf8String.Experimental/tests/System/Net/Http/Utf8StringContentTests.cs @@ -32,7 +32,7 @@ public static void Ctor_SetsContentTypeHeader(string mediaTypeForCtor, string ex } [Fact] - public static async Task Ctor_GetStream() + public static async Task Ctor_CopyToAsync_GetStream() { MemoryStream memoryStream = new MemoryStream(); diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Net/Http/Utf8StringContentTests.netcoreapp.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Net/Http/Utf8StringContentTests.netcoreapp.cs new file mode 100644 index 0000000000000..8df8b1e387952 --- /dev/null +++ b/src/libraries/System.Utf8String.Experimental/tests/System/Net/Http/Utf8StringContentTests.netcoreapp.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +using static System.Tests.Utf8TestUtilities; + +namespace System.Net.Http.Tests +{ + public partial class Utf8StringContentTests + { + [Fact] + public static void Ctor_CopyTo_GetStream() + { + var memoryStream = new MemoryStream(); + + new Utf8StringContent(u8("Hello")).CopyTo(memoryStream, default, default); + + Assert.Equal(u8("Hello").ToByteArray(), memoryStream.ToArray()); + } + + [Fact] + public static void Ctor_ReadAsStream() + { + var content = new Utf8StringContent(u8("Hello")); + Stream stream = content.ReadAsStream(); + + var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + Assert.Equal(u8("Hello").ToByteArray(), memoryStream.ToArray()); + } + } +}