Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[browser] [wasm] Refactor Request Streaming to use HttpContent.CopyToAsync #91699

Merged
merged 15 commits into from
Sep 21, 2023
Merged
100 changes: 93 additions & 7 deletions src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ public async Task BrowserHttpHandler_Streaming()

int readOffset = 0;
req.Content = new StreamContent(new DelegateStream(
canReadFunc: () => true,
readFunc: (buffer, offset, count) => throw new FormatException(),
readAsyncFunc: async (buffer, offset, count, cancellationToken) =>
{
await Task.Delay(1);
Expand Down Expand Up @@ -295,8 +297,12 @@ public async Task BrowserHttpHandler_StreamingRequest()
req.Options.Set(WebAssemblyEnableStreamingRequestKey, true);

int size = 1500 * 1024 * 1024;
int multipartOverhead = 125 + 4 /* "test" */;
int remaining = size;
req.Content = new StreamContent(new DelegateStream(
var content = new MultipartFormDataContent();
content.Add(new StreamContent(new DelegateStream(
canReadFunc: () => true,
readFunc: (buffer, offset, count) => throw new FormatException(),
readAsyncFunc: (buffer, offset, count, cancellationToken) =>
{
if (remaining > 0)
Expand All @@ -307,15 +313,16 @@ public async Task BrowserHttpHandler_StreamingRequest()
return Task.FromResult(send);
}
return Task.FromResult(0);
}));
})), "test");
req.Content = content;

req.Content.Headers.Add("Content-MD5-Skip", "browser");

using (HttpClient client = CreateHttpClientForRemoteServer(Configuration.Http.RemoteHttp2Server))
using (HttpResponseMessage response = await client.SendAsync(req))
{
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(size.ToString(), Assert.Single(response.Headers.GetValues("X-HttpRequest-Body-Length")));
Assert.Equal((size + multipartOverhead).ToString(), Assert.Single(response.Headers.GetValues("X-HttpRequest-Body-Length")));
// Streaming requests can't set Content-Length
Assert.False(response.Headers.Contains("X-HttpRequest-Headers-ContentLength"));
}
Expand All @@ -335,22 +342,101 @@ public async Task BrowserHttpHandler_StreamingRequest_ThrowFromContentCopy_Reque
req.Options.Set(WebAssemblyEnableStreamingRequestKey, true);

Exception error = new FormatException();
var content = new StreamContent(new DelegateStream(
req.Content = new StreamContent(new DelegateStream(
canSeekFunc: () => true,
lengthFunc: () => 12345678,
positionGetFunc: () => 0,
canReadFunc: () => true,
readFunc: (buffer, offset, count) => throw error,
readFunc: (buffer, offset, count) => throw new FormatException(),
readAsyncFunc: (buffer, offset, count, cancellationToken) => syncFailure ? throw error : Task.Delay(1).ContinueWith<int>(_ => throw error)));

req.Content = content;

using (HttpClient client = CreateHttpClientForRemoteServer(Configuration.Http.RemoteHttp2Server))
{
Assert.Same(error, await Assert.ThrowsAsync<FormatException>(() => client.SendAsync(req)));
}
}

public static TheoryData CancelRequestReadFunctions
=> new TheoryData<bool, Func<Task<int>>>
{
{ false, () => Task.FromResult(0) },
{ true, () => Task.FromResult(0) },
{ false, () => Task.FromResult(1) },
{ true, () => Task.FromResult(1) },
{ false, () => throw new FormatException() },
{ true, () => throw new FormatException() },
};

[OuterLoop]
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))]
[MemberData(nameof(CancelRequestReadFunctions))]
public async Task BrowserHttpHandler_StreamingRequest_CancelRequest(bool cancelAsync, Func<Task<int>> readFunc)
{
var WebAssemblyEnableStreamingRequestKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingRequest");

var req = new HttpRequestMessage(HttpMethod.Post, Configuration.Http.Http2RemoteEchoServer);

req.Options.Set(WebAssemblyEnableStreamingRequestKey, true);

using var cts = new CancellationTokenSource();
var token = cts.Token;
int readNotCancelledCount = 0, readCancelledCount = 0;
req.Content = new StreamContent(new DelegateStream(
canReadFunc: () => true,
readFunc: (buffer, offset, count) => throw new FormatException(),
readAsyncFunc: async (buffer, offset, count, cancellationToken) =>
{
if (cancelAsync) await Task.Delay(1);
Assert.Equal(token.IsCancellationRequested, cancellationToken.IsCancellationRequested);
if (!token.IsCancellationRequested)
{
readNotCancelledCount++;
cts.Cancel();
}
else
{
readCancelledCount++;
}
return await readFunc();
}));

using (HttpClient client = CreateHttpClientForRemoteServer(Configuration.Http.RemoteHttp2Server))
{
TaskCanceledException ex = await Assert.ThrowsAsync<TaskCanceledException>(() => client.SendAsync(req, token));
Assert.Equal(token, ex.CancellationToken);
Assert.Equal(1, readNotCancelledCount);
Assert.Equal(0, readCancelledCount);
}
}

[OuterLoop]
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))]
public async Task BrowserHttpHandler_StreamingRequest_Http1Fails()
{
var WebAssemblyEnableStreamingRequestKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingRequest");

var req = new HttpRequestMessage(HttpMethod.Post, Configuration.Http.RemoteHttp11Server.BaseUri);

req.Options.Set(WebAssemblyEnableStreamingRequestKey, true);

int readCount = 0;
req.Content = new StreamContent(new DelegateStream(
canReadFunc: () => true,
readFunc: (buffer, offset, count) => throw new FormatException(),
readAsyncFunc: (buffer, offset, count, cancellationToken) =>
{
readCount++;
return Task.FromResult(1);
}));

using (HttpClient client = CreateHttpClientForRemoteServer(Configuration.Http.RemoteHttp11Server))
{
HttpRequestException ex = await Assert.ThrowsAsync<HttpRequestException>(() => client.SendAsync(req));
Assert.Equal("TypeError: Failed to fetch", ex.Message);
Assert.Equal(1, readCount);
}
}

[OuterLoop]
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))]
public async Task BrowserHttpHandler_StreamingResponse()
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/System.Net.Http/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,9 @@
<data name="net_http_synchronous_reads_not_supported" xml:space="preserve">
<value>Synchronous reads are not supported, use ReadAsync instead.</value>
</data>
<data name="net_http_synchronous_writes_not_supported" xml:space="preserve">
<value>Synchronous writes are not supported, use WriteAsync instead.</value>
</data>
<data name="net_socks_auth_failed" xml:space="preserve">
<value>Failed to authenticate with the SOCKS server.</value>
</data>
Expand Down
Loading
Loading