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] Make response streaming opt-out #111680

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,6 @@ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
var req = new HttpRequestMessage(HttpMethod.Get, url) { Version = UseVersion };
req.Headers.ConnectionClose = connectionClose;

#if TARGET_BROWSER
var WebAssemblyEnableStreamingResponseKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse");
req.Options.Set(WebAssemblyEnableStreamingResponseKey, true);
#endif

Task<HttpResponseMessage> getResponse = client.SendAsync(TestAsync, req, HttpCompletionOption.ResponseHeadersRead, cts.Token);
await ValidateClientCancellationAsync(async () =>
{
Expand Down
45 changes: 25 additions & 20 deletions src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1005,12 +1005,9 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion };
if (PlatformDetection.IsBrowser)
{
if (enableWasmStreaming)
{
#if !NETFRAMEWORK
request.Options.Set(new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse"), true);
request.Options.Set(new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse"), enableWasmStreaming);
#endif
}
}

using (var client = new HttpMessageInvoker(CreateHttpClientHandler()))
Expand Down Expand Up @@ -1239,7 +1236,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
// Boolean properties returning correct values
Assert.True(responseStream.CanRead);
Assert.False(responseStream.CanWrite);
Assert.Equal(PlatformDetection.IsBrowser, responseStream.CanSeek);
Assert.False(responseStream.CanSeek);

// Not supported operations
Assert.Throws<NotSupportedException>(() => responseStream.BeginWrite(new byte[1], 0, 1, null, null));
Expand Down Expand Up @@ -1270,11 +1267,14 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.CopyToAsync(Stream.Null, -1, default); });
Assert.Throws<NotSupportedException>(() => { responseStream.CopyToAsync(nonWritableStream, 100, default); });
Assert.Throws<ObjectDisposedException>(() => { responseStream.CopyToAsync(disposedStream, 100, default); });
Assert.Throws<ArgumentNullException>(() => responseStream.Read(null, 0, 100));
Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], -1, 1));
Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 2, 1));
Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], 0, -1));
Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 0, 2));
if (PlatformDetection.IsNotBrowser)
{
Assert.Throws<ArgumentNullException>(() => responseStream.Read(null, 0, 100));
Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], -1, 1));
Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 2, 1));
Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], 0, -1));
Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 0, 2));
}
Assert.Throws<ArgumentNullException>(() => responseStream.BeginRead(null, 0, 100, null, null));
Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.BeginRead(new byte[1], -1, 1, null, null));
Assert.ThrowsAny<ArgumentException>(() => responseStream.BeginRead(new byte[1], 2, 1, null, null));
Expand All @@ -1284,29 +1284,37 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
Assert.Throws<ArgumentNullException>(() => { responseStream.CopyTo(null); });
Assert.Throws<ArgumentNullException>(() => { responseStream.CopyToAsync(null, 100, default); });
Assert.Throws<ArgumentNullException>(() => { responseStream.CopyToAsync(null, 100, default); });
Assert.Throws<ArgumentNullException>(() => { responseStream.Read(null, 0, 100); });
Assert.Throws<ArgumentNullException>(() => { responseStream.ReadAsync(null, 0, 100, default); });
Assert.Throws<ArgumentNullException>(() => { responseStream.BeginRead(null, 0, 100, null, null); });

// Empty reads
var buffer = new byte[1];
Assert.Equal(-1, responseStream.ReadByte());
Assert.Equal(0, await Task.Factory.FromAsync(responseStream.BeginRead, responseStream.EndRead, buffer, 0, 1, null));
if (PlatformDetection.IsNotBrowser)
{
Assert.Equal(-1, responseStream.ReadByte());
Assert.Equal(0, await Task.Factory.FromAsync(responseStream.BeginRead, responseStream.EndRead, buffer, 0, 1, null));
}
#if !NETFRAMEWORK
Assert.Equal(0, await responseStream.ReadAsync(new Memory<byte>(buffer)));
#endif
Assert.Equal(0, await responseStream.ReadAsync(buffer, 0, 1));
if (PlatformDetection.IsNotBrowser)
{
#if !NETFRAMEWORK
Assert.Equal(0, responseStream.Read(new Span<byte>(buffer)));
Assert.Equal(0, responseStream.Read(new Span<byte>(buffer)));
#endif
Assert.Equal(0, responseStream.Read(buffer, 0, 1));
Assert.Equal(0, responseStream.Read(buffer, 0, 1));
}

