Skip to content

Commit

Permalink
Enforce HttpClient limits on GetFromJsonAsync (#79386)
Browse files Browse the repository at this point in the history
* Enforce HttpClient limits on GetFromJsonAsync

* Disable tests requiring Sockets on Browser

* Avoid LengthLimitReadStream overhead for DeleteFromJsonAsync

* Improve wrapper stream

* Remove content null check
  • Loading branch information
MihaZupan authored Jan 10, 2023
1 parent 9d059da commit 47779c9
Show file tree
Hide file tree
Showing 14 changed files with 395 additions and 300 deletions.
4 changes: 2 additions & 2 deletions src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ private async Task<List<byte[]>> ReadRequestHeaderBytesAsync()
public async Task WriteStringAsync(string s)
{
byte[] bytes = Encoding.ASCII.GetBytes(s);
await _stream.WriteAsync(bytes);
await _stream.WriteAsync(bytes, 0, bytes.Length);
}

public async Task SendResponseAsync(string response)
Expand All @@ -736,7 +736,7 @@ public async Task SendResponseAsync(string response)

public async Task SendResponseAsync(byte[] response)
{
await _stream.WriteAsync(response);
await _stream.WriteAsync(response, 0, response.Length);
}

public async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, string additionalHeaders = null, string content = null)
Expand Down
6 changes: 6 additions & 0 deletions src/libraries/System.Net.Http.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,10 @@
<data name="SerializeWrongType" xml:space="preserve">
<value>The specified type {0} must derive from the specific value's type {1}.</value>
</data>
<data name="net_http_request_timedout" xml:space="preserve">
<value>The request was canceled due to the configured HttpClient.Timeout of {0} seconds elapsing.</value>
</data>
<data name="net_http_content_buffersize_exceeded" xml:space="preserve">
<value>Cannot write more bytes to the buffer than the configured maximum buffer size: {0}.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ System.Net.Http.Json.JsonContent</PackageDescription>
</PropertyGroup>

<ItemGroup>
<Compile Include="System\Net\Http\Json\HttpClientJsonExtensions.cs" />
<Compile Include="System\Net\Http\Json\JsonHelpers.cs" />
<Compile Include="System\Net\Http\Json\HttpClientJsonExtensions.Delete.cs" />
<Compile Include="System\Net\Http\Json\HttpClientJsonExtensions.Get.cs" />
Expand All @@ -21,6 +22,7 @@ System.Net.Http.Json.JsonContent</PackageDescription>
<Compile Include="System\Net\Http\Json\HttpContentJsonExtensions.cs" />
<Compile Include="System\Net\Http\Json\JsonContent.cs" />
<Compile Include="System\Net\Http\Json\JsonContentOfT.cs" />
<Compile Include="System\Net\Http\Json\LengthLimitReadStream.cs" />
</ItemGroup>

<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,166 +15,59 @@ namespace System.Net.Http.Json
/// </summary>
public static partial class HttpClientJsonExtensions
{
private static readonly Func<HttpClient, Uri?, CancellationToken, Task<HttpResponseMessage>> s_getAsync =
static (client, uri, cancellation) => client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellation);

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
public static Task<object?> GetFromJsonAsync(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, Type type, JsonSerializerOptions? options, CancellationToken cancellationToken = default)
{
if (client is null)
{
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, type, options, cancellationToken);
}
public static Task<object?> GetFromJsonAsync(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, Type type, JsonSerializerOptions? options, CancellationToken cancellationToken = default) =>
GetFromJsonAsync(client, CreateUri(requestUri), type, options, cancellationToken);

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
public static Task<object?> GetFromJsonAsync(this HttpClient client, Uri? requestUri, Type type, JsonSerializerOptions? options, CancellationToken cancellationToken = default)
{
if (client is null)
{
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, type, options, cancellationToken);
}
public static Task<object?> GetFromJsonAsync(this HttpClient client, Uri? requestUri, Type type, JsonSerializerOptions? options, CancellationToken cancellationToken = default) =>
FromJsonAsyncCore(static (client, uri, cancellation) => client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellation), client, requestUri, type, options, cancellationToken);

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, JsonSerializerOptions? options, CancellationToken cancellationToken = default)
{
if (client is null)
{
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore<TValue>(taskResponse, options, cancellationToken);
}
public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, JsonSerializerOptions? options, CancellationToken cancellationToken = default) =>
GetFromJsonAsync<TValue>(client, CreateUri(requestUri), options, cancellationToken);

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, Uri? requestUri, JsonSerializerOptions? options, CancellationToken cancellationToken = default)
{
if (client is null)
{
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore<TValue>(taskResponse, options, cancellationToken);
}

public static Task<object?> GetFromJsonAsync(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default)
{
if (client is null)
{
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, type, context, cancellationToken);
}

public static Task<object?> GetFromJsonAsync(this HttpClient client, Uri? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default)
{
if (client is null)
{
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, type, context, cancellationToken);
}
public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, Uri? requestUri, JsonSerializerOptions? options, CancellationToken cancellationToken = default) =>
FromJsonAsyncCore<TValue>(s_getAsync, client, requestUri, options, cancellationToken);

