}
}
+ 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(
[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();
[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);
client.DefaultRequestHeaders.Add("Accept-Encoding", manualAcceptEncodingHeaderValues);
}
- Task<HttpResponseMessage> clientTask = client.GetAsync(url);
+ Task<HttpResponseMessage> clientTask = client.SendAsync(TestAsync, CreateRequest(HttpMethod.Get, url, UseVersion));
Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
await TaskTimeoutExtensions.WhenAllOrAnyFailed(new Task[] { clientTask, serverTask });
_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<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Version != _expectedVersion)
{
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; }
}
}
=> 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)
{
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);
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);
}
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
+ }
}
}
{
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);
}
}
-// 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;
{
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<HttpHeaderData> headers = new List<HttpHeaderData> { header };
async server => {
HttpRequestData request = await server.HandleRequestAsync();
ValidateRequest(request);
- Person per = JsonSerializer.Deserialize<Person>(request.Body, s_defaultSerializerOptions);
+ Person per = JsonSerializer.Deserialize<Person>(request.Body, JsonOptions.DefaultSerializerOptions);
per.Validate();
});
}
async server => {
HttpRequestData request = await server.HandleRequestAsync();
ValidateRequest(request);
- Person obj = JsonSerializer.Deserialize<Person>(request.Body, s_defaultSerializerOptions);
+ Person obj = JsonSerializer.Deserialize<Person>(request.Body, JsonOptions.DefaultSerializerOptions);
obj.Validate();
});
}
-// 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;
[Fact]
public async Task TestGetFromJsonAsyncTextPlainUtf16Async()
{
- const string json = @"{""Name"":""David"",""Age"":24}";
+ string json = Person.Create().Serialize();
await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync(
async (handler, uri) =>
{
-// 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;
namespace System.Net.Http.Json.Functional.Tests
{
- public class JsonContentTests
+ public abstract class JsonContentTestsBase
{
+ protected abstract Task<HttpResponseMessage> SendAsync(HttpClient client, HttpRequestMessage request);
private class Foo { }
private class Bar { }
var request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = content;
- await client.SendAsync(request);
+ await SendAsync(client, request);
}
},
async server => {
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 => {
}
[Fact]
- public static async Task ValidateUtf16IsTranscodedAsync()
+ public async Task ValidateUtf16IsTranscodedAsync()
{
await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync(
async (handler, uri) =>
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 => {
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());
content.Headers.ContentType = null;
request.Content = content;
- await client.SendAsync(request);
+ await SendAsync(client, request);
}
},
async server => {
});
}
}
+
+ public class JsonContentTests_Async : JsonContentTestsBase
+ {
+ protected override Task<HttpResponseMessage> SendAsync(HttpClient client, HttpRequestMessage request) => client.SendAsync(request);
+ }
}
--- /dev/null
+// 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<HttpResponseMessage> 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);
+ }
+ }
+}
<Compile Include="JsonContentTests.cs" />
<Compile Include="TestClasses.cs" />
</ItemGroup>
+ <ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'">
+ <Compile Include="JsonContentTests.netcoreapp.cs" />
+ </ItemGroup>
<ItemGroup>
<Compile Include="$(CommonTestPath)System\Net\Capability.Security.cs"
Link="Common\System\Net\Capability.Security.cs" />
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<EnsureDefaultOptions>
{
public override EnsureDefaultOptions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
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<System.IO.Stream> 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; }
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<System.IO.Stream> CreateContentReadStreamAsync() { throw null; }
protected override System.Threading.Tasks.Task<System.IO.Stream> CreateContentReadStreamAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
protected override void Dispose(bool disposing) { }
public sealed partial class ReadOnlyMemoryContent : System.Net.Http.HttpContent
{
public ReadOnlyMemoryContent(System.ReadOnlyMemory<byte> content) { }
+ protected override System.IO.Stream CreateContentReadStream(System.Threading.CancellationToken cancellationToken) { throw null; }
protected override System.Threading.Tasks.Task<System.IO.Stream> 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; }
{
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<System.IO.Stream> 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) { }
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);
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) { }
{
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; }
}
}
<Compile Include="$(CoreLibSharedDir)\System\Text\Utf8Span.Searching.cs"
Link="System\Text\Utf8Span.Searching.cs" />
</ItemGroup>
+ <ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'">
+ <Compile Include="System\Net\Http\Utf8StringContent.netcoreapp.cs" />
+ </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<Reference Include="System.Buffers" />
<Reference Include="System.Memory" />
namespace System.Net.Http
{
- public sealed class Utf8StringContent : HttpContent
+ public sealed partial class Utf8StringContent : HttpContent
{
private const string DefaultMediaType = "text/plain";
Task.FromResult<Stream>(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<byte> buffer = _content.AsMemoryBytes();
if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> array))
--- /dev/null
+// 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());
+ }
+}
<Compile Include="System\Utf8TestUtilities.cs" />
</ItemGroup>
<ItemGroup Condition="'$(IsPrerelease)' != 'false' and '$(TargetFramework)' == '$(NetCoreAppCurrent)'">
+ <Compile Include="System\Net\Http\Utf8StringContentTests.netcoreapp.cs" />
<Compile Include="System\MemoryTests.netcoreapp.cs" />
<Compile Include="System\ReflectionTests.netcoreapp.cs" />
<Compile Include="System\Utf8ExtensionsTests.netcoreapp.cs" />
}
[Fact]
- public static async Task Ctor_GetStream()
+ public static async Task Ctor_CopyToAsync_GetStream()
{
MemoryStream memoryStream = new MemoryStream();
--- /dev/null
+// 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());
+ }
+ }
+}