// Empty copies
var ms = new MemoryStream();
await responseStream.CopyToAsync(ms);
Assert.Equal(0, ms.Length);
responseStream.CopyTo(ms);
Assert.Equal(0, ms.Length);
if (PlatformDetection.IsNotBrowser)
{
responseStream.CopyTo(ms);
Assert.Equal(0, ms.Length);
}
}
}
},
Expand All @@ -1322,9 +1330,6 @@ public async Task ReadAsStreamAsync_StreamingCancellation()
await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
{
var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion };
#if !NETFRAMEWORK
request.Options.Set(new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse"), true);
#endif

var cts = new CancellationTokenSource();
using (var client = new HttpMessageInvoker(CreateHttpClientHandler()))
Expand Down
19 changes: 9 additions & 10 deletions src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public static IEnumerable<object[]> RemoteServersAndReadModes()
{
for (int i = 0; i < 8; i++)
{
if (PlatformDetection.IsBrowser && i is 0 or 2 or 4 or 5 or 6) continue; // ignore sync reads

yield return new object[] { remoteServer, i };
}
}
Expand Down Expand Up @@ -176,10 +178,13 @@ public async Task GetStreamAsync_ReadZeroBytes_Success(Configuration.Http.Remote
using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
using (Stream stream = await client.GetStreamAsync(remoteServer.EchoUri))
{
Assert.Equal(0, stream.Read(new byte[1], 0, 0));
if (PlatformDetection.IsNotBrowser)
{
Assert.Equal(0, stream.Read(new byte[1], 0, 0));
#if !NETFRAMEWORK
Assert.Equal(0, stream.Read(new Span<byte>(new byte[1], 0, 0)));
Assert.Equal(0, stream.Read(new Span<byte>(new byte[1], 0, 0)));
#endif
}
Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 0));
}
}
Expand All @@ -200,7 +205,7 @@ await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeaders
cts.Cancel();

// Verify that the task completed.
Assert.True(((IAsyncResult)task).AsyncWaitHandle.WaitOne(new TimeSpan(0, 5, 0)));
Assert.Same(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromMinutes(5))));
Assert.True(task.IsCompleted, "Task was not yet completed");