public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default)
{
if (client is null)
{
throw new ArgumentNullException(nameof(client));
}
public static Task<object?> GetFromJsonAsync(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default) =>
GetFromJsonAsync(client, CreateUri(requestUri), type, context, cancellationToken);

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, jsonTypeInfo, cancellationToken);
}
public static Task<object?> GetFromJsonAsync(this HttpClient client, Uri? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default) =>
FromJsonAsyncCore(s_getAsync, client, requestUri, type, context, cancellationToken);

public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, Uri? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default)
{
if (client is null)
{
throw new ArgumentNullException(nameof(client));
}
public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default) =>
GetFromJsonAsync(client, CreateUri(requestUri), jsonTypeInfo, cancellationToken);

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return GetFromJsonAsyncCore<TValue>(taskResponse, jsonTypeInfo, cancellationToken);
}
public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, Uri? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default) =>
FromJsonAsyncCore(s_getAsync, client, requestUri, jsonTypeInfo, cancellationToken);

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
public static Task<object?> GetFromJsonAsync(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, Type type, CancellationToken cancellationToken = default)
=> client.GetFromJsonAsync(requestUri, type, options: null, cancellationToken);
public static Task<object?> GetFromJsonAsync(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, Type type, CancellationToken cancellationToken = default) =>
GetFromJsonAsync(client, requestUri, type, options: null, cancellationToken);

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
public static Task<object?> GetFromJsonAsync(this HttpClient client, Uri? requestUri, Type type, CancellationToken cancellationToken = default)
=> client.GetFromJsonAsync(requestUri, type, options: null, cancellationToken);
public static Task<object?> GetFromJsonAsync(this HttpClient client, Uri? requestUri, Type type, CancellationToken cancellationToken = default) =>
GetFromJsonAsync(client, requestUri, type, options: null, cancellationToken);

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, CancellationToken cancellationToken = default)
=> client.GetFromJsonAsync<TValue>(requestUri, options: null, cancellationToken);
public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, CancellationToken cancellationToken = default) =>
GetFromJsonAsync<TValue>(client, requestUri, options: null, cancellationToken);

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, Uri? requestUri, CancellationToken cancellationToken = default)
=> client.GetFromJsonAsync<TValue>(requestUri, options: null, cancellationToken);

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
private static async Task<object?> GetFromJsonAsyncCore(Task<HttpResponseMessage> taskResponse, Type type, JsonSerializerOptions? options, CancellationToken cancellationToken)
{
using (HttpResponseMessage response = await taskResponse.ConfigureAwait(false))
{
response.EnsureSuccessStatusCode();
// Nullable forgiving reason:
// GetAsync will usually return Content as not-null.
// If Content happens to be null, the extension will throw.
return await response.Content!.ReadFromJsonAsync(type, options, cancellationToken).ConfigureAwait(false);
}
}

[RequiresUnreferencedCode(HttpContentJsonExtensions.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(HttpContentJsonExtensions.SerializationDynamicCodeMessage)]
private static async Task<T?> GetFromJsonAsyncCore<T>(Task<HttpResponseMessage> taskResponse, JsonSerializerOptions? options, CancellationToken cancellationToken)
{
using (HttpResponseMessage response = await taskResponse.ConfigureAwait(false))
{
response.EnsureSuccessStatusCode();
// Nullable forgiving reason:
// GetAsync will usually return Content as not-null.
// If Content happens to be null, the extension will throw.
return await response.Content!.ReadFromJsonAsync<T>(options, cancellationToken).ConfigureAwait(false);
}
}

private static async Task<object?> GetFromJsonAsyncCore(Task<HttpResponseMessage> taskResponse, Type type, JsonSerializerContext context, CancellationToken cancellationToken)
{
using (HttpResponseMessage response = await taskResponse.ConfigureAwait(false))
{
response.EnsureSuccessStatusCode();
return await response.Content!.ReadFromJsonAsync(type, context, cancellationToken).ConfigureAwait(false);
}
}

private static async Task<T?> GetFromJsonAsyncCore<T>(Task<HttpResponseMessage> taskResponse, JsonTypeInfo<T> jsonTypeInfo, CancellationToken cancellationToken)
{
using (HttpResponseMessage response = await taskResponse.ConfigureAwait(false))
{
response.EnsureSuccessStatusCode();
return await response.Content!.ReadFromJsonAsync<T>(jsonTypeInfo, cancellationToken).ConfigureAwait(false);
}
}
public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, Uri? requestUri, CancellationToken cancellationToken = default) =>
GetFromJsonAsync<TValue>(client, requestUri, options: null, cancellationToken);
}
}
Loading

0 comments on commit 47779c9

Please sign in to comment.