Skip to content

Commit 4ad310d

Browse files
authored
[browser] [wasm] Refactor Request Streaming to use HttpContent.CopyToAsync (#91699)
1 parent 3470c4c commit 4ad310d

File tree

10 files changed

+316
-182
lines changed

10 files changed

+316
-182
lines changed

src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs

+93-7
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ public async Task BrowserHttpHandler_Streaming()
246246

247247
int readOffset = 0;
248248
req.Content = new StreamContent(new DelegateStream(
249+
canReadFunc: () => true,
250+
readFunc: (buffer, offset, count) => throw new FormatException(),
249251
readAsyncFunc: async (buffer, offset, count, cancellationToken) =>
250252
{
251253
await Task.Delay(1);
@@ -295,8 +297,12 @@ public async Task BrowserHttpHandler_StreamingRequest()
295297
req.Options.Set(WebAssemblyEnableStreamingRequestKey, true);
296298

297299
int size = 1500 * 1024 * 1024;
300+
int multipartOverhead = 125 + 4 /* "test" */;
298301
int remaining = size;
299-
req.Content = new StreamContent(new DelegateStream(
302+
var content = new MultipartFormDataContent();
303+
content.Add(new StreamContent(new DelegateStream(
304+
canReadFunc: () => true,
305+
readFunc: (buffer, offset, count) => throw new FormatException(),
300306
readAsyncFunc: (buffer, offset, count, cancellationToken) =>
301307
{
302308
if (remaining > 0)
@@ -307,15 +313,16 @@ public async Task BrowserHttpHandler_StreamingRequest()
307313
return Task.FromResult(send);
308314
}
309315
return Task.FromResult(0);
310-
}));
316+
})), "test");
317+
req.Content = content;
311318

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

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

337344
Exception error = new FormatException();
338-
var content = new StreamContent(new DelegateStream(
345+
req.Content = new StreamContent(new DelegateStream(
339346
canSeekFunc: () => true,
340347
lengthFunc: () => 12345678,
341348
positionGetFunc: () => 0,
342349
canReadFunc: () => true,
343-
readFunc: (buffer, offset, count) => throw error,
350+
readFunc: (buffer, offset, count) => throw new FormatException(),
344351
readAsyncFunc: (buffer, offset, count, cancellationToken) => syncFailure ? throw error : Task.Delay(1).ContinueWith<int>(_ => throw error)));
345352

346-
req.Content = content;
347-
348353
using (HttpClient client = CreateHttpClientForRemoteServer(Configuration.Http.RemoteHttp2Server))
349354
{
350355
Assert.Same(error, await Assert.ThrowsAsync<FormatException>(() => client.SendAsync(req)));
351356
}
352357
}
353358

359+
public static TheoryData CancelRequestReadFunctions
360+
=> new TheoryData<bool, Func<Task<int>>>
361+
{
362+
{ false, () => Task.FromResult(0) },
363+
{ true, () => Task.FromResult(0) },
364+
{ false, () => Task.FromResult(1) },
365+
{ true, () => Task.FromResult(1) },
366+
{ false, () => throw new FormatException() },
367+
{ true, () => throw new FormatException() },
368+
};
369+
370+
[OuterLoop]
371+
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))]
372+
[MemberData(nameof(CancelRequestReadFunctions))]
373+
public async Task BrowserHttpHandler_StreamingRequest_CancelRequest(bool cancelAsync, Func<Task<int>> readFunc)
374+
{
375+
var WebAssemblyEnableStreamingRequestKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingRequest");
376+
377+
var req = new HttpRequestMessage(HttpMethod.Post, Configuration.Http.Http2RemoteEchoServer);
378+
379+
req.Options.Set(WebAssemblyEnableStreamingRequestKey, true);
380+
381+
using var cts = new CancellationTokenSource();
382+
var token = cts.Token;
383+
int readNotCancelledCount = 0, readCancelledCount = 0;
384+
req.Content = new StreamContent(new DelegateStream(
385+
canReadFunc: () => true,
386+
readFunc: (buffer, offset, count) => throw new FormatException(),
387+
readAsyncFunc: async (buffer, offset, count, cancellationToken) =>
388+
{
389+
if (cancelAsync) await Task.Delay(1);
390+
Assert.Equal(token.IsCancellationRequested, cancellationToken.IsCancellationRequested);
391+
if (!token.IsCancellationRequested)
392+
{
393+
readNotCancelledCount++;
394+
cts.Cancel();
395+
}
396+
else
397+
{
398+
readCancelledCount++;
399+
}
400+
return await readFunc();
401+
}));
402+
403+
using (HttpClient client = CreateHttpClientForRemoteServer(Configuration.Http.RemoteHttp2Server))
404+
{
405+
TaskCanceledException ex = await Assert.ThrowsAsync<TaskCanceledException>(() => client.SendAsync(req, token));
406+
Assert.Equal(token, ex.CancellationToken);
407+
Assert.Equal(1, readNotCancelledCount);
408+
Assert.Equal(0, readCancelledCount);
409+
}
410+
}
411+
412+
[OuterLoop]
413+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))]
414+
public async Task BrowserHttpHandler_StreamingRequest_Http1Fails()
415+
{
416+
var WebAssemblyEnableStreamingRequestKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingRequest");
417+
418+
var req = new HttpRequestMessage(HttpMethod.Post, Configuration.Http.RemoteHttp11Server.BaseUri);
419+
420+
req.Options.Set(WebAssemblyEnableStreamingRequestKey, true);
421+
422+
int readCount = 0;
423+
req.Content = new StreamContent(new DelegateStream(
424+
canReadFunc: () => true,
425+
readFunc: (buffer, offset, count) => throw new FormatException(),
426+
readAsyncFunc: (buffer, offset, count, cancellationToken) =>
427+
{
428+
readCount++;
429+
return Task.FromResult(1);
430+
}));
431+
432+
using (HttpClient client = CreateHttpClientForRemoteServer(Configuration.Http.RemoteHttp11Server))
433+
{
434+
HttpRequestException ex = await Assert.ThrowsAsync<HttpRequestException>(() => client.SendAsync(req));
435+
Assert.Equal("TypeError: Failed to fetch", ex.Message);
436+
Assert.Equal(1, readCount);
437+
}
438+
}
439+
354440
[OuterLoop]
355441
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))]
356442
public async Task BrowserHttpHandler_StreamingResponse()

src/libraries/System.Net.Http/src/Resources/Strings.resx

+3
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,9 @@
534534
<data name="net_http_synchronous_reads_not_supported" xml:space="preserve">
535535
<value>Synchronous reads are not supported, use ReadAsync instead.</value>
536536
</data>
537+
<data name="net_http_synchronous_writes_not_supported" xml:space="preserve">
538+
<value>Synchronous writes are not supported, use WriteAsync instead.</value>
539+
</data>
537540
<data name="net_socks_auth_failed" xml:space="preserve">
538541
<value>Failed to authenticate with the SOCKS server.</value>
539542
</data>

0 commit comments

Comments
 (0)