// Verify that the task completed successfully or is canceled.
Expand Down Expand Up @@ -234,12 +239,10 @@ await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeaders
public async Task BrowserHttpHandler_Streaming()
{
var WebAssemblyEnableStreamingRequestKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingRequest");
var WebAssemblyEnableStreamingResponseKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse");

var req = new HttpRequestMessage(HttpMethod.Post, Configuration.Http.RemoteHttp2Server.BaseUri + "echobody.ashx");

req.Options.Set(WebAssemblyEnableStreamingRequestKey, true);
req.Options.Set(WebAssemblyEnableStreamingResponseKey, true);

byte[] body = new byte[1024 * 1024];
Random.Shared.NextBytes(body);
Expand Down Expand Up @@ -488,13 +491,9 @@ public async Task BrowserHttpHandler_StreamingRequest_Http1Fails()
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsChromium))]
public async Task BrowserHttpHandler_StreamingResponse()
{
var WebAssemblyEnableStreamingResponseKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse");

var size = 1500 * 1024 * 1024;
var req = new HttpRequestMessage(HttpMethod.Get, Configuration.Http.RemoteSecureHttp11Server.BaseUri + "large.ashx?size=" + size);

req.Options.Set(WebAssemblyEnableStreamingResponseKey, true);

using (HttpClient client = CreateHttpClientForRemoteServer(Configuration.Http.RemoteSecureHttp11Server))
// we need to switch off Response buffering of default ResponseContentRead option
using (HttpResponseMessage response = await client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead))
Expand All @@ -512,7 +511,7 @@ public async Task BrowserHttpHandler_StreamingResponse()
int fetchedCount = 0;
do
{
// with WebAssemblyEnableStreamingResponse option set, we will be using https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read
// we will be using https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read
fetchedCount = await stream.ReadAsync(buffer, 0, buffer.Length);
totalCount += fetchedCount;
} while (fetchedCount != 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ internal sealed class BrowserHttpController : IDisposable
private readonly string uri;
private readonly CancellationToken _cancellationToken;
private readonly HttpRequestMessage _request;
private bool _isDisposed;
internal bool _isDisposed;

public BrowserHttpController(HttpRequestMessage request, bool? allowAutoRedirect, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -314,13 +314,7 @@ private HttpResponseMessage ConvertResponse()
responseMessage.SetReasonPhraseWithoutValidation(responseType);
}

bool streamingResponseEnabled = false;
if (BrowserHttpInterop.SupportsStreamingResponse())
{
_request.Options.TryGetValue(EnableStreamingResponse, out streamingResponseEnabled);
}

responseMessage.Content = streamingResponseEnabled
responseMessage.Content = (_request.Options.TryGetValue(EnableStreamingResponse, out var streamingResponseEnabled) ? streamingResponseEnabled : true) && BrowserHttpInterop.SupportsStreamingResponse()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all browsers support response streaming, should we remove BrowserHttpInterop.SupportsStreamingResponse?

? new StreamContent(new BrowserHttpReadStream(this))
: new BrowserHttpContent(this);

Expand Down Expand Up @@ -361,10 +355,10 @@ public void Dispose()
internal sealed class BrowserHttpWriteStream : Stream
{
private readonly BrowserHttpController _controller; // we don't own it, we don't dispose it from here

public BrowserHttpWriteStream(BrowserHttpController controller)
{
ArgumentNullException.ThrowIfNull(controller);

_controller = controller;
}

Expand Down Expand Up @@ -392,7 +386,7 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati

public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override bool CanWrite => !_controller._isDisposed;

protected override void Dispose(bool disposing)
{
Expand Down Expand Up @@ -506,7 +500,7 @@ protected override void Dispose(bool disposing)

internal sealed class BrowserHttpReadStream : Stream
{
private BrowserHttpController _controller; // we own the object and have to dispose it
private readonly BrowserHttpController _controller; // we own the object and have to dispose it

public BrowserHttpReadStream(BrowserHttpController controller)
{
Expand Down Expand Up @@ -540,7 +534,7 @@ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, Cancel
return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
}

public override bool CanRead => true;
public override bool CanRead => !_controller._isDisposed;
public override bool CanSeek => false;
public override bool CanWrite => false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ await executor.Execute(async () =>
}

private static HttpRequestOptionsKey<bool> WebAssemblyEnableStreamingRequestKey = new("WebAssemblyEnableStreamingRequest");
private static HttpRequestOptionsKey<bool> WebAssemblyEnableStreamingResponseKey = new("WebAssemblyEnableStreamingResponse");
private static string HelloJson = "{'hello':'world'}".Replace('\'', '"');
private static string EchoStart = "{\"Method\":\"POST\",\"Url\":\"/Echo.ashx";

Expand All @@ -46,7 +45,6 @@ private async Task HttpClient_ActionInDifferentThread(string url, Executor execu
await ms.WriteAsync(Encoding.UTF8.GetBytes(HelloJson));

using var req = new HttpRequestMessage(HttpMethod.Post, url);
req.Options.Set(WebAssemblyEnableStreamingResponseKey, true);
req.Content = new StreamContent(ms);
using var client = new HttpClient();
var pr = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
Expand Down
Loading