diff --git a/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs b/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs index 98d1bede15eb51..f4a9cd4998a0b9 100644 --- a/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs @@ -230,6 +230,59 @@ await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeaders #if NETCOREAPP + [OuterLoop] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))] + public async Task BrowserHttpHandler_Streaming() + { + var WebAssemblyEnableStreamingRequestKey = new HttpRequestOptionsKey("WebAssemblyEnableStreamingRequest"); + var WebAssemblyEnableStreamingResponseKey = new HttpRequestOptionsKey("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); + + int readOffset = 0; + req.Content = new StreamContent(new DelegateStream( + readAsyncFunc: async (buffer, offset, count, cancellationToken) => + { + await Task.Delay(1); + if (readOffset < body.Length) + { + int send = Math.Min(body.Length - readOffset, count); + body.AsSpan(readOffset, send).CopyTo(buffer.AsSpan(offset, send)); + readOffset += send; + return send; + } + return 0; + })); + + using (HttpClient client = CreateHttpClientForRemoteServer(Configuration.Http.RemoteHttp2Server)) + using (HttpResponseMessage response = await client.SendAsync(req)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + // Streaming requests can't set Content-Length + Assert.False(response.Headers.Contains("X-HttpRequest-Headers-ContentLength")); + // Streaming response uses StreamContent + Assert.Equal(typeof(StreamContent), response.Content.GetType()); + + var stream = await response.Content.ReadAsStreamAsync(); + var buffer = new byte[1024 * 1024]; + int totalCount = 0; + int fetchedCount = 0; + do + { + fetchedCount = await stream.ReadAsync(buffer, 0, buffer.Length); + Assert.True(body.AsSpan(totalCount, fetchedCount).SequenceEqual(buffer.AsSpan(0, fetchedCount))); + totalCount += fetchedCount; + } while (fetchedCount != 0); + Assert.Equal(body.Length, totalCount); + } + } + [OuterLoop] [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))] [InlineData(true)] @@ -242,17 +295,17 @@ public async Task BrowserHttpHandler_StreamingRequest(bool useStringContent) req.Options.Set(WebAssemblyEnableStreamingRequestKey, true); - int expectedBodyLength; + int size; if (useStringContent) { string bodyContent = "Hello World"; - expectedBodyLength = bodyContent.Length; + size = bodyContent.Length; req.Content = new StringContent(bodyContent); } else { - expectedBodyLength = 1500 * 1024 * 1024; - int remaining = expectedBodyLength; + size = 1500 * 1024 * 1024; + int remaining = size; req.Content = new StreamContent(new DelegateStream( readAsyncFunc: (buffer, offset, count, cancellationToken) => { @@ -273,11 +326,12 @@ public async Task BrowserHttpHandler_StreamingRequest(bool useStringContent) using (HttpResponseMessage response = await client.SendAsync(req)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(expectedBodyLength.ToString(), Assert.Single(response.Headers.GetValues("X-HttpRequest-Body-Length"))); + Assert.Equal(size.ToString(), Assert.Single(response.Headers.GetValues("X-HttpRequest-Body-Length"))); + // Streaming requests can't set Content-Length Assert.Equal(useStringContent, response.Headers.Contains("X-HttpRequest-Headers-ContentLength")); if (useStringContent) { - Assert.Equal(expectedBodyLength.ToString(), Assert.Single(response.Headers.GetValues("X-HttpRequest-Headers-ContentLength"))); + Assert.Equal(size.ToString(), Assert.Single(response.Headers.GetValues("X-HttpRequest-Headers-ContentLength"))); } } } @@ -327,6 +381,7 @@ public async Task BrowserHttpHandler_StreamingResponse() // we need to switch off Response buffering of default ResponseContentRead option using (HttpResponseMessage response = await client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead)) { + // Streaming response uses StreamContent Assert.Equal(typeof(StreamContent), response.Content.GetType()); Assert.Equal("application/octet-stream", response.Content.Headers.ContentType.MediaType); diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/GenericHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/GenericHandler.cs index 846a30fd9951ef..31fa23f16de688 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/GenericHandler.cs +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/GenericHandler.cs @@ -88,6 +88,11 @@ public async Task Invoke(HttpContext context) await LargeResponseHandler.InvokeAsync(context); return; } + if (path.Equals(new PathString("/echobody.ashx"))) + { + await EchoBodyHandler.InvokeAsync(context); + return; + } // Default handling. await EchoHandler.InvokeAsync(context); diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoBodyHandler.cs b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoBodyHandler.cs new file mode 100644 index 00000000000000..3ddf4f62535e6d --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/Handlers/EchoBodyHandler.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; + +namespace NetCoreServer +{ + public class EchoBodyHandler + { + public static async Task InvokeAsync(HttpContext context) + { + context.Features.Get().MaxRequestBodySize = null; + + // Report back original request method verb. + context.Response.Headers["X-HttpRequest-Method"] = context.Request.Method; + + // Report back original entity-body related request headers. + string contentLength = context.Request.Headers["Content-Length"]; + if (!string.IsNullOrEmpty(contentLength)) + { + context.Response.Headers["X-HttpRequest-Headers-ContentLength"] = contentLength; + } + + string transferEncoding = context.Request.Headers["Transfer-Encoding"]; + if (!string.IsNullOrEmpty(transferEncoding)) + { + context.Response.Headers["X-HttpRequest-Headers-TransferEncoding"] = transferEncoding; + } + + context.Response.StatusCode = 200; + context.Response.ContentType = context.Request.ContentType; + await context.Request.Body.CopyToAsync(context.Response.Body); + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/NetCoreServer.csproj b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/NetCoreServer.csproj index a654a21e298c06..a458eb3eeec5ed 100644 --- a/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/NetCoreServer.csproj +++ b/src/libraries/Common/tests/System/Net/Prerequisites/NetCoreServer/NetCoreServer.csproj @@ -26,6 +26,7 @@ +